Elliptic Curve Signatures and How to Use Them in Your Java Application
By: Daniel Etzold
Tags: Signature, Performance, Security, Java, Elliptic Curve

Let’s assume you want to send a message and you want to ensure that a) the receiver can detect whether or not the message was modified (integrity) and b) the receiver can verify that you’re the author of this message (message authentication). In that case you typically use digital signatures to digitally sign that message.

Actually, digital signatures also provide non-repudiation, i.e. the sender cannot deny the signing of a message. However, providing integrity and message authentication are the two most common use cases for signatures.

Initial Steps

Initially, you create a public key and a private key. You have to do this just once. When you want to sign a message, you use the private key. To verify a signature, you use the public key. The private key is a secret key and must not be shared with other people as everyone in possession of this key, would be able to compute a valid signature and could trick the receiver of a message into believing that the message is from you. On the other hand, the public key needs to be distributed to everyone who should be able to verify a signature, i.e. the public key is public.

Steps to Sign and Verify a Message

If you have created a public and private key, a message is typically signed as follows:

Compute a hash h of the message m with a cryptographic hash function such as SHA-256. Compute the signature s for the hash using the private key. Send the message m and the signature s to the receiver. The receiver verifies the signature s with the sender’s public key.

Algorithms

The most commonly used signature algorithms are those standardized by the National Institute of Standards and Technology (NIST) in FIPS 186–4. These are 1) the RSA Digital Signature Algorithm, 2) the Digital Signature Algorithm (DSA) and 3) the Elliptic Curve Digital Signature Algorithm (ECDSA).

From these three algorithms DSA should not be used for generating digital signatures anymore as this algorithm will no longer be approved by NIST with FIPS 186–5, the successor of FIPS-186–4. Instead, FIPS-186–5 will only approve the verification of DSA signatures. However, the generation of RSA and ECDSA signatures are still approved and, additionally, the generation of signatures with the Edwards-curve Digital Signature Algorithm (EdDSA).

Both ECDSA and EdDSA are based on elliptic curves, DSA is based on the discrete logarithm problem and RSA’s security is based on the problem of factoring large numbers (decomposition of a composite number into smaller integers) and the RSA problem (taking the eth roots modulo a composite n) for which no efficient algorithm exists.

Security Level and Key Sizes

The big advantage of signature algorithms based on elliptic curves is that they require much smaller keys compared to RSA to achieve the same level of security resulting in much smaller signatures and reduced computational requirements. Due to this, these algorithms are particularly well suited for embedded and IoT devices but also for applications where a huge number of signatures needs to be computed or verified.

The security level is typically measured in number of bits. For instance, if a system provides a security of 5 bits, an attacker would need to perform 2·2·2·2·2=32 (2 to the power of 5) operations to break it. For symmetric ciphers such as AES the security level is typically equal to the size of the key that is used. For asymmetric algorithms such as RSA, ECDSA and EdDSA the security level of a key size is estimated from the difficulty of the underlying mathematical problem. The level is adjusted every time a new attack is discovered which reduces the effort to solve the mathematical problem. NIST has published their recommendations for key sizes in NIST Special Publication 800–57 Part 1 Revision 5. A summary for asymmetric algorithms is shown in the following table.

Also, in Publication 800–57 Part 1 Revision 5, NIST does no longer approve key sizes which provide a security level less then 112 bits, at least when cryptography is used to protect federal government information.

Recommendations on the key sizes may differ slightly between various organizations. An overview of recommendations can also be found here.

Signature Size

The size of a signature depends on the algorithm and the key size. The size of an RSA signature is equal to the size of the key. For instance, if you’re using a 3072 bit key to sign a message, the size of the signature is 3072 bit (384 bytes). ECDSA and EdDSA signatures have twice the size of the used key. For instance, if you’re using a 256 bit key to achieve 128 bit security, the signature will be 512 bit (64 bytes).

Compute ECDSA Signatures in Java

Select a Curve

Since version 7 Java supports various elliptic curves. If you have jshell installed on your machine, you can easily get a list of all supported curves by executing the following Java code in jshell:

jshell> import java.security.*;

jshell> Security.getProvider("SunEC").getService(

...> "AlgorithmParameters", "EC").getAttribute(

...> "SupportedCurves")

The list should contain at least the following curves:

secp192r1 (NIST P-192)

secp224r1 (NIST P-224)

secp256r1 (NIST P-256)

secp384r1 (NIST P-384)

secp521r1 (NIST P-521)

These are curves that have been standardized by NIST in FIPS 186–4. The prefix “sec” stands for “Standards for Efficient Cryptography”, the letter “p” indicates that this curve is over a prime field, the number following the “p” denotes the key size and finally, the letter “r” indicates that the parameters of the curve were chosen verifiably at random. More details on the naming convention, the curves and their parameters can also be found in “SEC 2: Recommended Elliptic Curve Domain Parameters”.

In the following example we use the curve secp224r1 which provides a security level of 112 bit, results in small signatures and doesn’t take too much resources to compute the signature. This curve is well suited for most applications with high security and high performance requirements.

Create a Public and Private Key

Before we can sign a message, we have to create a public and private key. We do this as follows:

import java.security.*;

import java.security.spec.*; KeyPairGenerator g = KeyPairGenerator.getInstance("EC","SunEC");

ECGenParameterSpec ecsp = new ECGenParameterSpec("secp224r1");

g.initialize(ecsp); KeyPair kp = g.genKeyPair();

PrivateKey privKey = kp.getPrivate();

PublicKey pubKey = kp.getPublic();

Now, we select the signature algorithm. Here, we use ECDSA to sign the SHA-256 hash of the message.

Signature s = Signature.getInstance("SHA256withECDSA","SunEC");

s.initSign(privKey);

Next, we compute the signature of a message. First, we call the update function of the Signature instance and provide the message as input. Then we call the sign method which computes and returns the signature.

byte[] msg = "Hello, World!".getBytes("UTF-8");

byte[] sig;

s.update(msg);

sig = s.sign();

Now, the signature is stored in the byte array “sig”.

The receiver of the message can now verify the signature as follows: the receiver creates an instance of Signature and initializes this instance with the public key. Then this instance is updated with the message and finally the receiver can verify the signature by calling the verify method with the signature to be verified.

Signature sg = Signature.getInstance("SHA256withECDSA", "SunEC");

sg.initVerify(pubKey);

sg.update(msg);

boolean validSignature = sg.verify(sig);

If the provided message is the same for which the signature was computed, the verify method will return true, otherwise false.

The full source code is also available on GitHub here.

Performance

To get an idea how much signatures we can compute per second, I signed the “Hello, World!” message in a loop and measured the time it takes on an i5–5300U with 2.3GHz on one CPU core. I have reused the signature instance, i.e. in each iteration I just called the update and sign method.

The following table summarizes the result.

Conclusion

Elliptic curve cryptography can be confusing as many different curves exist and one curve is sometimes known under different names. Usually, the curves standardized by NIST (i.e. P-192 aka secp192r1, P-224 aka secp224r1 and so on) should be sufficient for most applications with high security requirements.

Since Java 7 it is quite easy to compute elliptic curve signatures as since then Java supports the most frequently used curves. External dependencies are not necessary anymore. Furthermore, the number of lines required to compute or verify signatures is really small so that the code is simple and clean.

Curves with small key sizes are well suited for high performance applications. On a modern CPU with four cores you should be able to compute more than 10.000 signatures per second.