Search code examples
javaencryptioncryptographybouncycastlesign

Can't reproduce openssl command with BouncyCastle


I am trying, for already a couple of weeks to reproduce some openssl commands using bouncycastle and java.

After following a lot of samples and trying a lot of examples from Stackoverflow, I still can't make it work, thats why I'm asking for help now.

The openssl commands I have to reproduce are :

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

This first commands takes 3 files, the file to sign, a private certificate and a public certificate.

It returns a file looking like :

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...

Second command I have to use is :

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

This command takes 2 files, the file signed by the previous command and a public certificate, different than the one used in the previous command.

This returns a binary file, an encrypted file generated from step 2.

Any of the examples I found over the internet helped me to get a file looking like these previous ones, not even close.

I hope someone can help

Edit A few examples of what I tried, or refered to, until now

sign file with bouncy castle in java -> This returned a signed file that do not correspond to the signed file generated with openssl

AES encrypt/decrypt with Bouncy Castle provider -> Again, this isn't working, the result doesn't correspond to the encrypted file I generate with openssl

https://studylibfr.com/doc/3898805/cryptographie-avec-bouncy-castle---zenk -> Have been following the whole tutorial, not getting the expected result

X509 RSA bouncy castle sign and verify plain text in Java -> Signed file not corresponding too

https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java -> This class generate something that looks alike to what I'm trying to get, but I couldn't test it's validity as I have to encrypt it and still can't make encrytion work

https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/ReadSignedMail.java -> Same as previous class

https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java -> This method of encryption isn't returning the same result as openssl, so it isn't working

For sure, I've tried to keep working on these samples classes from bouncycastle, but without success.

Any help would be appreciated

Edit 2 The answer of the following question Sign and encrypt a file using S/MIME returns a Base64 encoded file that might correspond to what I generate with openssl. But the problem is that my entry file is about 25kb and the signed file generated is only 3kb, I don't understand why, I noticed that at this line :

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

the getEncoded() method returns me a byte array much smaller than the buffer I send to the CMSSignedData.

Does anyone knows the reason ?


Solution

  • For the signing, you were fairly close with org.bouncycastle.mail.smime.examples.CreateSignedMultipartMail except that

    • it does multipart data, which openssl smime doesn't do; start from CreateSignedMail instead

    • it does multipart signing, aka clear-signing, which openssl smime also defaults to, but -nodetach changes this to embedded aka encapsulated

    • it includes a full cert chain, but a self-generated one of length only 2 whereas nearly all 'real' certs are longer, whereas openssl by default includes only the signer cert

    • by default it uses some signedattributes different than openssl

    For the encryption (or more exactly enveloping) openssl smime -outform der in spite of the name doesn't do SMIME at all, it does CMS (originally and still also known as PKCS7). Bouncy uses the full OO goodness of Java to put CMS and SMIME, which are very similar but not the same, into different classes that are related but not the same, so you need the CMS class(es).

    Putting these together (plus a minimal test harness) I present to you:

        // 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);
    

    On, and 'does anyone know the reason'

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

    is smaller than the content? See the javadoc -- with encapsulate (the second parameter) set to false you told it to not include the content in the signature (more exactly SignedData), and it did as you demanded.