Thursday, June 2, 2022

Java RSA Encryption Decryption Example

In this example, we will be encrypting a string using a public key and decrypting it using a private key. As title says, we will be using the RSA (Rivest-Shamir-Adleman) public key cryptosystem. Making our life easy will be the Bouncy Castle Crypto APIs.

Here we go, let's create a Maven project:

mvn archetype:generate -DgroupId=com.blogspot.jpllosa -DartifactId=ecnrypt-decrypt -DarchetypeArtifactId=maven-archetype-quickstart

This template will create a Maven quick start project. Edit the pom.xml as shown below.


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.blogspot.jpllosa</groupId>
  <artifactId>encrypt-decrypt</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>ecnrypt-decrypt</name>
  <url>http://maven.apache.org</url>
  
  <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  
  <dependencies>
      <dependency>
          <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.68</version>
    </dependency>
    
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
  
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <!-- <scope>test</scope> -->
    </dependency>
  </dependencies>
</project>

Next, let's create two RSA key pairs. One with no password and the other with a password. We can create the key pairs using openssl. Like so:


$ openssl genrsa -out my-private-key.pem 4096

Generating RSA private key, 4096 bit long modulus
........................................................++
....++
e is 65537 (0x10001)

$ openssl rsa -in my-private-key.pem -outform PEM -pubout -out my-public-key.pem
writing RSA key

$ openssl genrsa -out mypassword-private-key.pem 4096 -passout pass:mypassword
Generating RSA private key, 4096 bit long modulus
.........................................................................................................++
....................++
e is 65537 (0x10001)

$ openssl rsa -in mypassword-private-key.pem -outform PEM -pubout -out mypassword-public-key.pem
writing RSA key

We will use the PEM (Privacy Enhanced Mail) format as this is a de facto file format for storing and sending cryptographic keys. Our private key has a key size of 4096 bits. The first two commands is the RSA key pair without a password and the next two are with a password.

First and foremost, we ask help from the Bouncy Castle API to read our PEM files.

CryptographyKeyReader.java

package com.blogspot.jpllosa;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.Security;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.util.io.pem.PemObject;

public class CryptographyKeyReader {

	public CryptographyKeyReader() {
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
	}
	
	public byte[] readPublicKey(String filename) {
		try (PEMParser pemParser = new PEMParser(new InputStreamReader(new FileInputStream(filename)))) {
			PemObject keyContent = pemParser.readPemObject();
			return keyContent.getContent();
		} catch (NullPointerException | IOException e) {
			System.out.println("Cannot read public key PEM format from file.");
			return null;
		}
	}
	
	public byte[] readPrivateKey(String filename, String password) {
		try (PEMParser pemParser = new PEMParser(new InputStreamReader(new FileInputStream(filename)))) {
			Object keyPair = pemParser.readObject();
			PrivateKeyInfo keyInfo;
			if (keyPair instanceof PEMEncryptedKeyPair) {
				PEMDecryptorProvider decryptorProvider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
				PEMKeyPair decryptedKeyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptorProvider);
				keyInfo = decryptedKeyPair.getPrivateKeyInfo();
			} else {
				keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
			}
			
			return keyInfo.getEncoded();
		} catch (NullPointerException | IOException e) {
			System.out.println("Cannot read private key PEM format from file.");
			return null;
		}
	}
}

We use PEMParser to read the public and private keys. For an encrypted key pair (i.e. the one with a password) we have an additional step. We pass the password to get the decrypted key pair.

Now that we can retrieve the key pairs, let's encrypt and decrypt some stuff.

App.java

package com.blogspot.jpllosa;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.codec.binary.Base64;

import static org.junit.Assert.*;

public class App {

	private CryptographyKeyReader ckReader;
	private Cipher cipher;
	
	PublicKey publicKey;
    PrivateKey privateKey;
    
    KeyPair keyPair;

	public App() {
		ckReader = new CryptographyKeyReader();
		try {
        	cipher = Cipher.getInstance("RSA");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        	e.printStackTrace();
        }
		
		privateKey = getPrivateKey("mypassword-private-key.pem", "mypassword");        
		publicKey = getPublicKey("mypassword-public-key.pem");
        
        keyPair = new KeyPair(publicKey, privateKey);
	}
    
    public String encrypt(String data) {
    	try {
    		cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
    		return Base64.encodeBase64String(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    	} catch (BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
    		System.out.println("Cannot encrypt data");
    		return "";
    	}
    }
    
    public String decrypt(String data) {
    	try {
    		cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
    		return new String(cipher.doFinal(Base64.decodeBase64(data)), StandardCharsets.UTF_8);
    	} catch (BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
    		System.out.println("Cannot decrypt data");
    		return "";
    	}
    }
    
    private PublicKey getPublicKey(String filename) {
    	PublicKey key = null;
    	
    	try {
    		byte[] keyBytes = ckReader.readPublicKey(filename);
    		X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); // Represents ASN.1 encoding of a public key
    		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    		key = keyFactory.generatePublic(spec);
    	} catch (NullPointerException npe) {
    		System.out.println("Cannot read public key from file.");
    	} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
    		System.out.println("Cannot create public key instance from file.");
    	}
    	
    	return key;
    }
    
    private PrivateKey getPrivateKey(String filename, String password) {
    	PrivateKey key = null;
    	
    	try {
    		byte[] keyBytes = ckReader.readPrivateKey(filename, password);
    		PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); // Represents ASN.1 encoding of a private key
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            key = keyFactory.generatePrivate(spec);
    	} catch (NullPointerException fileE) {
    		System.out.println("Cannot read private key from file.");
        } catch (NoSuchAlgorithmException | InvalidKeySpecException cryptoE) {
        	System.out.println("Cannot create private key instance from file.");
        }
        return key;
    }
    
    public static void main(String[] args) {
        System.out.println( "Encrypt/Decrypt with Public/Private keys." );

        App app = new App();
        
        String encryptedString = app.encrypt("once more unto the breach...");
        System.out.println("Encrypted: " + encryptedString);
        
        String decryptedString = app.decrypt(encryptedString);
        System.out.println("Decrypted: " + decryptedString);
        assertEquals("once more unto the breach...", decryptedString);
    }
}

On initialization, we get the key pairs and set the cipher instance to RSA. To encrypt, we initialize the cipher to encrypt mode and decrypt mode if we want to decrypt.

I use Spring Tool Suite to run the application (right click + Run As > Java Application). Your output should look something like this.


Encrypt/Decrypt with Public/Private keys.
Encrypted: fVmZilpad+Fdb2tw2fRmsRWbwpStk7zP64UGpFGZuopiY1Mi593O8NpsSm4Nd9cYun4m4jMfTY7VFE8MDHwimAPrIUAH2IaeKvR3qAGWSmalQp9v7e38g0QwCDzGR4Ql/+XBnCiBlEOW89djPDmXqEtT+OSpMi95cH6aQMdZ9tbd6e2ES0ioGM6MvciKcew5KPyzZdYJyMI1T5HmxpUjbRo0cjxE2yX8VUU9sVL9NZMWLZuIw6LqZfA7taRp5+i0dymYCrydJc0bkZRJkGMxdy06XNPhHSplaHQqJDat0YpzU2IAxcajgJ8Nxw/zklqK0jzqraTOKJydH2ghwWSzmoukIlojPQnDBX6yiyBtLoIjlRgB++WSYXOp/8bilypfb3OncHonSKft+CyF9fn0e+96HMKxew4DnG0pBLu4KYFlDVe7q0G9OxvhOn7TZcHlU+8G5izhk/xRD6vEVOe9Kedzz8LR00hCcK50dWa4x1SwPVquhlhZJdSw33R+9UTBshnmTWVNo1PXRVVlj0EUq5mqLyg3AGAeJS7nzaNWhXMVtKkPOkLkLyy9d8lAX9kxlNXvzyYxhPi6fySXBsJ+uFhxRaJBkspUuv4rcg63qRO4yKEm++0XjNQV1mseSCJZKdShneqpDnCbKd470i0PQuK+/oFFa1wEdIPj7Gw1KM4=
Decrypted: once more unto the breach...

There you have it. Hope this helps in your encryption needs. Full source code is found in GitHub.