RSA signatures in Java with Bouncy Castle

This is a complete guide, starting from RSA key pair generation into PEM files, through loading private/public keys from files into proper Bouncy Castle objects, to digital signature creation and verification – all using Bouncy Castle. No JCA.

How to generate RSA private and public keys ready for Java?

I know 3 programs that can generate RSA keys:

The first tool is intended only for Java world thus it is less universal. Moreover it’s harder with keytool to prepare a public key and private key in separate files. And I really don’t like to consider if there are any RSA key size limits in JDK. So I resigned from it.

After some research I think one should use openssl instead of ssh-keygen. The first one allows to write files with public and private keys in a format that can be loaded by a Java program. And it generates as good keys as the second one as both tools are based on Open SSL library.

Using openssl

Below bash commands lead to obtain 2 separate files with 4096-bit RSA key pair: one file with private key and one file with public key. Both files in PEM format which is a plain text. I’ve decided to use PEM format as it’s quite convenient and universal.

openssl genrsa -out priv-key.pem 4096
openssl rsa -in priv-key.pem -pubout -outform PEM -out pub-key.pem

After executing above commands we have an RSA public key in pub-key.pem file and the related RSA private key in priv-key.pem file.

How to create and check RSA signature in Java?

We will use Bouncy Castle cryptographic library. My experience is that JCA can be dissapointing (example: key strengh limits). To use Bouncy Castle we need these dependencies (Maven):

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.56</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.56</version>
        </dependency>

The second dependency is for class org.bouncycastle.openssl.PEMParser which we will use to load private and public keys from generated PEM files.

Loading RSA keys from PEM files

First we need to be able to load RSA private or public key from a disk file into a Java object of a proper class from Bouncy Castle. For this task I propose a following Java code:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.lang3.Validate;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;

public class KeyUtil {
	
	public static AsymmetricKeyParameter loadPublicKey(InputStream is) {
		SubjectPublicKeyInfo spki = (SubjectPublicKeyInfo) readPemObject(is);
		try {
			return PublicKeyFactory.createKey(spki);
		} catch (IOException ex) {
			throw new RuntimeException("Cannot create public key object based on input data", ex);
		}
	}
	
	public static AsymmetricKeyParameter loadPrivateKey(InputStream is) {
		PEMKeyPair keyPair = (PEMKeyPair) readPemObject(is);
		PrivateKeyInfo pki = keyPair.getPrivateKeyInfo();
		try {
			return PrivateKeyFactory.createKey(pki);
		} catch (IOException ex) {
			throw new RuntimeException("Cannot create private key object based on input data", ex);
		}
	}

	private static Object readPemObject(InputStream is) {
		try {
			Validate.notNull(is, "Input data stream cannot be null");
			InputStreamReader isr = new InputStreamReader(is, "UTF-8");
			PEMParser pemParser = new PEMParser(isr);
			
			Object obj = pemParser.readObject();
			if (obj == null) {
				throw new Exception("No PEM object found");
			}
			return obj;
		} catch (Throwable ex) {
			throw new RuntimeException("Cannot read PEM object from input data", ex);
		}
	}
}

Actual loading of a private or a public RSA key with use of KeyUtil class looks like this:

// load a public key from the file 'pub-key.pem'
InputStream pubKeyInpStream = new FileInputStream(new File("pub-key.pem"));
AsymmetricKeyParameter publKey = KeyUtil.loadPublicKey(pubKeyInpStream);

// load a private key from the file 'priv-key.pem'
InputStream prvKeyInpStream = new FileInputStream(new File("priv-key.pem"));
AsymmetricKeyParameter privKey = KeyUtil.loadPrivateKey(prvKeyInpStream);

 

RSA digital signature creation and verification

Basically the creation of a digital signature is an encryption of a message digest with use of a private key. The verification consists of decrypting a signature with use of a public key and comparing it to a message digest calculated again from the message.

So one new thing to do is a calculation of a message digest. Here one can choose from couple of different digest algorithms. Bouncy Castle support many of them (please note to choose a digest algorithm that is considered secure). Let’s stick to SHA512 for code samples. This algorithm is provided by org.bouncycastle.crypto.digests.SHA512Digest class.

Another thing to consider is that a “message” may be anything one wants. However before calculating its digest it must be first converted to a byte array. For text messages one can use String.getBytes(Charset). I will just assume our message is in byte array form already.

RSA signing and signature verification is handled by a single class in Bouncy Castle – org.bouncycastle.crypto.signers.RSADigestSigner. Now we have all ingredients!

Creation of an RSA digital signature:

// GIVEN: InputStream prvKeyInpStream
AsymmetricKeyParameter privKey = KeyUtil.loadPrivateKey(prvKeyInpStream);

// GIVEN: byte[] messageBytes = ...
RSADigestSigner signer = new RSADigestSigner(new SHA512Digest());
signer.init(true, privKey);
signer.update(messageBytes, 0, messageBytes.length);

try {
    byte[] signature = signer.generateSignature();
} catch (Exception ex) {
    throw new RuntimeException("Cannot generate RSA signature. " + ex.getMessage(), ex);
}

 

Verification of an RSA digital signature:

Please note that now the first parameter of RSADigestSigner.init(..) method call is false.

// GIVEN: InputStream pubKeyInpStream
AsymmetricKeyParameter publKey = KeyUtil.loadPublicKey(pubKeyInpStream);

// GIVEN: byte[] messageBytes
RSADigestSigner signer = new RSADigestSigner(new SHA512Digest());
signer.init(false, publKey);
signer.update(messageBytes, 0, messageBytes.length);

// GIVEN: byte[] signature - see code sample above
boolean isValidSignature = signer.verifySignature(signature);

 

Here is the real life example of signature verification code: https://github.com/k-tomaszewski/kt-app-monitor/blob/master/src/main/java/kt/appmonitor/RSASignatureVerifier.java

Please leave your comment if you know that something should be improved here to increase signature security.

Advertisements

About krzysztoftomaszewski

I've got M.Sc. in software engineering. I graduated in 2005 at Institute of Computer Science, Warsaw University of Technology, Faculty of Electronics and Information Technology. I'm working on computer software design and engineering continuously since 2004.
This entry was posted in Java, security and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s