2

I am trying to implement the El Gamal digital signature scheme, using the BigInteger class for generating large prime numbers. Samantha generates the public key, the private key, chooses a message, signs it and then Victor verifies the signature.

Problem: The output always says that the signature has not been verified, i.e. the verification algorithm returns false upon every execution, which randomizes the numbers yet again. However, when using small, constant numbers for testing I get the correct result.

Question: Where am I doing something wrong? I can't seem to get to a conclusion.

My code so far:

ElGamal_test - method used for the precomputation step and testing

public static void ElGamal_test(){

    // Samantha picks q, a 1024 bit prime and computes p = 2q + 1
    Random rng = new SecureRandom();
    BigInteger q = BigInteger.probablePrime(1024, rng);
    BigInteger p = q.multiply(BigInteger.valueOf(2)).add(BigInteger.ONE);
    //  BigInteger p = BigInteger.valueOf(467);

    // Samantha computes g, a primitive root modulo p
    BigInteger g;

    while(true){
        g = new BigInteger(1024, rng);
        if(g.compareTo(BigInteger.ONE) > 0 &&
           g.compareTo(p) < 0 &&
           !(g.multiply(g).mod(p).equals(BigInteger.ONE)) &&
           !(g.modPow(q, p).equals(BigInteger.ONE)))
            break;
    }
    //  g = BigInteger.valueOf(2);

    // Samantha computes her private key
    BigInteger s;

    while(true){
        s = new BigInteger(1024, rng);
        if(s.compareTo(BigInteger.ONE) > 0 &&
           s.compareTo(p.subtract(BigInteger.ONE)) < 0)
            break;
    }
    //  s = BigInteger.valueOf(127);

    // Samantha computes her public key
    BigInteger v = g.modPow(s, p);

    // Samantha chooses her message, m
    BigInteger m = new BigInteger("100");

    // Samantha signs her message
    BigInteger[] key = Cryptography.ElGamalSignature(p, g, s, m);

    // Victor verifies the signature
    boolean result = Cryptography.ElGamalVerification(p, g, v, m, key);

    String str = (result == true) ? "The signature has been verified" : "The signature has not been verified";
    System.out.println(str);
}

ElGamalSignature - method used for the signing algorithm

public static BigInteger[] ElGamalSignature(BigInteger prime, BigInteger generator, BigInteger privExp, BigInteger doc){

    BigInteger[] signature = new BigInteger[2];
    Random rng = new SecureRandom();

    // Samantha picks the ephemeral key
    BigInteger e;
    while(true){
        e = new BigInteger(1024, rng);
        if(e.compareTo(BigInteger.ONE) > 0 &&
           e.compareTo(prime.subtract(BigInteger.ONE)) < 0 &&
           e.gcd(prime.subtract(BigInteger.ONE)).equals(BigInteger.ONE))
            break;
    }
    //  e = BigInteger.valueOf(213);

    // Samantha computes the signature
    signature[0] = generator.modPow(e, prime);
    signature[1] = (doc.subtract(privExp.multiply(signature[0])))
                 .multiply(e.modInverse(prime.subtract(BigInteger.ONE)))
                 .mod(prime.subtract(BigInteger.ONE));
    return signature;
}

ElGamalVerification - method used for the verification algorithm

public static boolean ElGamalVerification(BigInteger prime, BigInteger generator, BigInteger publicExp, BigInteger doc, BigInteger[] key){

    BigInteger part1 = (publicExp.modPow(key[0], prime).multiply(key[0].modPow(key[1], prime))).mod(prime);
    BigInteger part2 = generator.modPow(doc, prime);

    if(part1.equals(part2))
        return true;
    else
        return false;
}
Bogdan Kandra
  • 150
  • 1
  • 14
  • 3
    **ElGamal requires p prime;** your generation algorithm produces p prime with very low probability (about 0.3% by the prime number theorem). Aside: it's conventional to make p a 'round' size like 1024 or 2048 bit, and for a safe prime p=2q+1 q is one bit smaller (1023 or 2047 bits). This has no meaningful effect on functionality or security, it just makes operating on the data slightly more convenient and efficient. – dave_thompson_085 Oct 31 '17 at 00:31
  • 1
    I honestly don't understand why I have such a low probability. I have used the algorithm presented in the first answer here: https://crypto.stackexchange.com/questions/820/how-does-one-calculate-a-primitive-root-for-diffie-hellman the fourth paragraph. Could you give me some specific details? – Bogdan Kandra Oct 31 '17 at 21:19
  • Too much for comments; answered. – dave_thompson_085 Nov 01 '17 at 07:42

1 Answers1

2

Expanded from comment; TLDR your p is not prime.

You cite https://crypto.stackexchange.com/questions/820/how-does-one-calculate-a-primitive-root-for-diffie-hellman which is about calculating the generator g, not p and q, for DH.

The 4th para of Thomas Pornin's answer considers a DH group with prime p=2q+1 with q also prime. For DH in this case it is sufficient if the generator has order q or 2q, but it must not have very small order (2 or 1). As he says only 1 has order 1 and only p-1 has order 2, so any other group element can be used as a generator and usually 2 is for convenience.

OTOH Jus12's answer addresses the question as (wrongly) stated, namely finding a primitive root when p=2k+1 with k prime (using k instead of q for the Sophie-Germain prime) where primitive means it has order 2k. To do that requires testing candidates in 2..p-1 (which you code as > 1 and < p) to find one whose order is not 2 or k (your q) and that is what your code does. Unlike DH, ElGamal does require a primitive root, so this is the correct way of finding g given valid p and q.

Both of those answers assume you already have p=2q+1 with q and p prime. They do not find p and q. Your algorithm starts by finding q prime (probablistically but that's good enough) and then just computing p=2q+1. But you don't verify if p=2q+1 is prime -- and it usually isn't. Even though p in this math notation means prime, Java doesn't automatically make a variable named p prime. And when p is not prime, ElGamal doesn't work.

You need to either generate q prime and verify p=2q+1 also prime, or generate p prime and verify q=(p-1)/2 also prime.

dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • Oh my god, I understand now. Of course I knew that Java doesn't automatically make a variable named p to be prime (duh), just that I didn't realize that p = 2 * q + 1 won't be prime, most of the time. But it doesn't surprise me, now that you've said it. Thanks! – Bogdan Kandra Nov 01 '17 at 13:43
  • One more question though; what would be the probability that if q is prime, p = 2 * q + 1 is also prime? How would I go about calculating this? – Bogdan Kandra Nov 01 '17 at 13:57
  • 1
    @BogdanKandra: as far as I can tell, 2q+1 isn't special and the probability it is prime is the same as any randomly chosen odd integer of the same size, and by the prime number theorem (as I mentioned earlier) for 1025-bit (or 1024-bit) p this is about 1/350 or about 0.3%. – dave_thompson_085 Nov 02 '17 at 07:25
  • well... that's not very good. I've seen this anyway, my program can take as much as 30 seconds in some cases until they compute the signature. I should implement another algorithm for finding a primitive root, but I don't know how. – Bogdan Kandra Nov 02 '17 at 15:53