I2P Address: [http://git.idk.i2p]

Skip to content
Snippets Groups Projects
Commit d1e42233 authored by zzz's avatar zzz
Browse files

* DSAEngine: Add code for alternate implementation using Java libs;

                 disabled by default. Add test code to verify identical results
                 and compare speed.
parent d308d7da
No related branches found
No related tags found
No related merge requests found
...@@ -29,10 +29,18 @@ package net.i2p.crypto; ...@@ -29,10 +29,18 @@ package net.i2p.crypto;
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.KeySpec;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.Hash; import net.i2p.data.Hash;
...@@ -44,26 +52,67 @@ import net.i2p.util.Log; ...@@ -44,26 +52,67 @@ import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger; import net.i2p.util.NativeBigInteger;
/** /**
* Sign and verify using DSA-SHA1.
* Also contains methods to sign and verify using a SHA-256 Hash, used by Syndie only.
*
* The primary implementation is code from TheCryto.
* As of 0.8.7, also included is an alternate implementation using java.security libraries, which
* is slightly slower. This implementation could in the future be easily modified
* to use a new signing algorithm from java.security when we change the signing algorithm.
*
* Params and rv's changed from Hash to SHA1Hash for version 0.8.1 * Params and rv's changed from Hash to SHA1Hash for version 0.8.1
* Hash variants of sign() and verifySignature() restored in 0.8.3, required by Syndie. * Hash variants of sign() and verifySignature() restored in 0.8.3, required by Syndie.
*/ */
public class DSAEngine { public class DSAEngine {
private Log _log; private final Log _log;
private I2PAppContext _context; private final I2PAppContext _context;
//private static final boolean _isAndroid = System.getProperty("java.vendor").contains("Android");
private static final boolean _useJavaLibs = false; // = _isAndroid;
public DSAEngine(I2PAppContext context) { public DSAEngine(I2PAppContext context) {
_log = context.logManager().getLog(DSAEngine.class); _log = context.logManager().getLog(DSAEngine.class);
_context = context; _context = context;
} }
public static DSAEngine getInstance() { public static DSAEngine getInstance() {
return I2PAppContext.getGlobalContext().dsa(); return I2PAppContext.getGlobalContext().dsa();
} }
/**
* Verify using DSA-SHA1.
* Uses TheCrypto code unless configured to use the java.security libraries.
*/
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) { public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
return verifySignature(signature, signedData, 0, signedData.length, verifyingKey); boolean rv;
if (_useJavaLibs) {
try {
rv = altVerifySigSHA1(signature, signedData, verifyingKey);
if ((!rv) && _log.shouldLog(Log.WARN))
_log.warn("Lib Verify Fail, sig =\n" + signature + "\npubkey =\n" + verifyingKey);
return rv;
} catch (GeneralSecurityException gse) {
if (_log.shouldLog(Log.WARN))
_log.warn("Lib Verify Fail, sig =\n" + signature + "\npubkey =\n" + verifyingKey, gse);
// now try TheCrypto
}
}
rv = verifySignature(signature, signedData, 0, signedData.length, verifyingKey);
if ((!rv) && _log.shouldLog(Log.WARN))
_log.warn("TheCrypto Verify Fail, sig =\n" + signature + "\npubkey =\n" + verifyingKey);
return rv;
} }
/**
* Verify using DSA-SHA1
*/
public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) { public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) {
return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey); return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey);
} }
/**
* Verify using DSA-SHA1
*/
public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) { public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) {
return verifySignature(signature, calculateHash(in), verifyingKey); return verifySignature(signature, calculateHash(in), verifyingKey);
} }
...@@ -92,6 +141,8 @@ public class DSAEngine { ...@@ -92,6 +141,8 @@ public class DSAEngine {
byte[] sigbytes = signature.getData(); byte[] sigbytes = signature.getData();
byte rbytes[] = new byte[20]; byte rbytes[] = new byte[20];
byte sbytes[] = new byte[20]; byte sbytes[] = new byte[20];
//System.arraycopy(sigbytes, 0, rbytes, 0, 20);
//System.arraycopy(sigbytes, 20, sbytes, 0, 20);
for (int x = 0; x < 40; x++) { for (int x = 0; x < 40; x++) {
if (x < 20) { if (x < 20) {
rbytes[x] = sigbytes[x]; rbytes[x] = sigbytes[x];
...@@ -99,6 +150,7 @@ public class DSAEngine { ...@@ -99,6 +150,7 @@ public class DSAEngine {
sbytes[x - 20] = sigbytes[x]; sbytes[x - 20] = sigbytes[x];
} }
} }
BigInteger s = new NativeBigInteger(1, sbytes); BigInteger s = new NativeBigInteger(1, sbytes);
BigInteger r = new NativeBigInteger(1, rbytes); BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData()); BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
...@@ -106,6 +158,7 @@ public class DSAEngine { ...@@ -106,6 +158,7 @@ public class DSAEngine {
try { try {
w = s.modInverse(CryptoConstants.dsaq); w = s.modInverse(CryptoConstants.dsaq);
} catch (ArithmeticException ae) { } catch (ArithmeticException ae) {
_log.warn("modInverse() error", ae);
return false; return false;
} }
byte data[] = hash.getData(); byte data[] = hash.getData();
...@@ -130,15 +183,36 @@ public class DSAEngine { ...@@ -130,15 +183,36 @@ public class DSAEngine {
} }
} }
/**
* Sign using DSA-SHA1.
* Uses TheCrypto code unless configured to use the java.security libraries.
*/
public Signature sign(byte data[], SigningPrivateKey signingKey) { public Signature sign(byte data[], SigningPrivateKey signingKey) {
if (_useJavaLibs) {
try {
return altSignSHA1(data, signingKey);
} catch (GeneralSecurityException gse) {
if (_log.shouldLog(Log.WARN))
_log.warn("Lib Sign Fail, privkey = " + signingKey, gse);
// now try TheCrypto
}
}
return sign(data, 0, data.length, signingKey); return sign(data, 0, data.length, signingKey);
} }
/**
* Sign using DSA-SHA1
*/
public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) { public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) {
if ((signingKey == null) || (data == null) || (data.length <= 0)) return null; if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
SHA1Hash h = calculateHash(data, offset, length); SHA1Hash h = calculateHash(data, offset, length);
return sign(h, signingKey); return sign(h, signingKey);
} }
/**
* Sign using DSA-SHA1.
* Reads the stream until EOF. Does not close the stream.
*/
public Signature sign(InputStream in, SigningPrivateKey signingKey) { public Signature sign(InputStream in, SigningPrivateKey signingKey) {
if ((signingKey == null) || (in == null) ) return null; if ((signingKey == null) || (in == null) ) return null;
SHA1Hash h = calculateHash(in); SHA1Hash h = calculateHash(in);
...@@ -192,28 +266,34 @@ public class DSAEngine { ...@@ -192,28 +266,34 @@ public class DSAEngine {
_context.random().harvester().feedEntropy("DSA.sign", rbytes, 0, rbytes.length); _context.random().harvester().feedEntropy("DSA.sign", rbytes, 0, rbytes.length);
if (rbytes.length == 20) { if (rbytes.length == 20) {
//System.arraycopy(rbytes, 0, out, 0, 20);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
out[i] = rbytes[i]; out[i] = rbytes[i];
} }
} else if (rbytes.length == 21) { } else if (rbytes.length == 21) {
//System.arraycopy(rbytes, 1, out, 0, 20);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
out[i] = rbytes[i + 1]; out[i] = rbytes[i + 1];
} }
} else { } else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short rbytes.length [" + rbytes.length + "]"); if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short rbytes.length [" + rbytes.length + "]");
//System.arraycopy(rbytes, 0, out, 20 - rbytes.length, rbytes.length);
for (int i = 0; i < rbytes.length; i++) for (int i = 0; i < rbytes.length; i++)
out[i + 20 - rbytes.length] = rbytes[i]; out[i + 20 - rbytes.length] = rbytes[i];
} }
if (sbytes.length == 20) { if (sbytes.length == 20) {
//System.arraycopy(sbytes, 0, out, 20, 20);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
out[i + 20] = sbytes[i]; out[i + 20] = sbytes[i];
} }
} else if (sbytes.length == 21) { } else if (sbytes.length == 21) {
//System.arraycopy(sbytes, 1, out, 20, 20);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
out[i + 20] = sbytes[i + 1]; out[i + 20] = sbytes[i + 1];
} }
} else { } else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short sbytes.length [" + sbytes.length + "]"); if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short sbytes.length [" + sbytes.length + "]");
//System.arraycopy(sbytes, 0, out, 40 - sbytes.length, sbytes.length);
for (int i = 0; i < sbytes.length; i++) for (int i = 0; i < sbytes.length; i++)
out[i + 20 + 20 - sbytes.length] = sbytes[i]; out[i + 20 + 20 - sbytes.length] = sbytes[i];
} }
...@@ -227,7 +307,11 @@ public class DSAEngine { ...@@ -227,7 +307,11 @@ public class DSAEngine {
return sig; return sig;
} }
/** @return hash SHA-1 hash, NOT a SHA-256 hash */ /**
* Reads the stream until EOF. Does not close the stream.
*
* @return hash SHA-1 hash, NOT a SHA-256 hash
*/
public SHA1Hash calculateHash(InputStream in) { public SHA1Hash calculateHash(InputStream in) {
MessageDigest digest = SHA1.getInstance(); MessageDigest digest = SHA1.getInstance();
byte buf[] = new byte[64]; byte buf[] = new byte[64];
...@@ -252,18 +336,222 @@ public class DSAEngine { ...@@ -252,18 +336,222 @@ public class DSAEngine {
return new SHA1Hash(digested); return new SHA1Hash(digested);
} }
/**
* Alternate to verifySignature() using java.security libraries.
* @throws GeneralSecurityException if algorithm unvailable or on other errors
* @since 0.8.7
*/
private boolean altVerifySigSHA1(Signature signature, byte[] data, SigningPublicKey verifyingKey) throws GeneralSecurityException {
java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA");
KeyFactory keyFact = KeyFactory.getInstance("DSA");
// y p q g
KeySpec spec = new DSAPublicKeySpec(new NativeBigInteger(1, verifyingKey.getData()),
CryptoConstants.dsap,
CryptoConstants.dsaq,
CryptoConstants.dsag);
PublicKey pubKey = keyFact.generatePublic(spec);
jsig.initVerify(pubKey);
jsig.update(data);
boolean rv = jsig.verify(sigBytesToASN1(signature.getData()));
//if (!rv) {
// System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(signature.getData()));
// System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(sigBytesToASN1(signature.getData())));
//}
return rv;
}
/**
* Alternate to sign() using java.security libraries.
* @throws GeneralSecurityException if algorithm unvailable or on other errors
* @since 0.8.7
*/
private Signature altSignSHA1(byte[] data, SigningPrivateKey privateKey) throws GeneralSecurityException {
java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA");
KeyFactory keyFact = KeyFactory.getInstance("DSA");
// y p q g
KeySpec spec = new DSAPrivateKeySpec(new NativeBigInteger(1, privateKey.getData()),
CryptoConstants.dsap,
CryptoConstants.dsaq,
CryptoConstants.dsag);
PrivateKey privKey = keyFact.generatePrivate(spec);
jsig.initSign(privKey, _context.random());
jsig.update(data);
return new Signature(aSN1ToSigBytes(jsig.sign()));
}
/**
* http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html
* Signature Format ASN.1 sequence of two INTEGER values: r and s, in that order:
* SEQUENCE ::= { r INTEGER, s INTEGER }
*
* http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
* 30 -- tag indicating SEQUENCE
* xx - length in octets
*
* 02 -- tag indicating INTEGER
* xx - length in octets
* xxxxxx - value
*
* Convert to BigInteger and back so we have the minimum length representation, as required.
* r and s are always non-negative.
*
* @since 0.8.7
*/
private static byte[] sigBytesToASN1(byte[] sig) {
//System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig));
ByteArrayOutputStream baos = new ByteArrayOutputStream(48);
baos.write(0x30);
baos.write(0); // length to be filled in below
byte[] tmp = new byte[20];
baos.write(2);
System.arraycopy(sig, 0, tmp, 0, 20);
BigInteger r = new BigInteger(1, tmp);
byte[] b = r.toByteArray();
baos.write(b.length);
baos.write(b, 0, b.length);
baos.write(2);
System.arraycopy(sig, 20, tmp, 0, 20);
BigInteger s = new BigInteger(1, tmp);
b = s.toByteArray();
baos.write(b.length);
baos.write(b, 0, b.length);
byte[] rv = baos.toByteArray();
rv[1] = (byte) (rv.length - 2);
//System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv));
return rv;
}
/**
* See above.
* @since 0.8.7
*/
private static byte[] aSN1ToSigBytes(byte[] asn) {
//System.out.println("pre from asn1\n" + net.i2p.util.HexDump.dump(asn));
byte[] rv = new byte[40];
int rlen = asn[3];
if ((asn[4] & 0x80) != 0)
throw new IllegalArgumentException("R is negative");
if (rlen > 21)
throw new IllegalArgumentException("R too big " + rlen);
else if (rlen == 21) {
System.arraycopy(asn, 5, rv, 0, 20);
} else
System.arraycopy(asn, 4, rv, 20 - rlen, rlen);
int slenloc = 25 + rlen - 20;
int slen = asn[slenloc];
if ((asn[slenloc + 1] & 0x80) != 0)
throw new IllegalArgumentException("S is negative");
if (slen > 21)
throw new IllegalArgumentException("S too big " + slen);
else if (slen == 21) {
System.arraycopy(asn, slenloc + 2, rv, 20, 20);
} else
System.arraycopy(asn, slenloc + 1, rv, 40 - slen, slen);
//System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv));
return rv;
}
private static final int RUNS = 1000;
/**
* Run consistency and speed tests with both TheCrypto and java.security libraries.
*
* TheCrypto is about 5-15% faster than java.security.
*/
public static void main(String args[]) { public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext(); I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[4096]; byte data[] = new byte[1024];
// warmump
ctx.random().nextBytes(data); ctx.random().nextBytes(data);
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
try { try {
for (int i = 0; i < 10; i++) { Thread.sleep(1000);
Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]); } catch (InterruptedException ie) {}
boolean ok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]); SimpleDataStructure keys[] = null;
System.out.println("OK: " + ok);
System.err.println("100 runs with new data and keys each time");
for (int i = 0; i < 100; i++) {
ctx.random().nextBytes(data);
keys = ctx.keyGenerator().generateSigningKeys();
Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
Signature jsig = null;
try {
jsig = ctx.dsa().altSignSHA1(data, (SigningPrivateKey)keys[1]);
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
} }
} catch (Exception e) { e.printStackTrace(); } boolean ok = ctx.dsa().verifySignature(jsig, data, (SigningPublicKey)keys[0]);
ctx.random().saveSeed(); boolean usok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
} boolean jok = false;
try {
jok = ctx.dsa().altVerifySigSHA1(sig, data, (SigningPublicKey)keys[0]);
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
}
boolean jjok = false;;
try {
jjok = ctx.dsa().altVerifySigSHA1(jsig, data, (SigningPublicKey)keys[0]);
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
}
System.err.println("TC->TC OK: " + usok + " JL->TC OK: " + ok + " TC->JK OK: " + jok + " JL->JL OK: " + jjok);
if (!(ok && usok && jok && jjok)) {
System.out.println("privkey\n" + net.i2p.util.HexDump.dump(keys[1].getData()));
return;
}
}
System.err.println("Starting speed test");
long start = System.currentTimeMillis();
for (int i = 0; i < RUNS; i++) {
Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
boolean ok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
if (!ok) {
System.err.println("TheCrypto FAIL");
return;
}
}
long time = System.currentTimeMillis() - start;
System.err.println("Time for " + RUNS + " DSA sign/verifies:");
System.err.println("TheCrypto time (ms): " + time);
start = System.currentTimeMillis();
for (int i = 0; i < RUNS; i++) {
boolean ok = false;
try {
Signature jsig = ctx.dsa().altSignSHA1(data, (SigningPrivateKey)keys[1]);
ok = ctx.dsa().altVerifySigSHA1(jsig, data, (SigningPublicKey)keys[0]);
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
}
if (!ok) {
System.err.println("JavaLib FAIL");
return;
}
}
time = System.currentTimeMillis() - start;
System.err.println("JavaLib time (ms): " + time);
/**** yes, arraycopy is slower for 20 bytes
start = System.currentTimeMillis();
byte b[] = new byte[20];
for (int i = 0; i < 10000000; i++) {
data[0] = data[i % 256];
System.arraycopy(data, 0, b, 0, 20);
}
time = System.currentTimeMillis() - start;
System.err.println("arraycopy time (ms): " + time);
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
data[0] = data[i % 256];
for (int j = 0; j < 20; j++) {
b[j] = data[j];
}
}
time = System.currentTimeMillis() - start;
System.err.println("loop time (ms): " + time);
****/
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment