/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.crypt;
import java.math.BigInteger;
import java.util.Random;
import thaw.core.Logger;
import net.i2p.util.NativeBigInteger;
/**
* Implements the Digital Signature Algorithm (DSA) described in FIPS-186
*/
public class DSA {
// FIXME DSAgroupBigA is 256 bits long and therefore cannot accomodate
// all SHA-256 output's. Therefore we chop it down to 255 bits.
static final BigInteger SIGNATURE_MASK =
Util.TWO.pow(255).subtract(BigInteger.ONE);
/**
* Returns a DSA signature given a group, private key (x), a random nonce
* (k), and the hash of the message (m).
*/
static DSASignature sign(DSAGroup g,
DSAPrivateKey x,
BigInteger k,
BigInteger m,
RandomSource random) {
if(k.signum() == -1) throw new IllegalArgumentException();
if(m.signum() == -1) throw new IllegalArgumentException();
if(g.getQ().bitLength() == 256)
m = m.and(SIGNATURE_MASK);
if(m.compareTo(g.getQ()) != -1)
throw new IllegalArgumentException();
BigInteger r=g.getG().modPow(k, g.getP()).mod(g.getQ());
BigInteger kInv=k.modInverse(g.getQ());
return sign(g, x, r, kInv, m, random);
}
public static DSASignature sign(DSAGroup g, DSAPrivateKey x, BigInteger m,
RandomSource r) {
BigInteger k = DSA.generateK(g, r);
return sign(g, x, k, m, r);
}
/**
* Returns a DSA signature given a group, private key (x),
* the precalculated values of r and k^-1, and the hash
* of the message (m)
*/
static DSASignature sign(DSAGroup g, DSAPrivateKey x,
BigInteger r, BigInteger kInv,
BigInteger m, RandomSource random) {
BigInteger s1=m.add(x.getX().multiply(r)).mod(g.getQ());
BigInteger s=kInv.multiply(s1).mod(g.getQ());
if((r.compareTo(BigInteger.ZERO) == 0) || (s.compareTo(BigInteger.ZERO) == 0)) {
Logger.notice(DSA.class, "DSA : R or S equals 0 : Weird behaviour detected, please report if seen too often.");
return sign(g, x, r, generateK(g, random), m, random);
}
return new DSASignature(r,s);
}
private static BigInteger generateK(DSAGroup g, Random r){
if(g.getQ().bitLength() < DSAGroup.Q_BIT_LENGTH)
throw new IllegalArgumentException("Q is too short! (" + g.getQ().bitLength() + '<' + DSAGroup.Q_BIT_LENGTH + ')');
BigInteger k;
do {
k=new NativeBigInteger(DSAGroup.Q_BIT_LENGTH, r);
} while ((g.getQ().compareTo(k) < 1) || (k.compareTo(BigInteger.ZERO) == 0));
return k;
}
/**
* Verifies the message authenticity given a group, the public key
* (y), a signature, and the hash of the message (m).
* @param forceMod If enabled, skip the clipping m to 255 bits.
*/
public static boolean verify(DSAPublicKey kp,
DSASignature sig,
BigInteger m, boolean forceMod) {
if(m.signum() == -1) throw new IllegalArgumentException();
if(kp.getGroup().getQ().bitLength() == 256 && !forceMod)
m = m.and(SIGNATURE_MASK);
try {
// 0<r<q has to be true
if((sig.getR().compareTo(BigInteger.ZERO) < 1) || (kp.getQ().compareTo(sig.getR()) < 1)) {
Logger.debug(DSA.class, "DSA : r < 0 || r > q");
return false;
}
// 0<s<q has to be true as well
if((sig.getS().compareTo(BigInteger.ZERO) < 1) || (kp.getQ().compareTo(sig.getS()) < 1)) {
Logger.debug(DSA.class, "DSA : s < 0 || s > q");
return false;
}
BigInteger w=sig.getS().modInverse(kp.getQ());
BigInteger u1=m.multiply(w).mod(kp.getQ());
BigInteger u2=sig.getR().multiply(w).mod(kp.getQ());
BigInteger v1=kp.getG().modPow(u1, kp.getP());
BigInteger v2=kp.getY().modPow(u2, kp.getP());
BigInteger v=v1.multiply(v2).mod(kp.getP()).mod(kp.getQ());
return v.equals(sig.getR());
//FIXME: is there a better way to handle this exception raised on the 'w=' line above?
} catch (ArithmeticException e) { // catch error raised by invalid data
Logger.notice(DSA.class, "DSA : Verify failed: "+e.toString());
e.printStackTrace();
return false; // and report that that data is bad.
}
}
public static void main(String[] args) throws Exception {
//DSAGroup g=DSAGroup.readFromField(args[0]);
DSAGroup g = Global.DSAgroupBigA;
//Yarrow y=new Yarrow();
DummyRandomSource y = new DummyRandomSource();
DSAPrivateKey pk=new DSAPrivateKey(g, y);
DSAPublicKey pub=new DSAPublicKey(g, pk);
DSASignature sig=sign(g, pk, BigInteger.ZERO, y);
System.err.println(verify(pub, sig, BigInteger.ZERO, false));
while(true) {
long totalTimeSigning = 0;
long totalTimeVerifying = 0;
long totalRSize = 0;
long totalSSize = 0;
long totalPubKeySize = 0;
long totalPrivKeySize = 0;
int maxPrivKeySize = 0;
int maxPubKeySize = 0;
int maxRSize = 0;
int maxSSize = 0;
int totalRUnsignedBitSize = 0;
int maxRUnsignedBitSize = 0;
Random r = new Random(y.nextLong());
byte[] msg = new byte[32];
for(int i=0;i<1000;i++) {
r.nextBytes(msg);
BigInteger m = new BigInteger(1, msg);
pk = new DSAPrivateKey(g, r);
int privKeySize = pk.asBytes().length;
totalPrivKeySize += privKeySize;
if(privKeySize > maxPrivKeySize) maxPrivKeySize = privKeySize;
pub = new DSAPublicKey(g, pk);
int pubKeySize = pub.asBytes().length;
totalPubKeySize += pubKeySize;
if(pubKeySize > maxPubKeySize) maxPubKeySize = pubKeySize;
long t1 = System.currentTimeMillis();
sig = sign(g, pk, m, y);
long t2 = System.currentTimeMillis();
if(!verify(pub, sig, m, false)) {
System.err.println("Failed to verify!");
}
long t3 = System.currentTimeMillis();
totalTimeSigning += (t2 - t1);
totalTimeVerifying += (t3 - t2);
int rSize = sig.getR().bitLength();
rSize = (rSize / 8) + (rSize % 8 == 0 ? 0 : 1);
totalRSize += rSize;
if(rSize > maxRSize) maxRSize = rSize;
int rUnsignedBitSize = sig.getR().bitLength();
totalRUnsignedBitSize += rUnsignedBitSize;
maxRUnsignedBitSize = Math.max(maxRUnsignedBitSize, rUnsignedBitSize);
int sSize = sig.getS().bitLength();
sSize = sSize / 8 + (sSize % 8 == 0 ? 0 : 1);
totalSSize += sSize;
if(sSize > maxSSize) maxSSize = sSize;
}
System.out.println("Total time signing: "+totalTimeSigning);
System.out.println("Total time verifying: "+totalTimeVerifying);
System.out.println("Total R size: "+totalRSize+" (max "+maxRSize+ ')');
System.out.println("Total S size: "+totalSSize+" (max "+maxSSize+ ')');
System.out.println("Total R unsigned bitsize: "+totalRUnsignedBitSize);
System.out.println("Total pub key size: "+totalPubKeySize+" (max "+maxPubKeySize+ ')');
System.out.println("Total priv key size: "+totalPrivKeySize+" (max "+maxPrivKeySize+ ')');
}
}
}