ppk
Concise Public Private Key encryption using Java.
Features
- Builders and method chaining
byte[]
encryption/decryptionString
encryption/decryption- streaming encryption/decryption
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.