Chapter 9: PKCS#7 Signatures and Envelopes

Contents

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

AspEncrypt provides support for the PKCS#7 format via the CryptoMessage object.

9.2 Encrypting & Decrypting Text Directly with Certificates

9.2.1 Encryption

The CryptoMessage object enables you to encrypt text information directly with a certificate's public key in one easy step. The resultant PKCS#7 encrypted message (also known as "envelope") can only be decrypted with a private key context associated with this certificate. Decryption can take place on the client's machine using the XEncrypt ActiveX control, or on the server.

PKCS#7 envelopes also allow you to encrypt data with multiple certificates at the same time. Any one of the corresponding private keys is sufficient to decrypt the message.

The following code sample demonstrates how to use the CryptoMessage object to encrypt a text string with a certificate. For this code sample to work, you must export your personal certificate to a .cer file and place it on the server.

Set CM = Server.CreateObject("Persits.CryptoManager")
Set Context = CM.OpenContext( "", True )

Set Msg = Context.CreateMessage( True )

' Obtain encryption certificate
Set Cert = CM.ImportCertFromFile("c:\path\mycert.cer")
Msg.AddRecipientCert Cert

Encrypted = Msg.EncryptText("my secret phrase")
ICryptoManager objCM = new CryptoManager();

ICryptoContext objContext = objCM.OpenContext( "", true, Missing.Value );

ICryptoMessage objMsg = objContext.CreateMessage( true );

// Obtain encryption certificate
ICryptoCert objCert = objCM.ImportCertFromFile( @"c:\path\mycert.cer" );
objMsg.AddRecipientCert( objCert );

txtResult.Text = objMsg.EncryptText("my secret phrase");

The CryptoMessage method EncryptText performs encryption on the specified text string and returns a PKCS#7 envelope which may look similar to the following:

MIICNwYJKoZIhvcNAQcDoIICKDCCAiQCAQAxggHgMIIB3AIBADCBwzCBrjELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJU
UlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMT
LVVUTi1VU0VSRmlyc3QtQ2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbAIQdIendmufMn2Q
RDFiCVP1BTANBgkqhkiG9w0BAQEFAASCAQAb+i9wpStZgR3TfWb+NHg+FduZFQVpCrFaityTREMQ
KB9A+OLBURVStO7RIPODdwvZ7uN3/69tLBrMwckvzXizwiBgLNGE9qtToulaDBIwdxqesN/IU9bU
HcukqUUFtRwS3P6ECsZ+7xuf64QwpouaxiMa4xoaOdEi/CRAxF/sfz2X6D2pCNN7qEy9EVdmjeue
9RgPvmQxQbI7CCP8aixnbdOMw+q9pMAlbqenusxJx6Hot4dTM3wlyG6mXA5mhH1a5PM66vhnuSCe
zy59XXv+KRbZ9cB6v91pKcp7Gj8UfJoYPEDDLJphMl0+UaH9U/0SLINKExt3CcB43buZCH5jMDsG
CSqGSIb3DQEHATAUBggqhkiG9w0DBwQIT1IO/rf63iWAGPAcGb+oHf3fnQD5butjI49SZZjVOcUm
wc==

Even a short string turns into a relatively large encrypted blob because a PKCS#7 envelope also contains the certificate used for encryption.

Before running this code sample, you need to export your personal certificate to a file and change the path argument to ImportCertFromFile, if necessary. Click on the links below to run this code sample.

9.2.1 Decryption

To decrypt a PKCS#7 envelope, you need to call the CryptoMessage method DecryptText and pass it, besides the blob, the name of the private key container for the encryption certificate. This name can be obtained via the expression Cert.PrivateKeyContext.ContainerName. If no container name is specified, AspEncrypt will try all certificates in the MY store until a match is found.

The following code snippet is to be executed on the user's machine using the XEncrypt ActiveX control:

<!-- Client-side VBScript -->

Sub Decrypt
Set Context = XEncrypt.OpenContext( "mycontainer", False)
Set Msg = Context.CreateMessage(True)

On Error Resume Next
document.myForm.txtDecr.Value = _
Msg.DecryptText(document.myForm.txtEncr.Value, "")

If Err <> 0 Then
   MsgBox Err.Description
End If

End Sub
<!-- Client-side Javascript -->

function Decrypt()
{
var Context = XEncrypt.OpenContext( "mycontainer", false);
var Msg = Context.CreateMessage(true);

try
{
   document.forms[0].txtDecr.value =
   Msg.DecryptText(document.forms[0].txtEncr.value, "")
}
catch( e )
{
   alert( e.description );
}
}

Click on the links below to run this code sample.

9.3 Decrypting with Email Client

Using XEncrypt as described above is not the only way to decrypt a PKCS#7 envelope on the user's machine. An alternative method is to generate an email message with the PKCS#7 blob as the message body. An S/MIME-enabled email client such as Microsoft Outlook, Outlook Express, Mozilla Thunderbird or Windows Live Mail with a digital certificate installed will be able to decrypt the envelope and display its content to the user. For this arrangement to work, certain MIME headers have to be added to the email message, and the CR/LF characters must be put in front of the text string before it can be operated on by CryptoMessage.EncryptText.

The following code sample demonstrates this technique:

Message = "This is my secret message!!!"

' Prepend message with a CR/LF characters to comply with MIME format
Message = chr(13) & chr(10) & Message

Set CM = Server.CreateObject("Persits.CryptoManager")
Set Context = CM.OpenContext("", True)
Set Msg = Context.CreateMessage( True )

' open encryption certificate
Set Cert = CM.ImportCertFromFile("c:\path\mycert.cer")
Msg.AddRecipientCert Cert

' Create PKCS#7 envelope
Encrypted = Msg.EncryptText(Message)

' Create instance of AspEmail
Set Mail = Server.CreateObject("Persits.MailSender")

' Replace with your own SMTP server's address
Mail.Host = "smtp.mycompany.com"

Mail.Subject = "Sample encrypted message"

' Replace these with your own emails
Mail.From = "me@mycompany.com"
Mail.AddAddress "someone@hiscompany.com"

' Add custom headers
Mail.AddCustomHeader "Content-Type: application/x-pkcs7-mime;name=""smime.p7m"""
Mail.AddCustomHeader "Content-Transfer-Encoding: Base64"
Mail.AddCustomHeader "Content-Disposition: attachment;filename=""smime.p7m"""

Mail.Body = Encrypted
Mail.Send
String strMessage = "This is my secret message!!!";

// Prepend message with a CR/LF characters to comply with MIME format
strMessage = "\r\n" + strMessage;

ICryptoManager objCM = new CryptoManager();
ICryptoContext objContext = objCM.OpenContext("", true, Missing.Value);
ICryptoMessage objMsg = objContext.CreateMessage( true );

// open encryption certificate
ICryptoCert objCert = objCM.ImportCertFromFile( @"c:\path\mycert.cer" );
objMsg.AddRecipientCert( objCert );

// Create PKCS#7 envelope
String strEncrypted = objMsg.EncryptText(strMessage);

// Create instance of AspEmail
IMailSender objMail = new MailSender();

// Replace with your own SMTP server's address
objMail.Host = "smtp.mycompany.com";
objMail.Subject = "Sample encrypted message";

// Replace these with your own emails
objMail.From = "me@mycompany.com";
objMail.AddAddress( "someone@hiscompany.com", Missing.Value );

// Add custom headers
objMail.AddCustomHeader(
   "Content-Type: application/x-pkcs7-mime;name=\"smime.p7m\"" );
objMail.AddCustomHeader(
   "Content-Transfer-Encoding: Base64" );
objMail.AddCustomHeader(
   "Content-Disposition: attachment;filename=\"smime.p7m\"" );

objMail.Body = strEncrypted;

objMail.Send( Missing.Value );

Before running this code sample, you need to specify your own certificate path, SMTP server and email addresses.

Click on the links below to run this code sample.

9.4 Generating and Verifying Detached PKCS#7 Signatures

9.4.1 Signature Generation

The CryptoMessage object can also be used to generate detached PKCS#7 digital signatures using a user certificate's private key. To let a user pick a certificate for signing, you can follow the same procedure as in the previous chapter. The only difference is that instead of using CryptoHash.Sign to generate a signature, you should use CryptoMessage.SignText, as follows:

' Obtain signer certificate into Cert object
...
Set Context = XEncrypt.OpenContext("", False)
Set Msg = Context.CreateMessage(True)
Msg.SetSignerCert Cert
Signature = Msg.SignText("text to sign")
// Obtain signer certificate into Cert object
...
var Context = XEncrypt.OpenContext("", false);
var Msg = Context.CreateMessage(true);
Msg.SetSignerCert( Cert );
var Signature = Msg.SignText("text to sign");

The CryptoMessage object is also capable of generating PKCS#7 signatures from arbitrary files via the method CryptoMessage.SignFile. This method is identical to SignText except that it expects a file path as an argument:

Signature = Msg.SignFile("c:\path\myfile.ext")
var Signature = Msg.SignFile("c:\\path\\myfile.ext");

9.4.2 Signature Verification

The CryptoMessage object provides the method VerifySignature which verifies a detached PKCS#7 signature against a given hash value and a public key contained in a certificate. The following code verifies a signature generated by the previous sample:

...
Set Cert = CM.ImportCertFromFile("c:\path\mycert.cer")
Set Msg = Context.CreateMessage(True)
Set Hash = Context.CreateHash
Hash.AddText "text to sign"
IsValid = Msg.VerifySignature(Signature, Hash, Cert)
...
ICryptoCert objCert = objCM.ImportCertFromFile( @"c:\path\mycert.cer" );
ICryptoMessage objMsg = objContext.CreateMessage(true);
ICryptoHash objHash = objContext.CreateHash( Missing.Value );
objHash.AddText( "text to sign" );
Bool IsValid = objMsg.VerifySignature( strSignature, objHash, objCert );

9.4.3 Client-Side Signing of Server-Side Data

As of Version 2.9, the CryptoMessage object is capable of generating PKCS#7 signatures based solely on the hash value of data being signed. The data itself does not need to be present to generate the signature. This is particularly useful when the data being signed resides on the server, while the signer certificate resides on the user workstation, and neither the data nor the user's private key can travel for security or practicality reasons.

Under this scenario, the hash value of the sever-side data is calculated and transferred to the client side where it is fed to CryptoMessage's new SignHash method. This method accepts a single argument, a CryptoBlob object populated with the hash value. Currently, only 20-byte values (SHA1) are supported as the input. The method returns another CryptoBlob object populated with the PKCS#7 signature of the specified hash value. The following code sample creates the PKCS#7 signature of a Hex-encoded hash value and formats the signature as a Hex-encoded string.

// Obtain signer certificate into Cert object
...
var Context = XEncrypt.OpenContext("", false);
var Msg = Context.CreateMessage(true);
Msg.SetSignerCert( Cert );
var HashBlob = XEncrypt.CreateBlob();
HashBlob.Hex = "A9993E364706816ABA3E25717850C26C9CD0D89D";
var PKCS7Blob = Msg.SignHash(HashBlob);
var Signature = PKCS7Blob.Hex;

While the SignHash method can be used to sign any kind of server-side data, this feature was added to AspEncrypt specifically to implement the client-side signing of server-side PDF files using the Persits AspPDF and AspPDF.NET components. The following user manual chapters contain detailed code samples and live demos demonstrating the use of the CryptoMessage.SignHash method in tandem with our PDF management components to perform the secure signing of PDF documents:

AspPDF User Manual Section 8.4 - Client-Side Signing of Server-Side PDFs

AspPDF.NET User Manual Section 8.4 - Client-Side Signing of Server-Side PDFs

9.5 Public-key Encryption without Certificates

9.5.1 Enhancements in EncryptText and EncryptBinary

As of Version 2.6, CryptoKey's EncryptText and EncryptBinary methods can be used not only for symmetric but also for public-key encryption. The encrypted data comes out in the PKCS#1 format and has Little-Endian byte order.

The following code snippet imports a certificate's public key into the CryptoKey object and encrypts some test with it.

...
Set Cert = CM.ImportCertFromFile( "c:\path\mycert.cer" )
Set Key = Context.ImportKeyFromCert( Cert )
Set Blob = Key.EncryptText( "some text" )
...
ICryptoCert objCert = objCM.ImportCertFromFile( @"c:\path\mycert.cer" );
ICryptoKey objKey = objContext.ImportKeyFromCert( objCert );
ICryptoBlob objBlob = objKey.EncryptText( "some text" );

Note that only short strings can be encrypted this way.

To decrypt, CryptoKey's DecryptText and DecryptBinary methods should be used. The decrypting CryptoKey object must be created using a context associated with the proper private key. For example, to decrypt with the private key associated with a certificate residing in the MY store, the following code can be used:

...
Set Store = CM.OpenStore( "MY", False )
Set Cert = Store.Certificates( "14 cc d0 96 81 78 c8 47 7f 15 0f eb bd 55 0a 57" )

Set Context = CM.OpenContext( Cert.PrivateKeyContext.ContainerName, False )
Set Key = Context.GetUserKey( True )

Decr = Key.DecryptText( Blob )
...
ICryptoStore objStore = objCM.OpenStore( "MY", false );
ICryptoCert objCert =
   objStore.Certificates["14 cc d0 96 81 78 c8 47 7f 15 0f eb bd 55 0a 57"];

ICryptoContext objContext = objCM.OpenContext(
objCert.PrivateKeyContext.ContainerName, false, Missing.Value );

ICryptoKey objKey = objContext.GetUserKey( true );
string strDecr = objKey.DecryptText( objBlob );

Note that many public-key encryption packages such as OpenSSL expect the PKCS#1 encrypted data to have Big-Endian byte order. Call the CryptoBlob Reverse method to reverse the byte order if necessary.

9.5.2 Importing Public Keys in PEM Format

Public keys are often distributed in the Privacy Enhanced Mail (PEM) format where the Base64-encoded public key is sandwiched between the header -----BEGIN CERTIFICATE---- and footer -----END CERTIFICATE----, as follows:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIxa9c7tYDvCL5ZCk5FGT2Zlo2
nYLEpX1KocTjfggXjrnL6+92HZMJs2jcbxp6Q4lyMPhlGP3t7/DCBpXM39v7w3U7
HRy0GAxa7tWICCnvXZRoI2BKF3bZDuWSeNyHXJtkJwnId6udn7eLj2q5TdTxc8On
JpCKjheJ/xs9fFHmSwIDAQAB
-----END PUBLIC KEY-----

As of Version 2.6, AspEncrypt is capable of importing such public keys via the CryptoContext method ImportPublicKey. This method accepts a single argument, a CryptoBlob object populated with the key data (but without the header and footer), and returns an instance of the CryptoKey object representing this public key.

The following code snippet imports the key shown above into a CryptoKey object:

...
sKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIxa9c7tYDvCL5ZCk5FGT2Zlo2"
sKey = sKey & "nYLEpX1KocTjfggXjrnL6+92HZMJs2jcbxp6Q4lyMPhlGP3t7/DCBpXM39v7w3U7"
sKey = sKey & "HRy0GAxa7tWICCnvXZRoI2BKF3bZDuWSeNyHXJtkJwnId6udn7eLj2q5TdTxc8On"
sKey = sKey & "JpCKjheJ/xs9fFHmSwIDAQAB"

Set Blob = CM.CreateBlob
Blob.Base64 = sKey

Set Key = Context.ImportPublicKey( Blob )
...
string sKey;
sKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIxa9c7tYDvCL5ZCk5FGT2Zlo2";
sKey += "nYLEpX1KocTjfggXjrnL6+92HZMJs2jcbxp6Q4lyMPhlGP3t7/DCBpXM39v7w3U7";
sKey += "HRy0GAxa7tWICCnvXZRoI2BKF3bZDuWSeNyHXJtkJwnId6udn7eLj2q5TdTxc8On";
sKey += "JpCKjheJ/xs9fFHmSwIDAQAB";

ICryptoBlob objBlob = objCM.CreateBlob();
objBlob.Base64 = sKey;

ICryptoKey objKey = objContext.ImportPublicKey( objBlob );

As of Version 2.7, AspEncrypt is capable of exporting the public key information from a certificate via the property CryptoCert.PublicKeyInfo. This property returns an instance of the CryptoBlob object populated with the public-key data in a format used by many standards such as PEM, DomainKeys Identified Mail (DKIM) and others.

Chapter 8: XEncrypt - Client-side ActiveX Control Chapter 10: Microsoft .NET and AspEncrypt Compatibility