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:
Key | Use | Known by |
Private key | Decryption. |
Only the "owner" of the key pair. You can see this as belonging on the "server" side
of a client/server relationship. |
Public key | Encryption. |
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.