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.
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.
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.
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 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.
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.
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 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.
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.
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.
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.
□
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. ↩︎
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. ↩︎