package net.i2p.crypto.elgamal;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Arrays;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SigUtil;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
/**
* ElG signatures with SHA-256
*
* ref: https://en.wikipedia.org/wiki/ElGamal_signature_scheme
*
* @since 0.9.25
*/
public final class ElGamalSigEngine extends Signature {
private final MessageDigest digest;
private ElGamalKey key;
/**
* No specific hash requested, allows any ElGamal key.
*/
public ElGamalSigEngine() {
this(SHA256Generator.getDigestInstance());
}
/**
* Specific hash requested, only matching keys will be allowed.
* @param digest the hash algorithm that keys must have to sign or verify.
*/
public ElGamalSigEngine(MessageDigest digest) {
super("ElGamal");
this.digest = digest;
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
digest.reset();
if (privateKey instanceof ElGamalPrivateKey) {
ElGamalPrivateKey privKey = (ElGamalPrivateKey) privateKey;
key = privKey;
} else {
throw new InvalidKeyException("cannot identify ElGamal private key: " + privateKey.getClass());
}
}
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
digest.reset();
if (publicKey instanceof ElGamalPublicKey) {
key = (ElGamalPublicKey) publicKey;
} else {
throw new InvalidKeyException("cannot identify ElGamal public key: " + publicKey.getClass());
}
}
@Override
protected void engineUpdate(byte b) throws SignatureException {
digest.update(b);
}
@Override
protected void engineUpdate(byte[] b, int off, int len)
throws SignatureException {
digest.update(b, off, len);
}
/**
* @return ASN.1 R,S
*/
@Override
protected byte[] engineSign() throws SignatureException {
BigInteger elgp = key.getParams().getP();
BigInteger pm1 = elgp.subtract(BigInteger.ONE);
BigInteger elgg = key.getParams().getG();
BigInteger x = ((ElGamalPrivateKey) key).getX();
if (!(x instanceof NativeBigInteger))
x = new NativeBigInteger(x);
byte[] data = digest.digest();
BigInteger k;
boolean ok;
do {
k = new BigInteger(2048, RandomSource.getInstance());
ok = k.compareTo(pm1) == -1;
ok = ok && k.compareTo(BigInteger.ONE) == 1;
ok = ok && k.gcd(pm1).equals(BigInteger.ONE);
} while (!ok);
BigInteger r = elgg.modPow(k, elgp);
BigInteger kinv = k.modInverse(pm1);
BigInteger h = new NativeBigInteger(1, data);
BigInteger s = (kinv.multiply(h.subtract(x.multiply(r)))).mod(pm1);
// todo if s == 0 go around again
byte[] rv;
try {
rv = SigUtil.sigBytesToASN1(r, s);
} catch (IllegalArgumentException iae) {
throw new SignatureException("ASN1", iae);
}
return rv;
}
/**
* @param sigBytes ASN.1 R,S
*/
@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
BigInteger elgp = key.getParams().getP();
BigInteger pm1 = elgp.subtract(BigInteger.ONE);
BigInteger elgg = key.getParams().getG();
BigInteger y = ((ElGamalPublicKey) key).getY();
if (!(y instanceof NativeBigInteger))
y = new NativeBigInteger(y);
byte[] data = digest.digest();
try {
BigInteger[] rs = SigUtil.aSN1ToBigInteger(sigBytes, 256);
BigInteger r = rs[0];
BigInteger s = rs[1];
if (r.signum() != 1 || s.signum() != 1 ||
r.compareTo(elgp) != -1 || s.compareTo(pm1) != -1)
return false;
NativeBigInteger h = new NativeBigInteger(1, data);
BigInteger modvalr = r.modPow(s, elgp);
BigInteger modvaly = y.modPow(r, elgp);
BigInteger modmulval = modvalr.multiply(modvaly).mod(elgp);
BigInteger v = elgg.modPow(h, elgp);
boolean ok = v.compareTo(modmulval) == 0;
return ok;
} catch (RuntimeException e) {
throw new SignatureException("verify", e);
}
}
/**
* @deprecated replaced with <a href="#engineSetParameter(java.security.spec.AlgorithmParameterSpec)">this</a>
*/
@Override
protected void engineSetParameter(String param, Object value) {
throw new UnsupportedOperationException("engineSetParameter unsupported");
}
/**
* @deprecated
*/
@Override
protected Object engineGetParameter(String param) {
throw new UnsupportedOperationException("engineSetParameter unsupported");
}
}