Public-Key Cryptography Standards: PKCS#7 Digital Signature
Introduction
Public
key cryptography is based on asymmetric cryptographic algorithms that use two
related keys, a public key and a private key; the two keys have
property that, given the public key, it is computationally infeasible to
derive the private key. Users publish public key in a public directory and keep
their private key to them.
An encryption algorithm could be used to
encrypt a data using the private key so that only the recipient who has the
corresponding public key could decrypt the data. A signature algorithm together
with a message digest algorithm could be used to transform a message of any
length using the private key to a signature in such a way that, without
the knowledge of the private key, it is computationally infeasible to find two
messages with the same signature, to find a message for a pre-determined
signature, or to find a signature for a given message. Anyone who has the
corresponding public key could verify the validity of the signature. Typical
public key digital signature algorithms are RSA, DSA, and ECDSA.
What is PKCS#7
Public-Key Cryptography
Standards (PKCS) are RSA Data Security, Inc.'s series of de-facto standard
formats for public-key cryptography. Among all the PKCS standards, PKCS#7 is
probably the most widely used one. It describes a general syntax for data that
may have cryptography applied to it, such as digital signatures and digital
envelopes.
The
S/MIME secure mail standard uses PKCS#7 for its digitally signed and encrypted
messages. Certificate requests and certificate store (.spc) files also normally
use the PKCS#7 format. Every PKCS#7 blob usually encapsulates some content
(such as an encrypted message or signed hash value) and one or more
certificates used to encrypt or sign this content.
PKCS #7 Cryptographic Message Syntax (CMS)
PKCS
#7 has been superseded by cryptographic message syntax (CMS), which is the
basis for the S/MIME specification. CMS defines the syntax that is used to
digitally sign, digest, authenticate, or encrypt arbitrary message content.
In
particular, CMS describes encapsulation syntax for data protection. The syntax
allows multiple encapsulations; one encapsulation envelope can be nested inside
another. Likewise, one party can digitally sign some previously encapsulated
data. In the CMS syntax, arbitrary attributes, such as signing time, can be
signed along with the message content, and other attributes, such as
countersignatures, can be associated with a signature.
PKCS#7
data content called the content type could be of two classes: Base and
Enhanced.
v Base
Content Type:
§ Contains
data without cryptographic enhancements.
v Enhanced
content type:
§ Contains
data (possibly encrypted), and other cryptographic enhancements (such as hashes
or signatures).
§ Employs
encapsulation, giving rise to the terms outer
content (the one containing the enhancements) and inner content (the one being enhanced).
Enhanced
class might contain the data content type (Base class) that has a signature
included with it. In this case, the data content type is the inner content and
the combination of the data content type and the signature forms the outer
content.
[Source: RFC 2315 - PKCS #7:
Cryptographic Message Syntax and
http://msdn.microsoft.com/en-us/library/windows/desktop/aa387331(v=vs.85).aspx]
The
content types defined in the PKCS #7 standards are as follows.
[Source: RFC 2315 - PKCS #7:
Cryptographic Message Syntax and
http://msdn.microsoft.com/en-us/library/windows/desktop/aa387331(v=vs.85).aspx]
Content
type
|
Description
|
Data
|
An
octet (BYTE) string.
|
Signed
Data
|
Content
of any type and encrypted message hashes (digests) of the content for zero or
more signers.
|
Enveloped
Data
|
Encrypted
content of any type and encrypted content-encryption keys for one or more
recipients. The combination of encrypted content and encrypted
content-encryption key for a recipient is a digital envelope for
that recipient.
|
Signed-and-Enveloped
Data
|
Encrypted
content of any type, encrypted content-encryption keys for one or more
recipients, and doubly encrypted message digests for one or more signers. The
double encryption consists of an encryption with a signer's private key
followed by an encryption with the content-encryption key.
|
Digested
Data
|
|
Encrypted
Data
|
Encrypted
content of any type. Unlike the enveloped-data content type,
the encrypted-data content type has neither recipients nor encrypted
content-encryption keys. Keys are assumed to be managed by other means.
|
Digital Signature Implementation
Digital
signature implementation (PKCS#7) implementation has three major steps
- The
creation of private and public keys
- Given
a message and a private key: the production of the signature.
- Given
a signed message and a public key: the signature verification.
Generate Private Public Key Pair
$ keytool -genkey -keyalg RSA -alias <alias> -keypass <key_password> -keystore <keystore_name.jks> - storepass <store_password> - validity <validity>
Note: Its recommended and expected that once a
keystore has been created private public key pair will be generated per client
so that every client has its own public key and server side implementation will
use corresponding private key.
Extract Public Key
We can extract public key from the key store
using the command
$ keytool -exportcert -alias
<alias> -keypass<key_password> -keystore
"keystore_name.jks" -rfc -file <certificate_name.pem>
Sign Data using Bouncy Castle
Code below is compatible with
bcprov-jdk15on-1.50.jar and bcmail-jdk15on-1.50.jar library.
package
com.test.pkcs;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import
java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import
org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import
org.bouncycastle.cms.CMSSignedData;
import
org.bouncycastle.cms.CMSSignedDataGenerator;
import
org.bouncycastle.cms.CMSTypedData;
import
org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import
org.bouncycastle.operator.ContentSigner;
import
org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import
org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import
org.bouncycastle.util.encoders.Base64;
public final class PKCS7BCSigner {
private
static final String PATH_TO_KEYSTORE = "<path_to_jks>";
private
static final String KEY_ALIAS_IN_KEYSTORE = "<alias_client_privatekey>";
private
static final String KEYSTORE_PASSWORD = "<keystore_password>";
private
static final String SIGNATUREALGO = "SHA256withRSA";
public
PKCS7BCSigner() {
}
KeyStore
loadKeyStore() throws Exception {
KeyStore
keystore = KeyStore.getInstance("JKS");
InputStream
is = new FileInputStream(PATH_TO_KEYSTORE);
keystore.load(is,
KEYSTORE_PASSWORD.toCharArray());
return
keystore;
}
CMSSignedDataGenerator
setUpProvider(final KeyStore keystore)
throws
Exception {
Security.addProvider(new
BouncyCastleProvider());
Certificate[]
certchain = (Certificate[]) keystore
.getCertificateChain(KEY_ALIAS_IN_KEYSTORE);
final
List<Certificate> certlist = new ArrayList<Certificate>();
for
(int i = 0, length = certchain == null ? 0 : certchain.length; i < length;
i++) {
certlist.add(certchain[i]);
}
Store
certstore = new JcaCertStore(certlist);
Certificate
cert = keystore.getCertificate(KEY_ALIAS_IN_KEYSTORE);
ContentSigner
signer = new JcaContentSignerBuilder(SIGNATUREALGO)
.setProvider("BC").build(
(PrivateKey)
(keystore.getKey(KEY_ALIAS_IN_KEYSTORE,
KEYSTORE_PASSWORD.toCharArray())));
CMSSignedDataGenerator
generator = new CMSSignedDataGenerator();
generator.addSignerInfoGenerator(new
JcaSignerInfoGeneratorBuilder(
new
JcaDigestCalculatorProviderBuilder().setProvider("BC")
.build()).build(signer,
(X509Certificate) cert));
generator.addCertificates(certstore);
return
generator;
}
byte[]
signPkcs7(final byte[] content,
final
CMSSignedDataGenerator generator) throws Exception {
CMSTypedData
cmsdata = new CMSProcessableByteArray(content);
CMSSignedData
signeddata = generator.generate(cmsdata, true);
return
signeddata.getEncoded();
}
public
static void main(String[] args) throws Exception {
PKCS7BCSigner
signer = new PKCS7BCSigner();
KeyStore
keyStore = signer.loadKeyStore();
CMSSignedDataGenerator
signatureGenerator = signer
.setUpProvider(keyStore);
String
content = "<some_bytes_to_be _signed>";
byte[]
signedBytes = signer.signPkcs7(content.getBytes("UTF-8"),
signatureGenerator);
System.out.println("Signed
Encoded Bytes: "
+
new String(Base64.encode(signedBytes)));
}
}
Signature Verification using Bouncy Castle
package
com.test.pkcs;
import java.security.Security;
import
java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import
org.bouncycastle.cert.X509CertificateHolder;
import
org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import
org.bouncycastle.cms.CMSProcessableByteArray;
import
org.bouncycastle.cms.CMSSignedData;
import
org.bouncycastle.cms.SignerInformation;
import
org.bouncycastle.cms.SignerInformationStore;
import
org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
import
org.bouncycastle.util.encoders.Base64;
public class PKCS7SignatureVerifier {
public
static void main(String[] args) throws Exception {
String
envelopedData = “<signed_data>”;
Security.addProvider(new
BouncyCastleProvider());
CMSSignedData
cms = new CMSSignedData(Base64.decode(envelopedData
.getBytes()));
Store
store = cms.getCertificates();
SignerInformationStore
signers = cms.getSignerInfos();
Collection
c = signers.getSigners();
Iterator
it = c.iterator();
while
(it.hasNext()) {
SignerInformation
signer = (SignerInformation) it.next();
Collection
certCollection = store.getMatches(signer.getSID());
Iterator
certIt = certCollection.iterator();
X509CertificateHolder
certHolder = (X509CertificateHolder) certIt
.next();
X509Certificate
cert = new JcaX509CertificateConverter()
.setProvider("BC").getCertificate(certHolder);
if
(signer.verify(new JcaSimpleSignerInfoVerifierBuilder()
.setProvider("BC").build(cert)))
{
System.out.println("verified");
CMSProcessableByteArray
cpb = (CMSProcessableByteArray) cms
.getSignedContent();
byte[]
rawcontent = (byte[]) cpb.getContent();
System.out.println(new
String(rawcontent));
}
}
}
}