使用已加密的内容和加密的密钥创建 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);
}