使用已加密的内容和加密的密钥创建 CMS 封装数据
Create CMS Enveloped Data with already encrypted content and encrypted key
tldr:
当我已经使用 AES 加密内容和已经使用 public 密钥加密的密钥时,有没有办法创建 CMS 封装数据?
长版:
我有一个使用 AES(CBC 和 GCM 模式)加密和解密数据的应用程序。对称密钥是 encrypted/decrypted 和 RSA 密钥对。当用户请求数据时,我们在后端对其进行解密 (Java) 并将其发送到浏览器
通常我们有 public 密钥和私钥,但在某些情况下我们没有私钥并且解密应该在浏览器中进行(用户提供带有 privatkey 的 PFX)。解决方案是 PKI.js,它可以使用 PFX 和 CMS Enveloped Data 解密数据。
问题是我们已经对数据进行了加密,无法访问可用于构建 CMS 封装数据的纯数据。
编辑:
@dave_thompson_085 谢谢回复!我有一个后续问题。我没有在系统中持有证书,所以我只有 public 密钥。有没有办法根据此要求调整您的代码?
在您回答之前,我第二次为 CMS 封装对象加密数据。在此代码中,我仅使用 public 密钥来生成收件人。有没有办法调整您的代码以仅使用 public 密钥生成收件人?
我之前的代码:
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
RecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(subjectKeyIdentifier.getEncoded(),oaepAlgId,publicKey).setProvider("BC");
envelopedDataGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
那么哈希算法呢?我是否需要一个或只是额外的保护来确保 CMS 封装对象没有改变?
FWIW 你可以只使用 BouncyCastle 来做 DER 格式化(加上设置版本,一个小的方便),如果你想要的话加上 PEM(也是一个小的方便),在你自己完成所有剩下的工作之后.示例:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
// sample data and encryption, replace as needed
byte[] input = "testdata".getBytes();
X509Certificate cert = null;
try(InputStream is = new FileInputStream(args[0])){
cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
}
byte[] skey = new byte[16], snonce = new byte[16];
SecureRandom rand = new SecureRandom(); rand.nextBytes(skey); rand.nextBytes(snonce);
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(skey,"AES"), new IvParameterSpec(snonce));
byte[] ctx1 = aes.doFinal(input);
Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
byte[] ctx2 = rsa.doFinal(skey);
// now build the message
byte[] issuer = cert.getIssuerX500Principal().getEncoded();
ASN1Set recips = new DERSet( new KeyTransRecipientInfo(
new RecipientIdentifier(
new IssuerAndSerialNumber(X500Name.getInstance(issuer),cert.getSerialNumber() )),
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption),
new DEROctetString(ctx2) ));
EnvelopedData env = new EnvelopedData (null/*no originfo*/, recips,
new EncryptedContentInfo(CMSObjectIdentifiers.data,
new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_CBC,
new DEROctetString(snonce) ),
new DEROctetString(ctx1) ),
new DERSet() /*no attributes*/ );
ContentInfo msg = new ContentInfo(CMSObjectIdentifiers.envelopedData, env);
try(OutputStream os = new FileOutputStream(args[1]) ){
os.write(msg.getEncoded());
}
// or use PemWriter (and a PemObject) if you want PEM
好的,我想出了如何调整代码以满足我的需要,所以我可以分享。
生成收件人:
public RecipientInfo generateRecipientInfo(){
try{
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
RecipientIdentifier recipId = new RecipientIdentifier(new DEROctetString(subjectKeyIdentifier));
return new RecipientInfo(new KeyTransRecipientInfo(recipId, getOAEPAlgorithmIdentifier(),
new DEROctetString(encryptedOaepKey)));
}catch(Exception e){
return null;
}
}
RSA-OAEP 的算法标识符:
private AlgorithmIdentifier getOAEPAlgorithmIdentifier(){
try{
String digest = "SHA-1";
String mgfDigest = "SHA-1";
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
return oaepAlgId;
}catch (Exception e){
return null;
}
}
AES CBC 的 AlgorithmIdentifier(或更改 CMSAlgorithm 后的 GCM。*)
public AlgorithmIdentifier getAlgorithmIdentifier() {
try{
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES","BC");
algorithmParameters.init(new IvParameterSpec(new byte[keyLength/8]));
return new AlgorithmIdentifier(CMSAlgorithm.AES128_CBC, AlgorithmParametersUtils.extractParameters(algorithmParameters));
}catch (Exception e){
return null;
}
}
最后生成 CMS 封装对象:
public CMSEnvelopedData generate() throws CMSException {
ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
AlgorithmIdentifier encAlgId = aesCryptography.getAlgorithmIdentifier();;
ASN1OctetString encContent = new BEROctetString(encryptedContent);;
recipientInfos.add(generateRecipientInfo());
EncryptedContentInfo eci = new EncryptedContentInfo(
CMSObjectIdentifiers.data,
encAlgId,
encContent);
ContentInfo contentInfo = new ContentInfo(
CMSObjectIdentifiers.envelopedData,
new EnvelopedData(null, new DERSet(recipientInfos), eci, (ASN1Set)null));
return new CMSEnvelopedData(contentInfo);
}
tldr:
当我已经使用 AES 加密内容和已经使用 public 密钥加密的密钥时,有没有办法创建 CMS 封装数据?
长版:
我有一个使用 AES(CBC 和 GCM 模式)加密和解密数据的应用程序。对称密钥是 encrypted/decrypted 和 RSA 密钥对。当用户请求数据时,我们在后端对其进行解密 (Java) 并将其发送到浏览器
通常我们有 public 密钥和私钥,但在某些情况下我们没有私钥并且解密应该在浏览器中进行(用户提供带有 privatkey 的 PFX)。解决方案是 PKI.js,它可以使用 PFX 和 CMS Enveloped Data 解密数据。
问题是我们已经对数据进行了加密,无法访问可用于构建 CMS 封装数据的纯数据。
编辑: @dave_thompson_085 谢谢回复!我有一个后续问题。我没有在系统中持有证书,所以我只有 public 密钥。有没有办法根据此要求调整您的代码?
在您回答之前,我第二次为 CMS 封装对象加密数据。在此代码中,我仅使用 public 密钥来生成收件人。有没有办法调整您的代码以仅使用 public 密钥生成收件人? 我之前的代码:
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
RecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(subjectKeyIdentifier.getEncoded(),oaepAlgId,publicKey).setProvider("BC");
envelopedDataGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
那么哈希算法呢?我是否需要一个或只是额外的保护来确保 CMS 封装对象没有改变?
FWIW 你可以只使用 BouncyCastle 来做 DER 格式化(加上设置版本,一个小的方便),如果你想要的话加上 PEM(也是一个小的方便),在你自己完成所有剩下的工作之后.示例:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
// sample data and encryption, replace as needed
byte[] input = "testdata".getBytes();
X509Certificate cert = null;
try(InputStream is = new FileInputStream(args[0])){
cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
}
byte[] skey = new byte[16], snonce = new byte[16];
SecureRandom rand = new SecureRandom(); rand.nextBytes(skey); rand.nextBytes(snonce);
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(skey,"AES"), new IvParameterSpec(snonce));
byte[] ctx1 = aes.doFinal(input);
Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
byte[] ctx2 = rsa.doFinal(skey);
// now build the message
byte[] issuer = cert.getIssuerX500Principal().getEncoded();
ASN1Set recips = new DERSet( new KeyTransRecipientInfo(
new RecipientIdentifier(
new IssuerAndSerialNumber(X500Name.getInstance(issuer),cert.getSerialNumber() )),
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption),
new DEROctetString(ctx2) ));
EnvelopedData env = new EnvelopedData (null/*no originfo*/, recips,
new EncryptedContentInfo(CMSObjectIdentifiers.data,
new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_CBC,
new DEROctetString(snonce) ),
new DEROctetString(ctx1) ),
new DERSet() /*no attributes*/ );
ContentInfo msg = new ContentInfo(CMSObjectIdentifiers.envelopedData, env);
try(OutputStream os = new FileOutputStream(args[1]) ){
os.write(msg.getEncoded());
}
// or use PemWriter (and a PemObject) if you want PEM
好的,我想出了如何调整代码以满足我的需要,所以我可以分享。
生成收件人:
public RecipientInfo generateRecipientInfo(){
try{
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
RecipientIdentifier recipId = new RecipientIdentifier(new DEROctetString(subjectKeyIdentifier));
return new RecipientInfo(new KeyTransRecipientInfo(recipId, getOAEPAlgorithmIdentifier(),
new DEROctetString(encryptedOaepKey)));
}catch(Exception e){
return null;
}
}
RSA-OAEP 的算法标识符:
private AlgorithmIdentifier getOAEPAlgorithmIdentifier(){
try{
String digest = "SHA-1";
String mgfDigest = "SHA-1";
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
OAEPParameterSpec oaepSpec = new OAEPParameterSpec(digest, "MGF1", new MGF1ParameterSpec(mgfDigest), PSource.PSpecified.DEFAULT);
AlgorithmIdentifier oaepAlgId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, oaepSpec);
return oaepAlgId;
}catch (Exception e){
return null;
}
}
AES CBC 的 AlgorithmIdentifier(或更改 CMSAlgorithm 后的 GCM。*)
public AlgorithmIdentifier getAlgorithmIdentifier() {
try{
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES","BC");
algorithmParameters.init(new IvParameterSpec(new byte[keyLength/8]));
return new AlgorithmIdentifier(CMSAlgorithm.AES128_CBC, AlgorithmParametersUtils.extractParameters(algorithmParameters));
}catch (Exception e){
return null;
}
}
最后生成 CMS 封装对象:
public CMSEnvelopedData generate() throws CMSException {
ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
AlgorithmIdentifier encAlgId = aesCryptography.getAlgorithmIdentifier();;
ASN1OctetString encContent = new BEROctetString(encryptedContent);;
recipientInfos.add(generateRecipientInfo());
EncryptedContentInfo eci = new EncryptedContentInfo(
CMSObjectIdentifiers.data,
encAlgId,
encContent);
ContentInfo contentInfo = new ContentInfo(
CMSObjectIdentifiers.envelopedData,
new EnvelopedData(null, new DERSet(recipientInfos), eci, (ASN1Set)null));
return new CMSEnvelopedData(contentInfo);
}