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, open the directory as a new
IntelliJ Java 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.
If you are working from the command line, you will need to make sure that
your PATH variable is set up so that the javac
and java
commands are
accessible.
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 this code:
KeysetHandle privateKey = KeysetHandle.generateNew(
KeyTemplates.get("ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM")
);
Then check that the code compiles, with
./gradlew classes
In the code you’ve just added, focus on the cipher specification string
ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM
. The first part of this
string specifies the use of ECIES: the
Elliptic Curve Integrated Encryption Scheme. This uses elliptic
curves for the public key cryptography side of things. The last part of
the string indicates that AES-GCM with a 128-bit key will be used for the
symmetric encryption part of the scheme. Finally, the middle part of
the string, beginning 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 will be 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 so1:
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 even omit run
from the above command, as it has been set as the
default Gradle task.
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.
□
This is on Linux or macOS. On Windows you can use the comp
command instead. ↩︎