/ppk

Concise Public Private Key (PKCS) encryption utilities in java

Primary LanguageJavaApache License 2.0Apache-2.0

ppk

Travis CI
Maven Central

Concise Public Private Key encryption using Java.

Features

  • Builders and method chaining
  • byte[] encryption/decryption
  • String encryption/decryption
  • streaming encryption/decryption

Maven site including javadoc.

Maven dependency

This library is available on Maven Central.

Add this maven dependency to your pom.xml:

<dependency>
    <groupId>com.github.davidmoten</groupId>
    <artifactId>ppk</artifactId>
    <version>0.1.8</version>
</dependency>

Generating keys

You'll need public and private key files. They can be generated using openssl, java, or a maven plugin:

Generating keys with OpenSSL

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -outform DER -pubout -out public.der
openssl pkcs8 -topk8 -nocrypt -in keypair.pem -outform DER -out private.der

Now move public.der and private.der somewhere so you can access them with your code (you can delete keypair.pem).

You can instead use the provided bash script generate-keys.sh:

./generate-keys.sh

which writes public.der and private.der to the current directory.

Generating keys with Java

KeyPair kp = PPK.createKeyPair();
byte[] privateKey = kp.privateKeyDer();
byte[] publicKey = kp.publicKeyDer();
//you might write those byte arrays to files to get 
// private.der and public.der
...

Generating keys with ppk-maven-plugin

<plugin>
    <groupId>com.github.davidmoten</groupId>
    <artifactId>ppk-maven-plugin</artifactId>
    <version>VERSION_HERE</version>
    <executions>
        <execution>
            <goals>
                <goal>create</goal>
            </goals>
            <configuration>
                <privateKeyFile>${project.build.directory}/private.der</privateKeyFile>
                <publicKeyFile>${project.build.directory}/public.der</publicKeyFile>
            </configuration>
        </execution>
    </executions>
</plugin>

To call:

mvn ppk:create

Encrypting and decrypting

Encrypt a string:

String content = "Hello World";
byte[] encrypted = 
    PPK.publicKey("/public.der")
       .encrypt(content, Charsets.UTF_8);

Decrypt a string:

String content = 
    PPK.privateKey("/private.der")
       .decrypt(bytes, Charsets.UTF_8);

Encrypt bytes:

byte[] encrypted = 
    PPK.publicKey("/public.der")
       .encrypt(bytes);

Decrypt bytes:

byte[] decrypted = 
    PPK.privateKey("/private.der")
       .decrypt(bytes);

The examples above assume /private.der and /public.der are on the classpath. You can use overloads for File definitions or pass in byte[] of InputStream values for those keys.

Encrypt a string:

String content = "Hello World";
byte[] encrypted = 
    PPK.publicKey(new File("/home/me/.keys/public.der"))
       .encrypt(content, Charsets.UTF_8);

If you are encrypting many things then its more efficient to use a single PPK object (though the AES secret key will be the same for all encryptions unless you call .unique() in the builder):

PPK ppk = PPK.publicKey("/public.der").build();
List<byte[]> encrypted = 
    list.stream()
        .map(ppk::encrypt)
        .collect(Collectors.toList());

Round trip example:

PPK ppk = PPK.publicKey("/public.der")
             .privateKey("/private.der")
             .build();
//result should be the same as bytes
byte[] result = ppk.decrypt(ppk.encrypt(bytes));

You can also minimize your memory usage by using the encrypt and decrypt methods with InputStream and OutputStream parameters:

PPK.publicKey("/public.der")
   .encrypt(inputStream, outputStream);
PPK.privateKey("/private.der")
   .decrypt(inputStream, outputStream);

Base64

A common use case is to encrypt a text password and store it encoded in Base64 (in a configuration file for example). ppk has convenience methods to support this:

String base64 = 
    PPK.publicKey("/public.der")
       .encryptAsBase64("mypassword");
System.out.println(base64);

which produces this output (364 characters):

/66kjqBF6C99vHTQmE2yk4HRD+3c9cNlCg3PO8fW4w7GvZokV0P7CUnWzI2SQuD7sOnEeAjMWfQZePpNk2cEVNMyKJUt2Gs3N92sgXjJra0fb7qqmQhWBWAKv/3avKO5SE3WcHT1E053tgs7lqiMoZEyZBdvqUY645UPnfQETMsBcXt+1fdo8udhdN+BibCJSJWZi50LziEBMllAJssY6DP8XFtZad7iknee32g+waS71ALT3DE/QaJhByeakKXjUhZKlH3zYMcNjF9/kuv1ORAgNriIS3mb7QDXwuvdFkAA3/7x3FE6fdYz2htsPNiEpHI8sYLRlbAsbZO2BrvKV6l7kl0W96bFG4BOoKaZIhR8

To decrypt:

String password = 
    PPK.privateKey("/private.der")
       .decryptBase64(base64);

Thread safety

Please note that PPK is not thread safe! Create a new one for each thread or use a pool.

Implementation details

This library uses a 2048 bit RSA public key to encrypt a generated (per instance of PPK) 128 bit AES key which is prepended to the AES encrypted message. The RSA algorithm used is RSA/ECB/OAEPWithSHA1AndMGF1Padding which uses Optimal Asymmetric Encryption Padding. This RSA variant has improved strength against plaintext attack.

Note that RSA can't be used to encrypt a message of arbitrary length because the maximum size of input in our case is 214 bytes. The AES key satisfies this criterion though, that's why it's used here. 256 bit AES is not used in this library because Java needs policy file additions to make it happen and 128 bit AES is currently strong enough. From Wikipedia:

As for now, there are no known practical attacks that would allow anyone to read correctly implemented AES [128 bit] encrypted data

The decrypt functionality knows about the format of the encrypted bytes and extracts the AES key using the RSA private key and then decodes the remaining bytes using the extracted AES key.

The output from the encryption method in this library is a byte sequence comprised of:

  • 1 byte = length in bytes of RSA encrypted AES key - 1
  • the bytes of the RSA encrypted AES key
  • the bytes of the AES encrypted message

If you do just want to use RSA on short input (<=214 bytes) you can use PPK.encryptRSA() and PPK.decryptRSA() methods.