2

I need to be able to read in Java a S/MIME file generated from OpenSSL. The openssl command looks like

openssl smime -encrypt -in test_message.txt -out test_out.pem -outform pem certificate.pem

This creates a file that looks like

-----BEGIN PKCS7-----
MIIBkwYJKoZIhvcNAQcDoIIBhDCCAYACAQAxggE+MIIBOgIBADAiMBoxGDAWBgNV
BAMTD0ZvcmRTRE5TZWN1cml0eQIEUw5nyTANBgkqhkiG9w0BAQEFAASCAQBK9wAV
wAXRM7oMWJz113VX7Tb/MslQatHZH2oaX5XJnIJrvnTv9T3irQR9H+pegh1q6OZv
v4Mz/QBFO2iq4tv6xGHE8hl0ZdmNCUdTN41qutZP2+N1YrKi9QLmnuAi3BkEzzeW
YTGvE8xGsjNlTLOjz7P5lZdCWpGJmdPeUDP0IYsOsuMspPcujyOdA5y++y6x90WF
J3ovzPhCRU7303EhdQ1hHse8KTen56XZflL3zhnT2KGtN/Pq3aZ1MVhmLZ+EZuUF
ygxlwCXi3FUx7P35XZAGpTUPFM2sz5p+oSrcxA+fsUgiMb96tfaXZLYE753mA2tZ
WfCRd86nzJsVE/YhMDkGCSqGSIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQIqd23
FXgqdaSAEHLeYH0LG9G+UfCBxQOalIE=
-----END PKCS7-----

I am currently using BouncyCastle to try to read in test_out.pem,

....
MimeMessage mimeMessage = new MimeMessage(session, new FileInputStream("test_out.pem"));
SMIMEEnveloped smimeEnveloped = new SMIMEEnveloped(mimeMessage);
...

but I can't figure out how to make it accept a message without the MIME headers, as I get the following error:

java.lang.NullPointerException: null
    at org.bouncycastle.cms.CMSEnvelopedData.<init>(Unknown Source) ~[bcpkix-jdk15on-1.50.jar:1.50.0]
    at org.bouncycastle.cms.CMSEnvelopedData.<init>(Unknown Source) ~[bcpkix-jdk15on-1.50.jar:1.50.0]
    at org.bouncycastle.mail.smime.SMIMEEnveloped.<init>(Unknown Source) ~[bcmail-jdk15on-1.50.jar:1.50.0]

What would be the best way to read a PEM (or DER) formatted file like this in and be able to decrypt it with a java.security.PrivateKey?

1 Answers1

1

Here's how you can do the decryption using BouncyCastle 1.57 (inspired by this article):

import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KeyTransRecipientInformation;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
import org.bouncycastle.util.encoders.Base64;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;

public class PKCS7Decryptor {

    private PrivateKey privateKey;

    public PKCS7Decryptor(String privateKeyStr) {
        try {
            byte[] privateKeyData = extractRawData(privateKeyStr, "PRIVATE KEY");
            PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(privateKeyData);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            privateKey = kf.generatePrivate(kspec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException("Unable to parse private key");
        }
    }

    public String decrypt(String encryptedText) throws CMSException {
        byte[] data = extractRawData(encryptedText, "PKCS7");

        CMSEnvelopedData envelopedData = new CMSEnvelopedData(data);

        Collection<RecipientInformation> recipients = envelopedData.getRecipientInfos().getRecipients();
        KeyTransRecipientInformation recipientInfo = (KeyTransRecipientInformation) recipients.iterator().next();
        JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient(privateKey);

        return new String(recipientInfo.getContent(recipient));
    }

    private byte[] extractRawData(String text, String dataType) {
        return Base64.decode(text
                .replace(String.format("-----BEGIN %s-----", dataType), "")
                .replace(String.format("-----END %s-----", dataType), ""));
    }
}

Some explanation:

  • In the class constructor the private key is converted to a proper format
  • Headers and footers (like "-----BEGIN PKCS7-----") are removed and the content is base64-decoded
wheleph
  • 7,974
  • 7
  • 40
  • 57