Digital Signatures

Digital signatures verify the authenticity of digital messages through the use of a cryptographic hash function and asymmetric encryption. It provides message integrity (via the hash function and encryption), authentication (via encryption), and non-repudiation (via asymmetric encryption).

The message produced by the sender is first hashed using a one-way cryptographic hashing function. The hash is then signed (i.e., encrypted) using the sender’s private key yielding the signature. The message and signature are transmitted to the recipient. The recipient computes the hash of the message and compares it to the verified signature (i.e., decrypted hash). If the hashes match, the message is authentic.

The following is a simple example digital signature class using JDK8 signature algorithms. Of note, the encryption algorithm selected must match the one used to generate the keys (see proceeding code block).

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
 
/**
 * Digital signatures class.
 * @author Ray Hylock
 */
public class DigitalSignature {
    // defaults
    private static final Algorithms DEFAULT_ALGO = Algorithms.SHA512withRSA;
    private static final String CHARSET = "UTF8";
     
    /** 
     * Digital signature algorithm: 
     * {@code NONEwithRSA}, {@code MD2withRSA}, {@code MD5withRSA}, 
     * {@code SHA1withRSA}, {@code SHA224withRSA}, {@code SHA256withRSA}, 
     * {@code SHA384withRSA}, {@code SHA512withRSA} (default), {@code NONEwithDSA}, 
     * {@code SHA1withDSA}, {@code SHA224withDSA}, {@code SHA256withDSA}, 
     * {@code NONEwithECDSA}, {@code SHA1withECDSA}, {@code SHA224withECDSA}, 
     * {@code SHA256withECDSA}, {@code SHA384withECDSA}, {@code SHA512withECDSA}
     */
    public static enum Algorithms { 
        NONEwithRSA, MD2withRSA, MD5withRSA, SHA1withRSA, SHA224withRSA, 
        SHA256withRSA, SHA384withRSA, SHA512withRSA, NONEwithDSA, 
        SHA1withDSA, SHA224withDSA, SHA256withDSA, NONEwithECDSA, 
        SHA1withECDSA, SHA224withECDSA, SHA256withECDSA, SHA384withECDSA, 
        SHA512withECDSA
    }
     
    /** Do not instantiate.*/
    private DigitalSignature(){}
     
    /**************************************************************************
     *                                SIGNERS                                 *
     **************************************************************************/
    /**
     * Signs the message using the {@code privateKey} and returns the signature
     * as a {@code byte[]}.
     * @param privateKey    {@link PrivateKey}
     * @param message       message to sign
     * @return              signature as a {@code byte[]}
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws SignatureException {@link SignatureException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static byte[] sign(PrivateKey privateKey, String message) 
            throws NoSuchAlgorithmException, InvalidKeyException, 
            SignatureException, UnsupportedEncodingException {
        return sign(DEFAULT_ALGO, privateKey, message);
    }
     
    /**
     * Signs the message using the {@code algorithm} and {@code privateKey},
     * returning the signature as a {@code byte[]}.
     * @param algorithm     {@link Algorithms}
     * @param privateKey    {@link PrivateKey}
     * @param message       message to sign
     * @return              signature as a {@code byte[]}
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws SignatureException {@link SignatureException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static byte[] sign(Algorithms algorithm, PrivateKey privateKey, 
            String message) throws NoSuchAlgorithmException, InvalidKeyException, 
        SignatureException, UnsupportedEncodingException {
        Signature s = Signature.getInstance(algorithm.name());
        s.initSign(privateKey);
        s.update(message.getBytes(CHARSET));
        return s.sign();
    }
     
    /**************************************************************************
     *                               VERIFIERS                                *
     **************************************************************************/
    /**
     * Verifies the message using the {@code publicKey}.
     * @param publicKey {@link PublicKey}
     * @param signature signature
     * @param message   message to verify
     * @return          {@code true} if valid, {@code false} otherwise
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws SignatureException {@link SignatureException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static boolean verify(PublicKey publicKey, byte[] signature, 
            String message) throws NoSuchAlgorithmException, SignatureException, 
            InvalidKeyException, UnsupportedEncodingException {
        return verify(DEFAULT_ALGO, publicKey, signature, message);
    }
     
    /**
     * Verifies the message using the {@code algorithm} and {@code publicKey}.
     * @param algorithm {@link Algorithms}
     * @param publicKey {@link PublicKey}
     * @param signature signature
     * @param message   message to verify
     * @return          {@code true} if valid, {@code false} otherwise
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws SignatureException {@link SignatureException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static boolean verify(Algorithms algorithm, PublicKey publicKey, 
            byte[] signature, String message) throws NoSuchAlgorithmException, 
            SignatureException, InvalidKeyException, 
            UnsupportedEncodingException {
        Signature s = Signature.getInstance(algorithm.name());
        s.initVerify(publicKey);
        s.update(message.getBytes(CHARSET));
        return s.verify(signature);
    }
}

The DigitalSignature class requires a key-pair generator for asymmetric encryption. The following is said implementation.

import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
 
/**
 * Key pair generator class.
 * @author Ray Hylock
 */
public class KeyPairGenerator {
    // defaults
    private static final Algorithms DEFAULT_GEN = Algorithms.RSA;
     
    /** 
     * Key pair generator algorithms: 
     * <ol>
     * <li>{@code DiffieHellman} - key size must be a multiple of 64 between 512 and 2048, default 2048</li>
     * <li>{@code DSA} - key size must be a multiple of 64 between 512 and 2048, default 2048</li>
     * <li>{@code EC} - key size must be between 112 and 571, default 256</li>
     * <li>{@code RSA} (default) - key size must be between 512 16384, default 4096</li> 
     * </ol>
     */
    public static enum Algorithms { 
        DiffieHellman(2048), DSA(2048), EC(256), RSA(4096);
        int defaultSize;
        private Algorithms(int defaultSize) { this.defaultSize = defaultSize; }
    }
     
    // key pair
    private final java.security.KeyPairGenerator keyGen;
    private final KeyPair pair;
     
    /**
     * Constructor.
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     */
    public KeyPairGenerator() throws NoSuchAlgorithmException {
        this(DEFAULT_GEN, DEFAULT_GEN.defaultSize);
    }
     
    /**
     * Constructor.
     * @param generatorAlgorithm {@link Algorithms} for generating pair
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     */
    public KeyPairGenerator(Algorithms generatorAlgorithm) 
            throws NoSuchAlgorithmException {
        this(generatorAlgorithm, generatorAlgorithm.defaultSize);
    }
     
    /**
     * Constructor.
     * @param keySize key size, see {@link Algorithms} for sizes
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     */
    public KeyPairGenerator(int keySize) throws NoSuchAlgorithmException {
        this(DEFAULT_GEN, keySize);
    }
     
    /**
     * Constructor. 
     * @param generatorAlgorithm    {@link Algorithms} for generating pairs
     * @param keySize               key size, see {@link Algorithms} for sizes
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     */
    public KeyPairGenerator(Algorithms generatorAlgorithm, int keySize) 
            throws NoSuchAlgorithmException {
        // create key generator
        keyGen = java.security.KeyPairGenerator.getInstance(generatorAlgorithm.name());
         
        // initilize key generator with secure random
        keyGen.initialize(keySize, new SecureRandom());
         
        // generate the pair
        pair = keyGen.generateKeyPair();
    }
     
    /**
     * Returns the private key.
     * @return the private key
     */
    public PrivateKey getPrivateKey(){
        return pair.getPrivate();
    }
     
    /**
     * Returns the private key as a {@code byte[]}.
     * @return the private key as a {@code byte[]}
     */
    public byte[] getPrivateKeyEncoded(){
        return pair.getPrivate().getEncoded();
    }
     
    /**
     * Returns the public key.
     * @return the public key
     */
    public PublicKey getPublicKey(){
        return pair.getPublic();
    }
     
    /**
     * Returns the public key as a {@code byte[]}.
     * @return the public key as a {@code byte[]}
     */
    public byte[] getPublicKeyEncoded(){
        return pair.getPublic().getEncoded();
    }
}

The following is an example implementation of the DigitalSignature class that (1) signs a message using RSA1024 and verifies it, (2) modifies the message like an attacker would (without resigning), which fails to verify, (3) signs a message using EC256 and verifies it, and (4) modifies the message like an attacker would (without resigning), which fails to verify.

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
public class DigitalSignatureTest {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, 
            SignatureException, UnsupportedEncodingException, NoSuchProviderException {
        // message
        String message = "This is a test message";
         
        ///// RSA /////
        KeyPairGenerator keyGen = new KeyPairGenerator(KeyPairGenerator.Algorithms.RSA, 1024);
         
        // SHA512withRSA - verify == true
        byte[] sig = DigitalSignature.sign(keyGen.getPrivateKey(), message);
        boolean verify = DigitalSignature.verify(keyGen.getPublicKey(), sig, message);
        System.out.println("RSA message \""+message+"\" is "+((verify)?"valid":"invalid"));
         
        // SHA512withRSA with modified message - verify == false
        message += "asdf";
        verify = DigitalSignature.verify(keyGen.getPublicKey(), sig, message);
        System.out.println("RSA message \""+message+"\" is "+((verify)?"valid":"invalid"));
         
        ///// EC /////
        keyGen = new KeyPairGenerator(KeyPairGenerator.Algorithms.EC, 256);
         
        // EC - verify == true
        message = "This is a test message";
        sig = DigitalSignature.sign(DigitalSignature.Algorithms.SHA512withECDSA, 
                keyGen.getPrivateKey(), message);
        verify = DigitalSignature.verify(DigitalSignature.Algorithms.SHA512withECDSA, 
                keyGen.getPublicKey(), sig, message);
        System.out.println("EC message \""+message+"\" is "+((verify)?"valid":"invalid"));
         
        // EC with modified message - verify == false
        message += "asdf";
        verify = DigitalSignature.verify(DigitalSignature.Algorithms.SHA512withECDSA,
                keyGen.getPublicKey(), sig, message);
        System.out.println("EC message \""+message+"\" is "+((verify)?"valid":"invalid"));
    }
}

Output from the example class:

RSA message "This is a test message" is valid
RSA message "This is a test messageasdf" is invalid
EC message "This is a test message" is valid
EC message "This is a test messageasdf" is invalid