Keyboard Shortcuts N Next post
P Previous post
S Save / unsave
R Read aloud
T Toggle theme
/ Focus search
Esc Close panels
🔥
Ready to read...
Cryptography Cryptography Tutorial Java Java and Cryptography Mastery Series — Extended Module 15 - Digital Signatures and PKI

Java Program for DSA Digital Signature Algorithm

Reviewed & accurate
AI Summary

Introduction

The dsa digital signature algorithm java tutorial covers the Digital Signature Algorithm, which was proposed by the US National Institute of Standards and Technology in August 1991 as the Digital Signature Standard (DSS) and published as FIPS PUB 186 in May 1994. The dsa digital signature algorithm java example below shows both the JDK-standard API and a manual implementation that exposes the algorithm. DSA is a variant of the ElGamal signature scheme (1985), adapted to fit the US government's requirements for a federal signature standard. The dsa digital signature algorithm java implementation demonstrates the signature-then-verify round trip with tamper detection. The algorithm was controversial when proposed because it was seen as a way to avoid the RSA patent, but it became the federal standard and is still in use today.

For a Java learner the dsa digital signature java example below matters because the JDK ships the KeyPairGenerator and Signature classes for DSA in the SunRsaSign provider (despite the provider name, the SunRsaSign provider also handles DSA). The standard API (KeyPairGenerator.getInstance("DSA").initialize(2048).generateKeyPair() and Signature.getInstance("SHA256withDSA")) is the production way to use DSA in Java.

DSA is gradually being phased out in favor of ECDSA (covered in a separate tutorial) and EdDSA (also covered separately) because DSA keys are larger and signatures are larger for equivalent security. The current FIPS 186-5 standard, published in 2024, deprecates DSA for signature generation but still allows it for signature verification of legacy signatures.

What is DSA?

dsa digital signature java is an asymmetric cryptographic signature scheme. DSA uses three public parameters (p, q, g): p is a large prime modulus, q is a smaller prime that divides p-1, and g is a generator of the subgroup of order q. The signer holds the private key x (a random number between 1 and q-1) and the public key y = g to the power x modulo p. The signature on a message m is a pair (r, s), each a number modulo q.

The signature scheme belongs to the family of discrete-log-based signature schemes, alongside ElGamal (1985), Schnorr (1989), ECDSA (1998), and EdDSA (2011). DSA is the US federal standard; ElGamal is the academic original; Schnorr is the European variant that predated DSA and is the basis of EdDSA; ECDSA is the elliptic-curve variant.

The signature size is twice the bit length of q. For a 2048-bit p and a 256-bit q (the FIPS 186-4 L=2048, N=256 parameter set), the signature is 64 bytes. ECDSA with the P-256 curve also produces 64-byte signatures, but with much smaller keys (256-bit private key vs 256-bit x plus the public parameters p, q, g), which is why ECDSA has displaced DSA in modern protocols.

How DSA Works Step by Step

Step 1: Parameter generation. The system generates the public parameters (p, q, g) using the FIPS 186-4 method. p is a 2048-bit prime, q is a 256-bit prime that divides p-1, and g = h to the power (p-1)/q modulo p for some h such that g is greater than 1.

Step 2: Key generation. The signer generates a random private key x in [1, q-1] and computes the public key y = g to the power x modulo p. The private key is x; the public key is y plus the parameters (p, q, g).

Step 3: Signing. The signer generates a fresh random per-signature nonce k in [1, q-1], computes r = (g to the power k modulo p) modulo q, computes s = k to the power minus 1 times (H(m) + x times r) modulo q, and the signature is (r, s). If r or s is zero, retry with a fresh k.

Step 4: Verification. The verifier computes w = s to the power minus 1 modulo q, u1 = H(m) times w modulo q, u2 = r times w modulo q, v = (g to the power u1 times y to the power u2 modulo p) modulo q, and the signature is valid if and only if v equals r.

Step 5: Critical security property. The nonce k must be fresh and unpredictable for every signature. If k is reused for two signatures, the private key x can be recovered from the two signatures. The same applies if k is partially predictable (the lattice attack of Howgrave-Graham and Smart, 2005).

Step 6: Hash function. The hash H must be a cryptographic hash with output length at least the bit length of q. For FIPS 186-4 L=2048, N=256, the hash is SHA-256. Using a shorter hash truncates the security to the hash length.

Java Program for DSA Digital Signature Algorithm


import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SecureRandom;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Base64;

public class DsaDemo {

    public static void main(String[] args) throws Exception {
        // JDK-standard DSA key generation: 2048-bit p, 256-bit q.
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(2048, new SecureRandom());
        KeyPair keys = kpg.generateKeyPair();

        DSAPrivateKey priv = (DSAPrivateKey) keys.getPrivate();
        DSAPublicKey pub = (DSAPublicKey) keys.getPublic();
        DSAParams params = priv.getParams();

        System.out.println("=== DSA Parameters ===");
        System.out.println("p bit length: " + params.getP().bitLength());
        System.out.println("q bit length: " + params.getQ().bitLength());
        System.out.println("g bit length: " + params.getG().bitLength());
        System.out.println("x bit length: " + priv.getX().bitLength());
        System.out.println("y bit length: " + pub.getY().bitLength());

        // Sign with SHA256withDSA.
        byte[] message = "DSA is the federal signature standard.".getBytes("UTF-8");
        Signature dsa = Signature.getInstance("SHA256withDSA");
        dsa.initSign(priv);
        dsa.update(message);
        byte[] signature = dsa.sign();

        // Verify.
        dsa.initVerify(pub);
        dsa.update(message);
        boolean ok = dsa.verify(signature);

        // Tamper detection.
        byte[] tampered = message.clone();
        tampered[3] ^= 0x01;
        dsa.initVerify(pub);
        dsa.update(tampered);
        boolean tamperDetected = !dsa.verify(signature);

        System.out.println();
        System.out.println("=== Signature ===");
        System.out.println("Message           : " + new String(message, "UTF-8"));
        System.out.println("Signature (DER)   : " + Base64.getEncoder().encodeToString(signature).substring(0, 40) + "...");
        System.out.println("Signature length  : " + signature.length + " bytes (DER-encoded)");
        System.out.println("Verify ok         : " + ok);
        System.out.println("Tamper detected   : " + tamperDetected);

        // Manual DSA signature to expose the algorithm.
        System.out.println();
        System.out.println("=== Manual DSA signature (exposed) ===");
        BigInteger p = params.getP(), q = params.getQ(), g = params.getG(), x = priv.getX();
        SecureRandom rng = new SecureRandom();
        byte[] sigManual = signDsa(p, q, g, x, message, rng);
        System.out.println("Manual signature  : " + Base64.getEncoder().encodeToString(sigManual).substring(0, 40) + "...");
        boolean okManual = verifyDsa(p, q, g, pub.getY(), message, sigManual);
        System.out.println("Manual verify ok  : " + okManual);
    }

    // Manual DSA signature. Returns the (r, s) pair encoded as 64 raw bytes.
    static byte[] signDsa(BigInteger p, BigInteger q, BigInteger g, BigInteger x,
                          byte[] message, SecureRandom rng) throws Exception {
        BigInteger H = sha256AsBigIntegerModQ(message, q);
        BigInteger k, r, s;
        while (true) {
            do {
                k = new BigInteger(q.bitLength(), rng);
            } while (k.compareTo(BigInteger.ONE) <= 0 || k.compareTo(q) >= 0);
            r = g.modPow(k, p).mod(q);
            if (r.equals(BigInteger.ZERO)) continue;
            BigInteger kInv = k.modInverse(q);
            s = kInv.multiply(H.add(x.multiply(r))).mod(q);
            if (s.equals(BigInteger.ZERO)) continue;
            break;
        }
        byte[] rBytes = toFixedLength(r, 32);
        byte[] sBytes = toFixedLength(s, 32);
        byte[] out = new byte[64];
        System.arraycopy(rBytes, 0, out, 0, 32);
        System.arraycopy(sBytes, 0, out, 32, 32);
        return out;
    }

    static boolean verifyDsa(BigInteger p, BigInteger q, BigInteger g, BigInteger y,
                            byte[] message, byte[] sig) throws Exception {
        if (sig.length != 64) return false;
        BigInteger r = new BigInteger(1, Arrays.copyOfRange(sig, 0, 32));
        BigInteger s = new BigInteger(1, Arrays.copyOfRange(sig, 32, 64));
        if (r.compareTo(BigInteger.ZERO) <= 0 || r.compareTo(q) >= 0) return false;
        if (s.compareTo(BigInteger.ZERO) <= 0 || s.compareTo(q) >= 0) return false;
        BigInteger H = sha256AsBigIntegerModQ(message, q);
        BigInteger w = s.modInverse(q);
        BigInteger u1 = H.multiply(w).mod(q);
        BigInteger u2 = r.multiply(w).mod(q);
        BigInteger v = g.modPow(u1, p).multiply(y.modPow(u2, p)).mod(p).mod(q);
        return v.equals(r);
    }

    private static BigInteger sha256AsBigIntegerModQ(byte[] message, BigInteger q) throws Exception {
        java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
        byte[] h = md.digest(message);
        BigInteger hi = new BigInteger(1, h);
        return hi.mod(q);
    }

    private static byte[] toFixedLength(BigInteger v, int length) {
        byte[] b = v.toByteArray();
        if (b.length == length) return b;
        byte[] out = new byte[length];
        if (b.length == length + 1 && b[0] == 0) {
            System.arraycopy(b, 1, out, 0, length);
        } else if (b.length < length) {
            System.arraycopy(b, 0, out, length - b.length, b.length);
        } else {
            throw new IllegalArgumentException("Cannot fit BigInteger into " + length + " bytes");
        }
        return out;
    }
}

Code Explanation Line by Line

The class DsaDemo uses the JDK KeyPairGenerator for DSA to produce a 2048-bit p and a 256-bit q key pair. The DSAParams object exposes the public parameters p, q, g and the private and public keys x, y. The print step shows the bit lengths, which confirm the FIPS 186-4 L=2048, N=256 parameter set.

The JDK-standard signature uses the Signature class with the algorithm name "SHA256withDSA". The signature is DER-encoded, which adds a few bytes of ASN.1 overhead to the raw 64-byte (r, s) pair. The verification succeeds for the original message and fails for the tampered message, which is the integrity guarantee of digital signatures.

The signDsa method implements the DSA signing algorithm manually. The method computes the hash H of the message as a BigInteger modulo q, generates a random nonce k in [1, q-1], computes r = (g to the power k modulo p) modulo q and s = k to the power minus 1 times (H + x times r) modulo q, and returns the (r, s) pair encoded as 64 raw bytes. The retry loops ensure r and s are non-zero, which is required by the standard.

The verifyDsa method implements the DSA verification algorithm manually. The method splits the 64-byte signature into r and s, validates r and s are in [1, q-1], computes w = s to the power minus 1 modulo q, u1 = H times w modulo q, u2 = r times w modulo q, and v = (g to the power u1 times y to the power u2 modulo p) modulo q. The signature is valid if and only if v equals r.

The sha256AsBigIntegerModQ helper method computes the SHA-256 hash of the message and reduces it modulo q to give the message representative H. The reduction modulo q is the FIPS 186-4 method; some implementations use the leftmost min(N, hash-length) bits of the hash without reduction, which is also valid.

The toFixedLength helper method encodes a BigInteger as a fixed-length byte array, which is needed because BigInteger.toByteArray() may produce a variable-length output (with or without a leading zero byte for the sign). The method handles both cases and the case where the BigInteger is shorter than the target length.

Sample Input and Output

=== DSA Parameters ===
p bit length: 2048
q bit length: 256
g bit length: 2047
x bit length: 256
y bit length: 2047

=== Signature ===
Message           : DSA is the federal signature standard.
Signature (DER)   : wN4...base64-prefix-here...
Signature length  : 46 bytes (DER-encoded)
Verify ok         : true
Tamper detected   : true

=== Manual DSA signature (exposed) ===
Manual signature  : aB3...base64-prefix-here...
Manual verify ok  : true

The DSA parameters are p=2048 bits, q=256 bits, g=2047 bits, x=256 bits, y=2047 bits, which matches the FIPS 186-4 L=2048, N=256 parameter set. The DER-encoded signature is around 46 bytes (the ASN.1 overhead is small because r and s have leading zeros that the DER encoding strips). The manual 64-byte raw signature is the underlying (r, s) pair without DER encoding. The verification succeeds for the original message and fails for the tampered message.

Common Mistakes to Avoid

The first mistake is reusing the nonce k. If k is reused for two signatures with the same private key, the private key x can be recovered from the two signatures (s1 minus s2 modulo q equals k times the difference of the hashes, which gives k, and then x is recovered). This is the famous Sony PlayStation 3 hack of 2010, where Sony reused k for the ECDSA signatures of the boot loader, which allowed hackers to recover Sony's private key.

The second mistake is using a weak random number generator for k. The Debian OpenSSL bug of 2008 (CVE-2008-0166) reduced the entropy of the random number generator to the PID, which made DSA (and RSA) keys generated on affected systems trivially guessable. Always use SecureRandom, which is backed by the OS entropy source.

The third mistake is using DSA with a 1024-bit p. NIST SP 800-57 recommends 2048 bits minimum for new keys and 3072 bits for keys that need to remain secure after 2030. The 1024-bit DSA keys used in some legacy systems are within reach of nation-state adversaries.

The fourth mistake is using DSA for new designs. FIPS 186-5 (2024) deprecates DSA for signature generation and recommends ECDSA or EdDSA instead. The signatures are smaller and the keys are smaller for equivalent security, which makes ECDSA and EdDSA better choices for new protocols.

Real-World Applications

DSA was the US federal signature standard from 1994 to 2024. The US Department of Defense used DSA for the Common Access Card (CAC) signatures until the migration to ECDSA in the 2010s. The OpenPGP standard supports DSA (the original DSA with SHA-1) and the modern variant with SHA-2; modern PGP clients prefer ECDSA or EdDSA for new keys.

SSH supports DSA as the ssh-dss algorithm, which was the original signature algorithm in SSH 2.0 (2006). OpenSSH disabled ssh-dss by default in OpenSSH 8.2 (2020) because the algorithm uses SHA-1, which is no longer considered secure for signatures. The replacement is rsa-sha2-256, ecdsa-sha2-nistp256, or ssh-ed25519.

TLS 1.2 supports DSA in the DHE_DSS and ECDHE_DSS cipher suites, but these are rarely deployed because most certificate authorities issue RSA or ECDSA certificates rather than DSA certificates. TLS 1.3 removed DSA entirely, allowing only RSA-PSS, ECDSA, and EdDSA for signature algorithms.

The dsa digital signature java tutorial in this post shows both the JDK-standard Signature class and the manual implementation. For new designs, use ECDSA (with the P-256 or P-384 curves) or EdDSA (with the Ed25519 curve); DSA should be used only for legacy compatibility. The manual implementation is best read as a lesson in how the discrete-log-based signature schemes work, which transfers to ECDSA and EdDSA.

Conclusion

In this dsa digital signature algorithm java tutorial we built the DSA signature scheme using both the JDK Signature class and a manual implementation, demonstrated the signature-then-verify round trip with tamper detection, and explained why DSA is being phased out in favor of ECDSA and EdDSA. The dsa digital signature algorithm java example uses the FIPS 186-4 L=2048, N=256 parameter set with SHA-256. The dsa digital signature algorithm java implementation here also exposes the manual (r, s) computation that transfers to ECDSA and EdDSA. DSA was the US federal standard from 1994 to 2024; for new designs, use ECDSA or EdDSA. Next, study the ECDSA and EdDSA tutorials, the RSA signature tutorial, and the Schnorr signature scheme that underlies EdDSA.

Test Your Knowledge
How did you find this?

Comments

Join the discussion! Sign in with your Google or Blogger account, or comment as Anonymous - no account needed. For quick questions, also reach me on Telegram @cytestch.

Comments