Post Thumbnail

How to Encrypt and Decrypt Data using RSA in Java

1. Overview

Rivest–Shamir–Adleman (RSA) is an asymmetric algorithm for encrypting and decrypting data using a public-private key pair. The public key is used to encrypt data while the private key is used to decrypt it.

The public key is intended to be shared publicly. This is because only the holder of the private key can successfully decrypt a data encrypted with the public key.

There are many applications of RSA in modern systems and chief of them is the HTTPS protocol which the vast majority of modern websites use.

2. RSA Key Generation

We will start by generating a public-private key pair using the KeyPairGenerator class from the java.security package.

Listing 2.1 RsaService.java

1
2
3
4
5
6

public static KeyPair generateRsaKeyPair(int rsaKeySize) throws NoSuchAlgorithmException {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(rsaKeySize);
    return keyPairGenerator.genKeyPair();
}

The generateRsaKeyPair() method will return an instance of KeyPair. This class has methods for getting the public and private keys.

The generateRsaKeyPair() accepts a key size to make it flexible for different use cases. It is recommended to use a large key size, like the 2048, to harden the encryption.

Listing 2.2 RsaServiceUnitTest.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

int rsaKeySize = 2048;
KeyPair keyPair = RsaService.generateRsaKeyPair(rsaKeySize);
assertNotNull(keyPair);

PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

//convert keys to Base64 string for easy storing and transmission
String base64PublicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
String base64privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());

In Listing 2.2 above, we converted the PublicKey and PrivateKey objects to a Base64 string for easy transmission and storage. It’s easier to share a String with someone than an array of bytes.

We can reverse the process and convert the base64 representation of the private and public keys to an instance of RSAPrivateKey and RSAPublicKey respectively.

This will come in handy in cases where we need to retrieve the base64 key from a database, or a config file.

Listing 2.3 RsaService.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static RSAPrivateKey base64StringToPrivateKey(String base64PrivateKey) throws Exception {
    byte [] keyBytes = Base64.getDecoder().decode(base64PrivateKey);
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return (RSAPrivateKey) keyFactory.generatePrivate(spec);
}

public static RSAPublicKey base64StringToPublicKey(String base64PublicKey) throws Exception {
    byte [] keyBytes = Base64.getDecoder().decode(base64PublicKey);
    X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    return (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec);
}

3. RSA Data Encryption and Decryption

Now that we have been able to generate a key pair, let’s use it to encrypt and decrypt some secret data that we want to convey between systems.

First, we will create a function for encrypting plain data using an RSA public key.

Listing 3.1 RsaService.java

1
2
3
4
5
public static String encryptDataRsa(String plainData, PublicKey publicKey) throws Exception {
    Cipher inputCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    inputCipher.init(Cipher.ENCRYPT_MODE, publicKey);
    return Base64.getEncoder().encodeToString(inputCipher.doFinal(plainData.getBytes()));
}

The encryptDataRsa() method will encrypt plain data and return a base64 encoded version of the final encrypted bytes. Again, this is for convenience during transmission and storage.

Listing 3.2 RsaService.java

1
2
3
4
5
6
public static String decryptDataRsa(String encryptedDataBase64, PrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(encryptedDataBase64));
    return new String(bytes);
}

The corresponding decryptDataRsa() method expects a base64 encoded data and a private key. It will decrypt the data and return it as a String which will be equal to the original plain data.

Let’s combine all the methods above to encrypt and decrypt the real identity of Satoshi Nakamoto.

Listing 3.3 RsaServiceUnitTest.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
void givenRSAKeyPairAndData_whenEncryptAndDecrypt_then() throws Exception {
    
    int rsaKeySize = 2048;
    KeyPair keyPair = RsaService.generateRsaKeyPair(rsaKeySize);
    assertNotNull(keyPair);

    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();

    //for learning purposes, let's convert to base64 string
    String base64PublicKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());
    String base64PrivateKey = Base64.getEncoder().encodeToString(privateKey.getEncoded());


    String plainSecretData = "The real name of Satoshi Nakomoto is ...";

    //encrypt
    RSAPublicKey rsaPublicKey = RsaService.base64StringToPublicKey(base64PublicKey);
    String encryptedData = RsaService.encryptDataRsa(plainSecretData, rsaPublicKey);

   //decrypt
    RSAPrivateKey rsaPrivateKey = RsaService.base64StringToPrivateKey(base64PrivateKey);
    String decryptedData = RsaService.decryptDataRsa(encryptedData, rsaPrivateKey);

    //assert
    assertEquals(plainSecretData, decryptedData);
    
}

To demonstrate that our encryption and decryption worked, we compared the original string and the final output of the decryptDataRsa() method - which turns out equal.

4. Conclusion

In this article, we have learned how to generate an RSA key pair and how we can use the resulting keys to encrypt and decrypt any data we want.

What if you want to apply this technique to encrypt and decrypt an HTTP request body? We will consider this in the next article.

The complete source code for this article is available on GitHub.

Seun Matt

Results-driven Engineer, dedicated to building elite teams that consistently achieve business objectives and drive profitability. With over 8 years of experience, spannning different facets of the FinTech space; including digital lending, consumer payment, collections and payment gateway using Java/Spring Boot technologies, PHP and Ruby on Rails