Java Digital Signature
In this tutorial, we will learn about Digital Signatures and how we can work with them in java.
What is a Digital Signature?
A digital signature is an encrypted hash value (digest, checksum) generated from a message.
Use of a Digital Signature
A digital signature ensures the following things:
Integrity of a message – that it has not been changed during transit (over the network).
Authenticity – that the author of the message is authentic and not fake (different from what they claim to be)
Non-repudiation – the author cannot deny that they are the source of the message
Sending a message with its digital signature
- Generate a hash from the message that we want to send
- Encrypt it using a private key using a chosen algorithm
- Send the message along with the encrypted hash, public key, and algorithm. This is called sending a message with digital signature.
Receiving a message with digital signature and verifying it
- Decrypt the encrypted hash, that has been received along with the message, using the public key.
- Generate a new hash from the received message
- Compare if the two hash are same or not. If same, digital signatures are verified.
Digital certificate and public key identity
A digital certificate is a document that gives an identity to a public key. Certificates are signed by a third party entity called CA (Certificate Authority). Now the question is – How do we ensure that the public key is coming from the right source? This is done by the use of digital certificates. A digital certificate contains a public key and is itself signed by another entity. The signature of that entity can be verified by another entity and so on. Thus, we can have a chain of certificates. The top most entity in this chain is self-signed. Self-signed means its public key is signed by its own private key.
The most used certificate format is X.509. It is shipped as either binary format (DER) or text format (PEM). JCA provides an implementation for this by the X509Certificate class.
Steps required to create a digital signature
- Generate a public/private key pair
- Load the private key for signing the message
- publish the public key
- Generate a public/private key pair :
This can be done using java keytool. Run the below command on command prompt
Keytool -genkeypair -alias senderPubPrivKeyPair -keyalg RSA -keysize 2048 -dname “CN=sks” -validity 365 -storetype PKCS12 -keystore sender_keystore.pkcs12 -storepass changeit
The above command creates a private key for us. This key is stored in the keystore file named sender_keysote.pkcs12. We have used PKCS12 keystore format. It is standard and recommended over Java proprietary JKS format. Please make note of alias and password used in the above command. They will be used later in the program while loading a keystore file.
2. Create a java project in Eclipse/ STS and write the below class DigitalSignTest to load the private key for signing the message
package com.sks;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class DigitalSignTest {
public static void main(String[] args) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
PrivateKey privateKey = loadPrivateKey();
System.out.println("private key is : " + privateKey);
}
public static PrivateKey loadPrivateKey() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("C:\\swdtools\\pubprikey\\sender_keystore.pkcs12"), "changeit".toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey("senderPubPrivKeyPair", "changeit".toCharArray());
System.out.println("Private key is : " + privateKey);
return privateKey;
}
3. Publish the public key – In this step, we have to decide whether we want to publish a self-signed certificate or a CA (Certificate Authority) signed certificate. Let’s first see the example in which we will export a self-signed certificate.
In self-signed approach, we only need to export the certificate from the keystore that we had generated in the step 1.
Keytool -exportcert -alias senderPubPrivKeyPair -storetype PKCS12 -keystore sender_keystore.pkcs12 -file sender_cert.cer -rfc -storepass changeit
In CA-signed approach, we need to create a certificate signing request. We use certreq command for this purpose:
keytool -certreq -alias senderPubPrivKeyPair -storetype PKCS12 -keystore sender_keystore.pkcs12 -file -rfc -storepass changeit > sender_cert.csr
The Certificate Signing Request or CSR file, sender_cert.csr, is then sent to a Certificate Authority for signing. We’ll receive a signed public key wrapped in an X.509 certificate when signing is done. It is either in binary (DER) or text (PEM) format. In this example, we’ve used the rfc option for a PEM format. After the signing is done, public key received from CA can be made available to the clients.
Generate a message hash and encrypt the same :
A file called ‘message.txt’ at following location – C:\Users\saras\Desktop\digitalsig\message.txt. You can create a file at your own location.
The encrypted digital signature we are going to write at following location – C:\Users\saras\Desktop\digitalsig\digitalSig.txt
package com.sks;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class DigitalSignTest {
public static void main(String[] args) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
PrivateKey privateKey = loadPrivateKey();
System.out.println("private key is : " + privateKey);
PublicKey publicKey = loadPublicKey();
System.out.println("Public key value is : " + publicKey);
}
public static PrivateKey loadPrivateKey() throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException, UnrecoverableKeyException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("C:\\swdtools\\pubprikey\\sender_keystore.pkcs12"), "changeit".toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey("senderPubPrivKeypair", "changeit".toCharArray());
System.out.println("Private key is : " + privateKey);
return privateKey;
}
public static PublicKey loadPublicKey() throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("C:\\swdtools\\pubprikey\\receiver_keystore.pkcs12"), "changeit".toCharArray());
Certificate cert = keyStore.getCertificate("receiverPubPrivKeyPair");
PublicKey publicKey = cert.getPublicKey();
return publicKey;
}
public static byte[] generateHash() throws IOException, NoSuchAlgorithmException {
byte[] msgBytes = Files.readAllBytes(Paths.get("C:\\Users\\saras\\Desktop\\digitalsig\\message.txt"));
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messagehash = md.digest(msgBytes);
return messagehash;
}
public static void generateDigitalSignature(PrivateKey privateKey, byte[] messageHash)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] digitalSignature = cipher.doFinal(messageHash);
Files.write(Paths.get("C:\\Users\\saras\\Desktop\\digitalsig\\digitalSig.txt"), digitalSignature);
}
}
At receiver end –
Load the public key for verification
As the receiver, you have to import the public key into your keystore using the below command:
keytool -importcert -alias receiverPubPrivKeyPair -storetype PKCS12 -keystore receiver_keystore.pkcs12 -file sender_cert.cer -rfc -storepass changeit
Now, verify the digital signature. Below is the entire code.
package com.sks;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class DigitalSignTest {
public static void main(String[] args) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
PrivateKey privateKey = loadPrivateKey();
System.out.println("private key is : " + privateKey);
PublicKey publicKey = loadPublicKey();
System.out.println("Public key value is : " + publicKey);
}
public static PrivateKey loadPrivateKey() throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException, UnrecoverableKeyException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("C:\\swdtools\\pubprikey\\sender_keystore.pkcs12"), "changeit".toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey("senderPubPrivKeypair", "changeit".toCharArray());
System.out.println("Private key is : " + privateKey);
return privateKey;
}
public static PublicKey loadPublicKey() throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
FileNotFoundException, IOException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("C:\\swdtools\\pubprikey\\receiver_keystore.pkcs12"), "changeit".toCharArray());
Certificate cert = keyStore.getCertificate("receiverPubPrivKeyPair");
PublicKey publicKey = cert.getPublicKey();
return publicKey;
}
public static byte[] generateHash() throws IOException, NoSuchAlgorithmException {
byte[] msgBytes = Files.readAllBytes(Paths.get("C:\\Users\\saras\\Desktop\\digitalsig\\message.txt"));
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messagehash = md.digest(msgBytes);
return messagehash;
}
public static void generateDigitalSignature(PrivateKey privateKey, byte[] messageHash)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] digitalSignature = cipher.doFinal(messageHash);
Files.write(Paths.get("C:\\Users\\saras\\Desktop\\digitalsig\\digitalSig.txt"), digitalSignature);
}
}
public static void verifySignature(PublicKey publicKey) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] encryptedMessageHash = Files.readAllBytes(Paths.get("C:\\Users\\saras\\Desktop\\digitalsig\\digitalSig.txt"));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decryptedMessageHash = cipher.doFinal(encryptedMessageHash);
byte[] messageBytes = Files.readAllBytes(Paths.get("C:\\Users\\saras\\Desktop\\digitalsig\\message.txt"));
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] newMessageHash = md.digest(messageBytes);
boolean isCorrect = Arrays.equals(decryptedMessageHash, newMessageHash);
System.out.println("Is Digital certificate verification successful = " + isCorrect);
}
}
Note :- You can play around with this example by changing the content of message.txt file and saving it with a different name. Then you generate hash from this file and compare with the decrypted hash. Now, the digital signature verification should fail.