Sunday, August 12, 2012

Digital Signature by Example

We use digital signature for two main purposes in message communication:

1. To guarantee the integrity of the content (i.e to ensure that message has not been changed during transmission from sender to receiver)
2. To authenticate the message origination (i.e to verify that the message was sent from the party that we think it is sent from)

We use cryptographic mechanisms to generate and verify digital signature.
Before going into the code level, let me briefly mention the steps involved in creating and verifying the digital signature.

Pre-requisites:

Have your keystore created with private key and public key. You can refer to the posts at here and here for the steps in creating a keystore with private/public key pair using java keytool.

Creating digital signature at the sender:

1. Computing the hash value of the content to be signed...

Here we use hash functions in cryptography to create a fixed length hash value such that it is impossible to calculate the original content or the length of the content from the hash value. This is called message digest or one-way encryption. Hence hash functions provide a digital fingerprint of the content to be signed.

2. Encrypting the hash value with his/her private key...

Here we use asymmetric key cryptography to encrypt the hash value computed from the message content.

Verifying digital signature at the receiver:

1. Decrypting the signature and obtaining the message digest..

Receiver once again applies asymmetric key cryptography to decrypt the message signature using sender's public key. 

How does the receiver obtain sender's public key?
It can happen in different ways according to the message communication protocol that you use. Usually communicating parties can exchange keys before the message communication, in a trusted way or the sender can send the certificate containing the public key along with the signed content and the signature. It is not recommended to send the public key itself since it is susceptible to MIM attacks. You can read more about it from here.

At this step, message origin authentication happens. Since only the one who owns the private key related to the public key used to decrypt the signature, can sign the message - we can identify who has sent the message.

2. Comparing the hash values to verify message integrity...
Receiver computes the hash value on the original message and compares it with the hash value sent by the sender, which was obtained in the above step after decrypting the digital signature. If the two values are identical, receiver can verify that the message integrity is protected during the transmission.

Now let us see how we can create and verify digital signature over some text content with an existing private/public key pair in the keystore named 'mykeystore.jks', using Java Security API.

I hope the comments in the following source code will help you understand each step performed in doing this.
package org.digital.signature.sample;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;


public class Sample {
    //keystore related constants
    private static String keyStoreFile = "/home/hasini/Digital-Signature/sample/src/main/resources/mykeystore.jks";
    private static String password = "mypassword";
    private static String alias = "mycert";

    public static void main(String[] args) {

        try {
            KeyStore keystore = KeyStore.getInstance("JKS");
            char[] storePass = password.toCharArray();

            //load the key store from file system
            FileInputStream fileInputStream = new FileInputStream(keyStoreFile);
            keystore.load(fileInputStream, storePass);
            fileInputStream.close();

            /***************************signing********************************/
            //read the private key
            KeyStore.ProtectionParameter keyPass = new KeyStore.PasswordProtection(storePass);
            KeyStore.PrivateKeyEntry privKeyEntry = (KeyStore.PrivateKeyEntry) keystore.getEntry(alias, keyPass);
            PrivateKey privateKey = privKeyEntry.getPrivateKey();

            //initialize the signature with signature algorithm and private key
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);

            //Read the string into a buffer
            String data = "{\n" +
                          "  \"schemas\":[\"urn:scim:schemas:core:1.0\"],\n" +
                          "  \"userName\":\"bjensen\",\n" +
                          "  \"externalId\":\"bjensen\",\n" +
                          "  \"name\":{\n" +
                          "    \"formatted\":\"Ms. Barbara J Jensen III\",\n" +
                          "    \"familyName\":\"Jensen\",\n" +
                          "    \"givenName\":\"Barbara\"\n" +
                          "  }\n" +
                          "}";

            byte[] dataInBytes = data.getBytes();

            //update signature with data to be signed
            signature.update(dataInBytes);

            //sign the data
            byte[] signedInfo = signature.sign();

            System.out.println(signedInfo.toString());

            /**************************verify the signature****************************/
            Certificate publicCert = keystore.getCertificate(alias);

            //create signature instance with signature algorithm and public cert, to verify the signature.
            Signature verifySig = Signature.getInstance("SHA256withRSA");
            verifySig.initVerify(publicCert);

            //update signature with signature data.
            verifySig.update(dataInBytes);

            //verify signature
            boolean isVerified = verifySig.verify(signedInfo);

            if (isVerified) {
                System.out.println("Signature verified successfully");
            }
            
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (UnrecoverableEntryException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();  
        }
    }
}

References:

[2] http://www.garykessler.net/library/crypto.html#intro