无法使用 BouncyCastle 重现 openssl 命令

Can't reproduce openssl command with BouncyCastle

我正在尝试使用 bouncycastle 和 java.

重现一些 openssl 命令已经有几个星期了

在遵循了大量示例并尝试了 Whosebug 中的大量示例之后,我仍然无法使其正常工作,这就是我现在寻求帮助的原因。

我必须重现的 openssl 命令是:

openssl smime -sign -in fileToSign.eml -out signedFile.step2 -passin pass:« password» -binary -nodetach -signer myprivatecert.pem -certfile mypubliccert.pem

第一个命令需要 3 个文件,要签名的文件,一个私有证书和一个 public 证书。

它 returns 文件看起来像:

MIME-Version: 1.0 Content-Disposition: attachment; filename="smime.p7m" Content-Type: application/x-pkcs7-mime; smime-type=signed-data; name="smime.p7m" Content-Transfer-Encoding: base64

MIJAYAYJKoZIhvcNAQcCoIJAUTCCQE0CAQExDzANBglghkgBZQMEAgEFADCCNTUG CSqGSIb3DQEHAaCCNSYEgjUiQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7 CmJvdW5kYXJ5PSItLS0tPUxPR0lQT0xfTUlGXzE1NDY4NTAwNDc4MTYiCi0tLS0t LT1MT0dJUE9MX01JRl8xNTQ2ODUwMDQ3ODE2DQpDb250ZW50LVR5cGU6IHRleHQv WE1MOw0KbmFtZT0iUERBX1A5MDAxMjZfMDA1XzIwMTkwMTA3LjA5MzIwMF8wMDAw MV9JTklULnhtbCI7IGZpbGVuYW1lPSJQREFfUDkwMDEyNl8wMDVfMjAxOTAxMDcu MDkzMjAwXzAwMDAxX0lOSVQueG1sIg0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGlu ZzogYmFzZTY0DQoNClBEOTRiV3dnZG1WeWMybHZiajBpTVM0d0lpQmxibU52Wkds dVp6MGlWVlJHTFRnaVB6NDhUVWxHVmtGUFNXNW1iMGx1YVhScFlXeGwNClBnbzhT VzVtYjNNK0NqeFdaWEp6YVc5dVBqSXVPVHd2Vm1WeWMybHZiajRLUEVodmNtOWtZ WFJsUGpJd01Ua3RNREV0TURkVU1EazYNCk16UTZNRGM4TDBodmNtOWtZWFJsUGdv OFUyRnBjMmxsU0c5eWIyUmhkR1UrTWpBeE9TMHdNUzB3TjFRd09Ub3pNam93TUR3...

我必须使用的第二个命令是:

openssl smime -encrypt -in signedFile.step2 -out encryptedFile.P7M -outform DER -binary anotherpubliccertificate.pub.pem

此命令需要 2 个文件,由上一个命令签名的文件和一个 public 证书,与上一个命令中使用的证书不同。

这个returns一个二进制文件,一个从第2步生成的加密文件。

我在 Internet 上找到的任何示例都帮助我得到了一个看起来像这些以前的文件的文件,甚至没有接近。

希望有人能帮忙

编辑 到目前为止我尝试或参考的一些例子

-> 这返回了一个与 openssl

生成的签名文件不对应的签名文件

AES encrypt/decrypt with Bouncy Castle provider -> 同样,这不起作用,结果与我用 openssl

生成的加密文件不对应

https://studylibfr.com/doc/3898805/cryptographie-avec-bouncy-castle---zenk -> 一直跟着整个教程,没有得到预期的结果

-> 签名文件也不对应

https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java -> 这个 class 生成的东西看起来与我想要得到的东西相似,但我无法测试它的有效性,因为我必须加密它并且仍然可以'使加密工作

https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/ReadSignedMail.java -> 与之前相同 class

https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java -> 这种加密方法返回的结果与 openssl 不同,因此它不起作用

可以肯定的是,我已经尝试继续研究来自 bouncycastle 的这些示例 classes,但没有成功。

如有任何帮助,我们将不胜感激

编辑 2 以下问题的答案 Sign and encrypt a file using S/MIME returns 一个 Base64 编码的文件,可能与我用 openssl 生成的文件相对应。但问题是我的入口文件大约 25kb 而生成的签名文件只有 3kb,我不明白为什么,我注意到这一行:

CMSTypedData content = new CMSProcessableByteArray(buffer);
CMSSignedData signedData = signGen.generate(content, false);
byte[] signeddata = signedData.getEncoded();

getEncoded() 方法 returns 我的字节数组比我发送到 CMSSignedData 的缓冲区小得多。

有谁知道原因吗?

对于签名,你与 org.bouncycastle.mail.smime.examples.CreateSignedMultipartMail 相当接近,除了

  • 它做多部分数据,而openssl smime不做;从 CreateSignedMail 开始,而不是

  • 它执行多部分签名,又名清晰签名,openssl smime也默认为,但-nodetach将其更改为嵌入式又名封装

  • 它包括一个完整的证书链,但是一个自生成的长度只有 2,而几乎所有 'real' 证书都更长,而默认情况下 openssl 只包括签名者证书

  • 默认情况下它使用一些与 openssl 不同的签名属性

对于加密(或更准确地说是包络)openssl smime -outform der 尽管名称根本不执行 SMIME,但它执行 CMS(最初也称为 PKCS7)。 Bouncy利用Java的完整OO优势,将非常相似但不相同的CMS和SMIME放入相关但不相同的不同class中,因此您需要CMS class(es).

将这些放在一起(加上一个最小的测试工具),我向您展示:

    // for test, (own) signing key+certchain and (peer) encryption cert in file
    KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream(args[0]),args[1].toCharArray());
    PrivateKey signkey = (PrivateKey) ks.getKey(args[2], args[1].toCharArray());
    Certificate[] signcert = ks.getCertificateChain(args[2]);
    Certificate encrcert = ks.getCertificate(args[3]);
    // and data in file
    byte[] data = Files.readAllBytes(new File(args[4]).toPath());

    // adapted from org.bouncycastle.mail.smime.examples.CreateSignedMail 
    // OpenSSL uses this rather silly capability list; may not be needed 
    SMIMECapabilityVector       caps = new SMIMECapabilityVector();
    caps.addCapability(SMIMECapability.aES256_CBC);
    caps.addCapability(SMIMECapability.aES192_CBC);
    caps.addCapability(SMIMECapability.aES128_CBC);
    caps.addCapability(SMIMECapability.dES_EDE3_CBC);
    caps.addCapability(SMIMECapability.rC2_CBC, 128);
    caps.addCapability(SMIMECapability.rC2_CBC, 64);
    caps.addCapability(SMIMECapability.dES_CBC);
    caps.addCapability(SMIMECapability.rC2_CBC, 40);
    ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
    signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
    // Bouncy default adds RFC6211 in addition to standard ctype, stime, mdgst
    // and changing this is complicated; recipient _should_ ignore unneeded attr

    SMIMESignedGenerator gen = new SMIMESignedGenerator();
    gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder()//.setProvider("BC") not needed
        .setSignedAttributeGenerator(new AttributeTable(signedAttrs))
        .build("SHA1withRSA", signkey, (X509Certificate) signcert[0]) ); 
    // change sigalg if key not RSA and/or want better hash
    // OpenSSL by default includes only signer cert; recipient may want more
    gen.addCertificates(new JcaCertStore (Arrays.asList (new Certificate[]{signcert[0]}) ));

    MimeBodyPart msg = new MimeBodyPart();
    msg.setText(new String(data, "ISO-8859-1")); // OpenSSL doesn't know charsets
    ByteArrayOutputStream temp = new ByteArrayOutputStream();
    gen.generateEncapsulated(msg).writeTo(temp); // OpenSSL -nodetach is encapsulated
    // Bouncy uses BER here (unlike OpenSSL DER) 
    // and I don't see a simple way to change it but it _should_ not matter 
    byte[] signedblob = temp.toByteArray();

    // now CMS (not SMIME) enveloping
    CMSEnvelopedDataGenerator edgen = new CMSEnvelopedDataGenerator();
    edgen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate) encrcert));
    CMSEnvelopedData edmsg = edgen.generate( new CMSProcessableByteArray(signedblob),
            new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).build() );
    byte[] encrblob = edmsg.toASN1Structure().getEncoded(ASN1Encoding.DER); // OpenSSL is DER though std doesn't require it

    // for test, write to a file
    Files.write(new File(args[5]).toPath(), encrblob);

开,'does anyone know the reason'

CMSSignedData signedData = signGen.generate(content, false);
byte[] signeddata = signedData.getEncoded();

小于内容?请参阅 the javadoc - encapsulate(第二个参数)设置为 false 你告诉它 不要 在签名中包含内容(更准确地说SignedData),它按照你的要求做了。