First part: ElGamal encryption system, cracking it using CADO-NFS
All variable names as in Wikipedia's explanation of ElGamal signing scheme.
As in ElGamal encryption scheme, we reuse these parameters and keys:
g, p are public parameters. x, y are keys. x - private, y - public.
Let m be a message.
Generate 'ephemeral key', k. Must be coprime to p-1.
Compute \( r=g^k \pmod p \). This is how k is transmitted in 'obfuscated' way.
Now find such a s, so that the following equation would hold:
\[ m = xr + sk \pmod{p-1} \]m, r and s are published as message + signature.
Here, let's recall generalized version of Fermat little theorem. If g and p are coprimes and \( x=y \pmod p-1 \), then \( g^x=g^y \pmod p \).
Back to our equation. Verification step. Since g is coprime to p, due the theorem:
\[ m = xr + sk \pmod {p-1} \]... can be transformed to:
\[ g^m = g^{xr + sk} \pmod p \](g and p must be coprimes -- they are, since g is a primitive root of p.)
... and can be rewritten further to:
\[ (g^{x})^r (g^{k})^s \pmod p \] \[ (y)^r (r)^s \pmod p \]Verification step doesn't use x (private key), only y (public key). So anyone, having g, p, y can verify signature.
So the last verification step is:
if pow(g, m, p) == divmod(pow(y, r, p) * pow(r, s, p), p)[1]:
print ("verification passed")
Now let's back to finding "such a s".
Rewriting this:
\[ m = xr + sk \pmod {p-1} \]Would result in:
\[ \frac{m - xr}{k} \pmod {p-1} \]However we can't use division operation, we must use inverse modulo:
\[ (m - xr)k^{-1} \pmod {p-1} \]As in ElGamal encryption scheme, the problem of cracking is the problem of finding the 'ephemeral key' k. And again, to do this, the discrete logarithm is to be found.
Now a practical example. g/p parameters and key generation functions are the same as in the previous example.
Parameters:
p, g = (1123, 2)
Keys:
secret key = 108 full public key g, p, y = (2, 1123, 368)
The signing code:
#!/usr/bin/env python3
import math, random
# private key:
x=108
# g/p parameters and public key:
g, p, h = 2, 1123, 368
m=123
assert m>=0 and m<=p
# AKA ephemeral key
# random for each message
# must be coprime to $p-1$
while True:
k=random.randint(2, p-2)
if math.gcd(k, p-1)==1:
break
r=pow(g, k, p) # publish $k$ in 'obfuscated' form
s=divmod((m - x*r) * pow(k, -1, p-1), p-1)[1] # the same as (m - x*r) / k mod p-1
assert s!=0
print ("m, r, s=", m, r, s)
The verification code:
#!/usr/bin/env python3
import sys
# g/p parameters and public key:
g, p, y = 2, 1123, 368
m=int(sys.argv[1])
r=int(sys.argv[2])
s=int(sys.argv[3])
if pow(g, m, p) == divmod(pow(y, r, p) * pow(r, s, p), p)[1]:
print ("verification passed")
else:
print ("verification NOT passed")
The log:
% ./sign.py m, r, s= 123 107 159 % ./verify.py 123 107 159 verification passed

Yes, I know about these lousy Disqus ads. Please use adblocker. I would consider to subscribe to 'pro' version of Disqus if the signal/noise ratio in comments would be good enough.