December 22, 2017

Choosing Java Cryptographic Algorithms Part 3 - Public/Private key asymmetric encryption

Abstract

This is the 3rd of a three-part blog series covering Java cryptographic algorithms. The series covers how to implement the following:

  1. Hashing with SHA–512
  2. Single-key symmetric encryption with AES–256
  3. 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):

  1. Algorithm: RSA
  2. Mode: ECB // It’s really NONE but ECB is needed to get Java to work.
  3. Padding: OAEPWithSHA–512AndMGF1Padding
  4. 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:

  1. Generate and store an RSA 4096-bit key
  2. RSA Encryption
  3. 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