This exercise explores hybrid encryption, which combines symmetric and asymmetric (public key) cryptographic techniques. A symmetric cipher and MAC are used to do AEAD, and the problem of how the communicating parties agree on the keys used by the cipher and MAC is solved using public key cryptography.
Make sure you do the exercise on Symmetric Ciphers in Java before attempting this one. As in that exercise, we use Google’s Tink library, because it provides direct support for hybrid encryption, via a clean, convenient high-level API.
Download tink-hybrid.zip and unzip it. This should create
a directory named tink-hybrid. You can work from the command line inside
this directory or, if you prefer IDEs, import the directory as a new
IntelliJ project. The rest of these instructions assume the use of
the command line.
Once again, Gradle is used to manage downloading of dependencies and the compilation & execution of the code - see the README file for further information on this. Note that you do not need to install Gradle yourself first; the only prerequisite for this exercise is a properly installed JDK.
Open CreateKeys.java in your editor or IDE. You’ll find this file and
the other source code files in the src/main/java subdirectory. The program
is incomplete but contains comments to guide implementation.
Under the ‘Configure Tink’ comment, add the following:
HybridConfig.register();
This initializes the Tink library to use all the primitives needed for hybrid encryption and decryption operations.
Under the ‘Generate key material’ comment, add these two lines:
KeyTemplate template = EciesAeadHkdfPrivateKeyManager.eciesP256HkdfHmacSha256Aes128GcmTemplate();
KeysetHandle privateKey = KeysetHandle.generateNew(template);
Make sure you get that complicated first line right!1. Then check that the code compiles, with
./gradlew classes
The method call in the first line obtains a key template that precisely
specifies the encryption scheme that we wish to use. In this case, it is
ECIES: the Elliptic Curve Integrated Encryption Scheme.
This uses elliptic curves for the public key cryptography side of things.
You can also see from the class name and method name that AEAD will be
used for the symmetric encryption part of the scheme - specifically
AES-GCM with a 128-bit key. Finally, the Hkdf indicates the use of a
hash-based key derivation function to derive the cipher and MAC keys
needed for AEAD. Specifically, an HMAC based on SHA-256 is used.
The next step is to write the private key to a file. We assume that the name of this file is specified as the first command line argument. This uses code that you have already seen in earlier exercises:
CleartextKeysetHandle.write(
privateKey, JsonKeysetWriter.withPath(args[0])
);
The key written by this code will be used by the decryption program.
The final step is to obtain the public key and then write it to another JSON file, this time assuming that the file name has been supplied as the second command line argument:
KeysetHandle publicKey = privateKey.getPublicKeysetHandle();
CleartextKeysetHandle.write(
publicKey, JsonKeysetWriter.withPath(args[1])
);
The key written by this code will be used by the encryption program.
Test the finished program with
./gradlew createkeys
This should output the two keys to files named private_key.json and
public_key.json, in the build subdirectory. Examine the contents of
these files.
Encryption and decryption are handled by the programs Encrypt.java and
Decrypt.java, which can both be found in src/main/java. Both programs
are incomplete skeletons that compile and run but currently do nothing.
You’ll need to add the required code to each. We’ll leave it to you to
figure this code out for yourself!
Use the comments in the files and the Tink API docs to help you. Keep
in mind that Tink has a remarkably consistent API, so the code you need to
write is very similar to what you’ve seen in the previous exercise. The
main difference is that you’ll need to create separate encryption and
decryption primitives here, using the classes HybridEncrypt and
HybridDecrypt.
You can test the encryption program with
./gradlew encrypt
If you’ve implemented it correctly, the encrypted output should appear in
the file build/encrypted.bin.
You can test decryption with
./gradlew decrypt
If you’ve implemented the program correctly, the decrypted output should
appear in the file build/decrypted.txt. You can compare this with the
original input like so2:
diff -s build/decrypted.txt data/message.txt
If you wish to run all three programs in the required sequence, do
./gradlew run
You can use Tink for hybrid encryption in several other languages besides Java - e.g., C++ and Python. An alternative to Tink in Python is PyCryptodome. This doesn’t support hybrid encryption explicitly, but the documentation shows a simple example of using RSA to protect an AES session key.