Chapter 6: Digital Signatures
Contents
6.1 What is a Digital Signature?
The digital signature of a document is essentially the document's hash value encrypted with the signer's private key.
As mentioned in Chapter 4, public-key cryptography is based on two keys, public and private. The two are mathematically related but one cannot be deduced from the other. A message encrypted with one key can only be decrypted with the other.
To generate the digital signature of a document, the signer computes the one-way hash function of this document and then encrypts this value with her private key. To validate the signature, the document's recipient decrypts the signature using the signer's public key, computes the hash value of the document and compares the two values: if they match the signature is validated. Therefore, the signature serves two purposes: it provides proof of the signer's identity and verifies that the document has not been modified since the signing.
To simplify signature validation, the signature is sometimes bundled with the signer's certificate containing her public key.
6.2 OpenContext Method Revisited
Throughout the previous chapters, we have been calling the ubiquitous OpenContext method with an empty string as the first argument. As mentioned briefly in Section 2.1.2 of Chapter 2, the first argument to OpenContext is the name of a public/private key container within the current Cryptographic Service Provider. The container contains two key pairs, one for signatures and the other for key exchange (for consistency purposes we will always use the former, although there is no practical difference between the two.) If the name specified in OpenContext does not yet exist, a new container is created and populated with two brand-new key pairs.
By default, the lengths of the public keys in the container are 1024 bits. As of Version 2.5, OpenContext allows you to specify a different key length by appending it to the container name via the delimiter string "##", as follows:
true, Missing.Value );
To connect to one of the two key pairs of the current context, the CryptoContext method GetUserKey should be used. This method's only argument specifies whether the key pair is the signature pair (if set to False) or key-exchange pair (if set to True). For example:
Set Context = CM.OpenContext( "mycontainer", True )
Set Key = Context.GetUserKey( False )
ICryptoContext objContext = objCM.OpenContext( "mycontainer", true, Missing.Value );
ICryptoKey objKey = objContext.GetUserKey( false );
The public key of a key pair can be exported to a file via the CryptoKey method ExportToFile. It can later be used for signature validation.
<%
...
Set Key = Context.GetUserKey( False )
Key.ExportToFile Nothing, "c:\path\key.pub", cbtPublicKeyBlob
%>
ICryptoKey objKey = objContext.GetUserKey( false );
objKey.ExportToFile( null, @"c:\path\key.pub", CryptoBlobTypes.cbtPublicKeyBlob );
The first argument to ExportToFile should normally be another key to encrypt the key being exported. However, since we are exporting a public key, no encryption is necessary. Therefore, Nothing (null) is specified instead of a CryptoKey object.
A public key file can be imported back to the CryptoKey object via the CryptoContext method ImportKeyFromFile, as follows:
Set Key = Context.ImportKeyFromFile( Nothing, "c:\path\key.pub", 0 )
ICryptoKey objKey = objContext.ImportKeyFromFile( null, @"c:\path\key.pub", 0 )
6.3 Creating and Verifying Digital Signatures
6.3.1 Signature Creation
As mentioned earlier, a digital signature is the result of encrypting a hash value with the signer's private key. Hash values are calculated using the CryptoHash object described in Chapter 3.
The CryptoHash object provides the Sign method that signs the underlying hash value with one of the two private keys associated with the context from which this CryptoHash object was created. Just like the GetUserKey method described earlier, the Sign method accepts a Boolean argument which specifies which of the two keys to use for the signing: the signature key if set to False or key-exchange key if set to True. The Sign method returns the signature as a CryptoBlob object.
For example, the following code snippet signs a text string with the signature key of the "mycontainer" context and outputs the signature value in Base64 encoding:
Set Context = CM.OpenContext("mycontainer", true )
Set Hash = Context.CreateHash
Hash.AddText "The text to be signed"
Set Blob = Hash.Sign( false )
Response.Write Blob.Base64
ICryptoContext objContext = objCM.OpenContext( "mycontainer",true,Missing.Value );
ICryptoHash objHash = objContext.CreateHash(Missing.Value);
objHash.AddText( "The text to be signed" );
ICryptoBlob objBlob = objHash.Sign( false );
Response.Write( objBlob.Base64 );
If we were to create the signature of a file, the call to CryptoHash.AddText would be replaced with a call to CryptoHash.AddFile:
Hash.AddFile "c:\path\file.ext"
...
objHash.AddFile( @"c:\path\file.ext" );
...
The output of the script above would look similar to the following:
ilYGevZVNZiUnQZ2PgLBnrQNBF1uZew2kXUopenTYdDZbe+oGslIFHBa8EO7qJezJeGLgzzv6hrT
6KVUtP3H6Gnxna3P8Qs=
The compact format of the signatures produced by the CryptoHash.Sign method is referred to as PKCS#1. It only contains the encrypted hash value and nothing else. There is another format called PKCS#7 which contains the signature as well as the signer certificate. AspEncrypt supports it also. PKCS#7 signatures and envelopes will be covered in Chapter 9.
6.3.2 Signature Verification
The signature verification process goes as follows:
1. Compute your own hash value of the file or text string.
2. Decrypt the signature using the signer's public key.
3. Compare the values obtained in Steps 1 and 2. If they match the signature is verified.
With AspEncrypt, steps 2 and 3 are performed with the CryptoHash method VerifySignature which accepts two arguments: a CryptoBlob object containing the signature being verified and a CryptoKey object containing the public key corresponding to the private key used to create the signature. This method returns True if the signature is verified, and False otherwise. To verify the Base64 value created by the code snippet above, you may use the following code:
Set Context = CM.OpenContext("", true )
Set Hash = Context.CreateHash
Hash.AddText "The text to be signed"
Set Blob = CM.CreateBlob
Blob.Base64 = "<insert signature value here>"
Set Key = Context.ImportKeyFromFile( Nothing, "c:\path\key.pub", 0 )
Response.Write Hash.VerifySignature( Blob, Key )
ICryptoContext objContext = objCM.OpenContext("", true, Missing.Value);
ICryptoHash objHash = objContext.CreateHash(Missing.Value);
objHash.AddText( "The text to be signed" );
ICryptoBlob objBlob = objCM.CreateBlob();
objBlob.Base64 = "<insert signature value here>";
ICryptoKey objKey = objContext.ImportKeyFromFile( null, @"c:\path\key.pub", 0 );
Response.Write( objHash.VerifySignature( objBlob, objKey ) );
The following code sample combines the code snippets above. It performs both the signing and signature verification of a text string. To avoid creating temporary files, it uses the methods CryptoKey.ExportToBlob and CryptoContext.ImportKeyFromBlob instead of ExportToFile and ImportKeyFromFile.
Click the links below to run this code sample:
6.4 Signing with a Certificate's Private Key Context
All the code samples above use private keys from arbitrary key containers opened via the OpenContext method. To verify the signature, the public key has to be exported into a file and transferred to a location where the verification takes place.
A signature can also be generated based on the private key associated with a specific certificate as opposed to an abstract key container. The certificate itself (its public-key portion) can be used for signature verification.
Once the certificate is obtained and moved to the HKEY_LOCAL_MACHINE section of the registry (see Section 4.6), its private key context can be obtained via the CryptoCert property PrivateKeyContext. This property returns an instance of the CryptoContext object that can be used to create a CryptoHash object to perform the signing.
The following code sample obtains a certificate from the local-machine MY store and signs a text string with its private key:
CM.LogonUser "domain", "account", "password"
Set Store = CM.OpenStore( "MY", True )
Set Cert = Store.Certificates("74 87 a7 76 6b 9f 32 7d 90 44 31 62 09 53 f5 05")
Set Context = Cert.PrivateKeyContext
Set Hash = Context.CreateHash
Hash.AddText "The text to be signed"
Set Blob = Hash.Sign( True )
Response.Write Blob.Base64
objCM.LogonUser( "domain", "account", "password", Missing.Value );
ICryptoStore objStore = objCM.OpenStore( "MY", true );
ICryptoCert objCert =
objStore.Certificates["74 87 a7 76 6b 9f 32 7d 90 44 31 62 09 53 f5 05"];
ICryptoContext objContext = objCert.PrivateKeyContext;
ICryptoHash objHash = objContext.CreateHash(Missing.Value);
objHash.AddText( "The text to be signed" );
ICryptoBlob objBlob = objHash.Sign( true );
Response.Write( objBlob.Base64 );
Note that we always pass True to the Sign method when the private key context is associated with a certificate.
The following code sample verifies the signature using the public key imported from the signer certificate's .cer file via the CryptoContext method ImportKeyFromCert:
Set Blob = CM.CreateBlob
Blob.Base64 = "<signature value>"
Set Cert = CM.ImportCertFromFile("c:\path\mycert.cer")
' extract public key from this certificate
Set Context = CM.OpenContext("", True)
Set Key = Context.ImportKeyFromCert( Cert )
' re-create hash
Set Hash = Context.CreateHash
Hash.AddText "The text to be signed"
If Hash.VerifySignature(Blob, Key) Then
Response.Write "Signature validated."
Else
Response.Write "Signature not validated."
End If
ICryptoBlob objBlob = objCM.CreateBlob();
objBlob.Base64 = "<signature value>";
ICryptoCert objCert = objCM.ImportCertFromFile( @"c:\path\mycert.cer" );
// extract public key from this certificate
ICryptoContext objContext = objCM.OpenContext("", true, Missing.Value);
ICryptoKey objKey = objContext.ImportKeyFromCert( objCert );
// re-create hash
ICryptoHash objHash = objContext.CreateHash( Missing.Value );
objHash.AddText( "The text to be signed" );
if( objHash.VerifySignature( objBlob, objKey) )
{
Response.Write( "Signature validated." );
}
else
{
Response.Write( "Signature not validated." );
}
The code samples above compute and verify the signature of data. As of Version 2.9, the Value property of the CryptoHash object is made read/write (as opposed to read-only.) This way, a CryptoHash object can directly be populated with a hash value so that the signature can be computed based on the hash of data rather than data itself. This is useful when the data being signed resides on the server while the private key used for signing resides on the user workstation. Only the hash of the data needs to be transferred to the workstation for signing, not the entire data. The code sample below demonstrates how a hash object can be assigned a value:
Set Hash = Context.CreateHash
Set HashBlob = CM.CreateBlob
HashBlob.Hex = "A9993E364706816ABA3E25717850C26C9CD0D89D"
Hash.Value = HashBlob
Set Blob = Hash.Sign( True )
...
ICryptoHash objHash = objContext.CreateHash(Missing.Value);
ICryptoBlob objHashBlob = objCM.CreateBlob();
objHashBlob.Hex = "A9993E364706816ABA3E25717850C26C9CD0D89D";
objHash.Value = HashBlob;
ICryptoBlob objBlob = objHash.Sign( true );
...