Exercise 8: Digital Signatures in Java

This exercise explores how public key cryptography can be used to generate a digital signature for content in a file, and then verify that signature. The scheme used here is Ed25519: a specific configuration of the Edwards curve Digital Signature Algorithm (EdDSA). Ed25519 provides a level of security comparable to that of a symmetric cipher with a 128-bit key. It offers higher performance and significantly smaller key sizes than more traditional approaches based on the RSA algorithm, and is rapidly replacing such approaches in applications requiring digital signatures.

As in previous exercises, we use Google’s Tink library here, because it provides direct support for signing and signature verification via a clean, convenient high-level API.

Setting Up

Download tink-sig.zip and unzip it. This should create a directory named tink-sig. 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.

Creating a Key Pair

  1. 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. Add all the code that is needed.

    Note that this program will be very similar to the equivalent program from the Hybrid Encryption in Java exercise. The only real differences will be in how Tink is configured and how a key template is acquired. You’ll need to use the SignatureConfig class for Tink configuration. You’ll need to specify a key template using the string "ED25519". Consult the Tink API docs if you need further help.

  2. 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.

Signing a File

Signing is an asymmetric cryptographic operation involving the private key. The operation can be reversed by anyone possessing the corresponding public key, so this does not provide confidentiality as conventional encryption would. However, successful reversal of the operation using a public key means that the operation must have been carried with the corresponding private key. Thus the signature provides an authentication tool comparable to an HMAC, but without the problem of sharing a secret securely1.

  1. Open SignFile.java in your editor or IDE. Add the required code, using the comments in the file to guide you. You will have seen much of what is needed in earlier exercises. The main difference is how you obtain a cryptographic primitive that can be used for signing:

    PublicKeySign signer = key.getPrimitive(PublicKeySign.class);

    Once you have a PublicKeySign object2, you can call its sign() method to generate a signature for a given array of bytes. See the API docs for further information.

  2. Test the finished program with

    ./gradlew signfile

    This expects you to have already run the CreateKeys program. It uses the previously-generated private key to sign the file data/message.txt. It should output the signature to the file build/message.txt.sig.

Verifying a Signature

Verifying a signature involves use of the public key, which means that anyone can attempt it (because the key is public). However, a verifiable signature can only be generated by the owner of the corresponding private key. Also, a signature will verify only if the signed data hasn’t changed in any way since the time that the signature was generated.

  1. Open VerifySignature.java in your editor or IDE. Add the required code, using the comments in the file to guide you. This time, the required cryptographic primitive is an instance of PublicKeyVerify:

    PublicKeyVerify verifier = key.getPrimitive(PublicKeyVerify.class);

    Once you have a PublicKeyVerify object, you can call its verify() method to verify the signature on given data. See the API docs for further information.

    Note that verify() will throw a GeneralSecurityException if verification fails – so you’ll need to wrap the call using try...catch. Arrange things so that the program prints “Signature is NOT valid” if the exception is thrown, and “Signature is valid” if there is no exception.

  2. Test the finished program with

    ./gradlew verifysig

    You can also run all three programs in sequence, with

    ./gradlew run

    The final verifysig step should report that the signature is valid.

  3. Edit data/message.txt, changing a single character of the file. Run the verification step one more time, with

    ./gradlew verifysig

    This time, it should report that the signature is not valid.

  1. Nevertheless, signatures can be secure only if (a) the private key used for signing has been kept secret, and (b) you can trust the public key. ↩︎

  2. The name PublicKeySign simply refers to the fact that public key cryptography is used. Keep in mind that signing is done with the private key and verification is done with the public key. ↩︎