Abstract
This is the 3rd of a three-part blog series covering Java cryptographic algorithms. The series covers how to implement the following:
- Hashing with SHA–512
- Single-key symmetric encryption with AES–256
- Public/Private key asymmetric encryption with RSA–4096
This 3rd post details how to implement public/private key, asymmetric, RSA–4096 encryption. Let’s get started.
Disclaimer
This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.
Requirements
I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.
- Java 1.8.0_152_x64
- NetBeans 8.2 (Build 201609300101)
- Maven 3.0.5 (Bundled with NetBeans)
Download
Visit my GitHub Page to see all of my open source projects. The code for this post is located in project: thoth-cryptography
Asymmetric Encryption
About
Asymmetric algorithms are based on 2 keys: a public key and a private key. The public key is responsible for encryption and the private key is responsible for decryption. The public key can be freely distributed. With the public key, any client can encrypt a message which only you - with the private key - can decrypt (Asymmetric algorithms, n.d. para. 3).
Asymmetric algorithms are the workhorse of the Internet. Protocols like SSH, OpenPGP, SSL, and TLS rely on asymmetric algorithms (Rouse, 2016, para. 2). Anyone who uses a web browser for something like online banking inherently knows the importance of asymmetric algorithms.
Research done as of today seems to indicate the best and most secure public/private key, asymmetric, encryption algorithm is the following (Sheth, 2017, “Choosing the correct algorithm”, para.2):
- Algorithm: RSA
- Mode: ECB // It’s really NONE but ECB is needed to get Java to work.
- Padding: OAEPWithSHA–512AndMGF1Padding
- Key size: 4096 bit
RSA isn’t a block cipher so ECB mode doesn’t make much sense, but, ECB
is needed to make Java work even though the mode isn’t used under the covers (Brightwell, 2015). OAEP provides a high level of randomness and padding. Let’s take a look at an example.
Example
Listing 1 is the RsaTest.java unit test. It is a full demonstration on the following:
- Generate and store an RSA 4096-bit key
- RSA Encryption
- RSA Decryption
Listing 2 shows RsaKeyPairProducer.java. This is a helper class which is responsible for producing a new KeyPair
. The KeyPair
contains both the PublicKey
and PrivateKey
.
Listing 3 shows RsaPrivateKeyProducer.java. This is a helper class which is responsible for reproducing a PrivateKey
from a byte[]
.
Listing 4 shows RsaPublicKeyProducer.java. This is a helper class which is responsible for reproducing a PublicKey
from a byte[]
.
Listing 5 shows ByteArrayWriter.java and Listing 6 shows ByteArrayReader.java. These are helper classes responsible for reading and writing a byte[]
to a file. It’s up to you to determine how to store the byte[]
of your keys, but it needs to be stored securely somewhere (file, database, git repository, etc.).
Listing 7 shows RsaEncrypter.java. This is a helper class which is responsible for encryption.
Finally, listing 8 shows RsaDecrypter.java. This is a helper class which is responsible for decryption.
Listing 1 - RsaTest.java class
package org.thoth.crypto.asymmetric;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.thoth.crypto.io.ByteArrayReader;
import org.thoth.crypto.io.ByteArrayWriter;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaTest {
static Path privateKeyFile;
static Path publicKeyFile;
@BeforeClass
public static void beforeClass() throws Exception {
// Store the PrivateKey and PublicKey bytes in the ./target
// diretory. Do this so it will be ignore by source control.
// We don't want this file committed.
privateKeyFile
= Paths.get("./target/RsaPrivate.key").toAbsolutePath();
publicKeyFile
= Paths.get("./target/RsaPublic.key").toAbsolutePath();
// Create KeyPair for RSA
KeyPair keyPair
= new RsaKeyPairProducer().produce();
// Store the PrivateKey bytes. This is what
// you want to keep absolutely safe
{
ByteArrayWriter writer = new ByteArrayWriter(privateKeyFile);
writer.write(keyPair.getPrivate().getEncoded());
}
// Store the PublicKey bytes. This you
// can freely distribute so others can
// encrypt messages which you can then
// decrypt with the PrivateKey you keep safe.
{
ByteArrayWriter writer = new ByteArrayWriter(publicKeyFile);
writer.write(keyPair.getPublic().getEncoded());
}
}
@Test
public void encrypt_and_decrypt() throws Exception {
// setup
PrivateKey privateKey
= new RsaPrivateKeyProducer().produce(
new ByteArrayReader(privateKeyFile).read()
);
PublicKey publicKey
= new RsaPublicKeyProducer().produce(
new ByteArrayReader(publicKeyFile).read()
);
RsaDecrypter decrypter
= new RsaDecrypter(privateKey);
RsaEncrypter encrypter
= new RsaEncrypter(publicKey);
String toEncrypt
= "encrypt me";
// run
byte[] encryptedBytes
= encrypter.encrypt(toEncrypt);
System.out.printf("Encrypted %s%n", new String(encryptedBytes,"UTF-8"));
String decrypted
= decrypter.decrypt(encryptedBytes);
// assert
Assert.assertEquals(toEncrypt, decrypted);
}
}
Listing 2 - RsaKeyPairProducer.java class
package org.thoth.crypto.asymmetric;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaKeyPairProducer {
/**
* Generates a new RSA-4096 bit {@code KeyPair}.
*
* @return {@code KeyPair}, never null
* @throws RuntimeException All exceptions are caught
* and re-thrown as {@code RuntimeException}
*/
public KeyPair produce() {
KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
//keyGen.initialize(3072);
keyGen.initialize(4096);
return keyGen.generateKeyPair();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
Listing 3 - RsaPrivateKeyProducer.java class
package org.thoth.crypto.asymmetric;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaPrivateKeyProducer {
/**
* Regenerates a previous RSA {@code PrivateKey}.
*
* @param encodedByteArrayForPrivateKey The bytes this method
* will use to regenerate a previously created {@code PrivateKey}
*
* @return {@code PrivateKey}, never null
* @throws RuntimeException All exceptions are caught
* and re-thrown as {@code RuntimeException}
*/
public PrivateKey produce(byte[] encodedByteArrayForPrivateKey) {
try {
PrivateKey privateKey = KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(encodedByteArrayForPrivateKey));
return privateKey;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
Listing 4 - RsaPublicKeyProducer.java class
package org.thoth.crypto.asymmetric;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaPublicKeyProducer {
/**
* Regenerates a previous RSA {@code PublicKey}.
*
* @param encodedByteArrayForPublicKey The bytes this method
* will use to regenerate a previously created {@code PublicKey}
*
* @return {@code PublicKey}, never null
* @throws RuntimeException All exceptions are caught
* and re-thrown as {@code RuntimeException}
*/
public PublicKey produce(byte[] encodedByteArrayForPublicKey) {
try {
PublicKey publicKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(encodedByteArrayForPublicKey));
return publicKey;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
Listing 5 - ByteArrayWriter.java class
package org.thoth.crypto.io;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ByteArrayWriter {
protected Path outputFile;
private void initOutputFile(Path outputFile) {
this.outputFile = outputFile;
}
private void initOutputDirectory() {
Path outputDirectory = outputFile.getParent();
if (!Files.exists(outputDirectory)) {
try {
Files.createDirectories(outputDirectory);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public ByteArrayWriter(Path outputFile) {
initOutputFile(outputFile);
initOutputDirectory();
}
public void write(byte[] bytesArrayToWrite) {
try (
OutputStream os
= Files.newOutputStream(outputFile);
PrintWriter writer
= new PrintWriter(os);
){
for (int i=0; i<bytesArrayToWrite.length; i++) {
if (i>0) {
writer.println();
}
writer.print(bytesArrayToWrite[i]);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Listing 6 - ByteArrayReader.java class
package org.thoth.crypto.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class ByteArrayReader {
protected Path inputFile;
public ByteArrayReader(Path inputFile) {
this.inputFile = inputFile;
}
public byte[] read() {
try (
Scanner scanner
= new Scanner(inputFile);
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
){
while (scanner.hasNext()) {
baos.write(Byte.parseByte(scanner.nextLine()));
}
baos.flush();
return baos.toByteArray();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Listing 7 - RsaEncrypter.java class
package org.thoth.crypto.asymmetric;
import java.security.PublicKey;
import javax.crypto.Cipher;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaEncrypter {
protected RsaCipher cipher;
public RsaEncrypter(PublicKey key) {
this.cipher = new RsaCipher(Cipher.ENCRYPT_MODE, key);
}
public byte[] encrypt(String message) {
try {
return cipher
.update(message.getBytes("UTF-8"))
.doFinal()
;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Listing 8 - RsaDecrypter.java class
package org.thoth.crypto.asymmetric;
import java.security.PrivateKey;
import javax.crypto.Cipher;
/**
*
* @author Michael Remijan mjremijan@yahoo.com @mjremijan
*/
public class RsaDecrypter {
protected RsaCipher cipher;
public RsaDecrypter(PrivateKey key) {
this.cipher = new RsaCipher(Cipher.DECRYPT_MODE, key);
}
public String decrypt(byte[] message) {
try {
return new String(
cipher.update(message).doFinal()
, "UTF-8"
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Summary
Encryption isn’t easy. And easy examples will result in implementations with security vulnerabilities for your application. If you need a public/private key, asymmetric, encryption algorithm, use RSA/ECB/OAEPWithSHA–512AndMGF1Padding, with a 4096 bit key.
References
Sheth, M. (2017, April 18). Encryption and Decryption in Java Cryptography. Retrieved from https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography.
- Best algorithms, modes, and paddings
- Use 4096-bit key
Brightwell, W., poncho. (2015, May 4). Is ECB mode safe to use with RSA encryption?. Retrieved from https://crypto.stackexchange.com/questions/25420/is-ecb-mode-safe-to-use-with-rsa-encryption.
- ECB isn’t applicable; Java doesn’t use it under the covers.
- RSA uses randomness and the padding to avoid ECB issue of the same plaintext generating the same ciphertext
Marilena. (2016, November 29). Java - Asymmetric Cryptography example. Retrieved from https://www.mkyong.com/java/java-asymmetric-cryptography-example/.
- Writing the public/private keys to file
- Reading the public/private keys from file
- Encrypt & Decrypt
Key size. (2017, October 12). Wikipedia. Retrieved from https://en.wikipedia.org/wiki/Key_size.
- 2048-bit keys are sufficient until 2030
- An RSA key length of 3072 bits should be used if security is required beyond 2030
user4982. (2013, November 4). How are IVs used in association with RSA Encryption?. Retrieved from https://crypto.stackexchange.com/questions/11403/how-are-ivs-used-in-association-with-rsa-encryption.
- IV values are not used with RSA