RSA encryption in Java

RSA is one of the most common schemes for asymmetric encryption, named after its inventors (Rivest–Shamir–Adleman). To perform RSA encryption in Java, we use a Cipher object in a similar way to symmetric encryption. However, the code is slightly different because instead of a single secret key, RSA works with a public/private key pair. The use of the two keys can be summarised as follows:

KeyUseKnown by
Private keyDecryption. Only the "owner" of the key pair. You can see this as belonging on the "server" side of a client/server relationship.
Public keyEncryption. Any party: as the name implies, the public key is not a secret and can be freely distributed to any "client" that might need to communicate with the "server" in our client/server model.

On the "server", an RSA key pair would be generated in Java as follows:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.genKeyPair();
Key publicKey = kp.getPublic();
Key privateKey = kp.getPrivate();

Notice that we specify a key length of 2048 bits, which is a commonly recommended size to guarantee a good level of security. Choosing an RSA key length is a tradeoff between security and performance. For some applications, 1024 bits may be an appropriate choice.

The public key can now be distributed freely to any client that wishes to communicate securely with us. (In practice in Java, Key implementations are serializable, as one way of physically doing this. It is also possible to inspect them for the actual numbers that make them up and transmit these directly.)

A client wishing to send information would then encrypt it as follows, using a Java Cipher objet instantiated to use the RSA cheme:

byte[] data = ...data to encrypt
	
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, kp.getPublic());
byte[] bytesToSend = cipher.doFinal(data);

Notice that, unlike a typical symmetric encryption scheme, we perform the encryption in a single call to doFinal(). This is because asymmetric schemes such as RSA are designed to encrypt a small handful of bytes such as a user name/password (or, more commonly, the secret key for a symmetric scheme such as AES that will then be used for the remainder of the communication). In fact, the Java call Cipher.doFinal() will actually throw an exception if you try to encrypt a number of bytes more than around a tenth of the key size.

Back on the server, the data can then be decrypted using the private key:

byte[] bytesReceived = ...data received

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, kp.getPrivate());
byte[] decrypted = cipher.doFinal(bytesReceived);

The scheme is therefore "asymmetric" because only one party will have the private key be able to decrypt information. But any party or "client" with the public key can encrypt information.

Saving the public and private key

In practice, we need often to store the public and private keys somewhere, and possibly transmitted to different participants. Typically, the private key will be placed on our server, and the public key distributed to clients. To store the key, we simply need to pull out the modulus and the public and private exponents, then write these numbers to some file (or put in whatever convenient place).

The Key interface allows us to pretend for a second that we don't need to worry about the algorithm-specific details of keys. But unfortunately, in practice we do. So there also exist "key specification" classes— RSAPublicKeySpec and RSAPrivateKeySpec in this case— with transparent methods for pulling out the parameters that make up the key. Then, a KeyFactory allows us to translate between Keys and their corresponding specification. It's a bit clumsy, but the code ends up as follows:

KeyFactory fact = KeyFactory.getInstance("RSA");
RSAPublicKeySpec pub = fact.getKeySpec(kp.getPublic(),
  RSAPublicKeySpec.class);
RSAPrivateKeySpec priv = fact.getKeySpec(kp.getPrivate(),
  RSAPrivateKeySpec.class);

saveToFile("public.key", pub.getModulus(),
  pub.getPublicExponent());
saveToFile("private.key", priv.getModulus(),
  priv.getPrivateExponent());

To save the moduli and exponents to file, we can just use boring old serialisation, since the modulus and exponents are just BigInteger objects:

public void saveToFile(String fileName,
  BigInteger mod, BigInteger exp) throws IOException {
  ObjectOutputStream oout = new ObjectOutputStream(
    new BufferedOutputStream(new FileOutputStream(fileName)));
  try {
    oout.writeObject(mod);
    oout.writeObject(exp);
  } catch (Exception e) {
    throw new IOException("Unexpected error", e);
  } finally {
    oout.close();
  }
}

In our example, we end up with two files: public.key, which is distributed with out clients (it can be packed into the jar, or whatever); meanwhile, private.key, is kept secret on our server. Needless to say, if we save a private key to file, then we must generally configure access permissions to that file so that unauthorised users (or malware!) cannot read the file.

Now we have a mechanism to generate a key pair and save those keys for the future, we can consider how to actually perform RSA encryption/decryption in more detail, reading in the key files we generated.

Other encryption and RSA-related topics

The information above will hopefully give you a good introduction to the concepts of asymmetric encryption and the RSA encryption scheme. Other issues to consider include:

Other areas relating to Java cryptography include:


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.