One of the requirements for a recent project was to digitally sign messages using the PKCS7 standard. PKCS7 is an RSA standard, and is well-supported by various Microsoft technologies. Finding clear, concise examples about how to do this in Java was a bit tricky for me, so I decided to write one.
First things to know. Out of the box, Java 5 does not support PKCS7 signatures. So I went in search of an open source implementation. I found some good examples suggesting Bouncy Castle as a JCE Provider that has PKCS7 support. Another thing to know is that that PKCS7 has evolved into something called CMS (which is part of Secure/MIME). So documentation can be found under a few different names.
First, we need some keys. Keys are good. Keys are our friends. Without keys, you can’t sign things.
Now, we can create our own keys using the keytool utility. For example over here, I discuss how to create a .keystore file under my Documents and Settings using Java’s keytool command line utility. That works and everything. Then I can load that like this:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyStore; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.SystemUtils; public class MyKeystoreProvider { public KeyStore getKeystore(char[] password) throws GeneralSecurityException, IOException { KeyStore keystore = KeyStore.getInstance("jks"); InputStream input = new FileInputStream(SystemUtils.USER_HOME + File.separator + ".keystore"); try { keystore.load(input, password); } catch (IOException e) { } finally { IOUtils.closeQuietly(input); } return keystore; } }
This class loads up my keystore in a way that allows me to use it for signing later on.
Next, I want to have some code that signs some content. Let’s say that my content is a bunch of ASCII text that I can represent as an array of bytes. I use some Bouncy Castle classes to generate “CMS Signed Data”:
public byte[] sign(byte[] data) throws GeneralSecurityException, CMSException, IOException { Security.addProvider(new BouncyCastleProvider()); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); generator.addSigner(getPrivateKey(), (X509Certificate) getCertificate(), CMSSignedDataGenerator.DIGEST_SHA1); generator.addCertificatesAndCRLs(getCertStore()); CMSProcessable content = new CMSProcessableByteArray(data); CMSSignedData signedData = generator.generate(content, true, "BC"); return signedData.getEncoded(); }
Note that the “BC” referred to above is not me, BC Holmes, but rather the standard security provider name for Bouncy Castle.
private CertStore getCertStore() throws GeneralSecurityException { ArrayList<Certificate> list = new ArrayList<Certificate>(); Certificate[] certificates = getKeystore().getCertificateChain(this.alias); for (int i = 0, length = certificates == null ? 0 : certificates.length; i < length; i++) { list.add(certificates[i]); } return CertStore.getInstance("Collection", new CollectionCertStoreParameters(list), "BC"); } private PrivateKey getPrivateKey() throws GeneralSecurityException { if (this.privateKey == null) { this.privateKey = initalizePrivateKey(); } return this.privateKey; } private PrivateKey initalizePrivateKey() throws GeneralSecurityException { KeyStore keystore = new MyKeystoreProvider().getKeystore(); return (PrivateKey) keystore.getKey(this.alias, getPasswordAsCharArray()); }
And I get an array of bytes back. This array of bytes contains a bunch of stuff. It contains:
- My original content (there’s a boolean flag on the
generator.generate()
step that tells Bouncy Castle to include the original content with the signature. - My signature of the content
- The certificate I used to sign the content
- Some additional information that tells the recipient about the structure of the overall signature.
I can then transmit this byte array to whatever system needs my signed message. On the other end, I might want to look at the message and verify the signature. That could be as easy as this:
CMSSignedData s = new CMSSignedData(signedBytes); CertStore certs = s.getCertificatesAndCRLs("Collection", "BC"); SignerInformationStore signers = s.getSignerInfos(); boolean verified = false; for (Iterator i = signers.getSigners().iterator(); i.hasNext(); ) { SignerInformation signer = (SignerInformation) i.next(); Collection<? extends Certificate> certCollection = certs.getCertificates(signer.getSID()); if (!certCollection.isEmpty()) { X509Certificate cert = (X509Certificate) certCollection.iterator().next(); if (signer.verify(cert.getPublicKey(), "BC")) { verified = true; } } } CMSProcessable signedContent = s.getSignedContent() ; byte[] originalContent = (byte[]) signedContent.getContent();
And there it is.
About The Author: Andrea Ramirez
More posts by Andrea Ramirez