package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.ProviderException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import net.i2p.I2PAppContext;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.i2p.crypto.provider.I2PProvider;
import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.data.SimpleDataStructure;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.SystemVersion;
// main()
import net.i2p.data.DataHelper;
import net.i2p.data.Signature;
import net.i2p.util.Clock;
import net.i2p.util.RandomSource;
/** Define a way of generating asymmetrical key pairs as well as symmetrical keys
* @author jrandom
*/
public final class KeyGenerator {
private final I2PAppContext _context;
static {
I2PProvider.addProvider();
}
public KeyGenerator(I2PAppContext context) {
_context = context;
}
public static KeyGenerator getInstance() {
return I2PAppContext.getGlobalContext().keyGenerator();
}
/** Generate a private 256 bit session key
* @return session key
*/
public SessionKey generateSessionKey() {
// 256bit random # as a session key
SessionKey key = new SessionKey();
byte data[] = new byte[SessionKey.KEYSIZE_BYTES];
_context.random().nextBytes(data);
key.setData(data);
return key;
}
private static final int PBE_ROUNDS = 1000;
/**
* PBE the passphrase with the salt.
* Warning - SLOW
*/
public SessionKey generateSessionKey(byte salt[], byte passphrase[]) {
byte salted[] = new byte[16+passphrase.length];
System.arraycopy(salt, 0, salted, 0, Math.min(salt.length, 16));
System.arraycopy(passphrase, 0, salted, 16, passphrase.length);
byte h[] = _context.sha().calculateHash(salted).getData();
for (int i = 1; i < PBE_ROUNDS; i++)
_context.sha().calculateHash(h, 0, Hash.HASH_LENGTH, h, 0);
return new SessionKey(h);
}
/** standard exponent size */
private static final int PUBKEY_EXPONENT_SIZE_FULL = 2048;
/**
* short exponent size, which should be safe for use with the Oakley primes,
* per "On Diffie-Hellman Key Agreement with Short Exponents" - van Oorschot, Weiner
* at EuroCrypt 96, and crypto++'s benchmarks at http://www.eskimo.com/~weidai/benchmarks.html
* Also, "Koshiba & Kurosawa: Short Exponent Diffie-Hellman Problems" (PKC 2004, LNCS 2947, pp. 173-186)
* aparently supports this, according to
* http://groups.google.com/group/sci.crypt/browse_thread/thread/1855a5efa7416677/339fa2f945cc9ba0#339fa2f945cc9ba0
* (damn commercial access to http://www.springerlink.com/(xrkdvv45w0cmnur4aimsxx55)/app/home/contribution.asp?referrer=parent&backto=issue,13,31;journal,893,3280;linkingpublicationresults,1:105633,1 )
*/
private static final int PUBKEY_EXPONENT_SIZE_SHORT = 226;
/** @since 0.9.8 */
private static final boolean DEFAULT_USE_LONG_EXPONENT =
!SystemVersion.isSlow();
/**
* @deprecated use getElGamalExponentSize() which allows override in the properties
*/
@Deprecated
public static final int PUBKEY_EXPONENT_SIZE = DEFAULT_USE_LONG_EXPONENT ?
PUBKEY_EXPONENT_SIZE_FULL :
PUBKEY_EXPONENT_SIZE_SHORT;
private static final String PROP_LONG_EXPONENT = "crypto.elGamal.useLongKey";
/** @since 0.9.8 */
public boolean useLongElGamalExponent() {
return _context.getProperty(PROP_LONG_EXPONENT, DEFAULT_USE_LONG_EXPONENT);
}
/** @since 0.9.8 */
public int getElGamalExponentSize() {
return useLongElGamalExponent() ?
PUBKEY_EXPONENT_SIZE_FULL :
PUBKEY_EXPONENT_SIZE_SHORT;
}
/** Generate a pair of keys, where index 0 is a PublicKey, and
* index 1 is a PrivateKey
* @return pair of keys
*/
public Object[] generatePKIKeypair() {
return generatePKIKeys();
}
/**
* Same as above but different return type
* @since 0.8.7
*/
public SimpleDataStructure[] generatePKIKeys() {
BigInteger a = new NativeBigInteger(getElGamalExponentSize(), _context.random());
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
SimpleDataStructure[] keys = new SimpleDataStructure[2];
keys[0] = new PublicKey();
keys[1] = new PrivateKey();
// bigInteger.toByteArray returns SIGNED integers, but since they'return positive,
// signed two's complement is the same as unsigned
try {
keys[0].setData(SigUtil.rectify(aalpha, PublicKey.KEYSIZE_BYTES));
keys[1].setData(SigUtil.rectify(a, PrivateKey.KEYSIZE_BYTES));
} catch (InvalidKeyException ike) {
throw new IllegalArgumentException(ike);
}
return keys;
}
/** Convert a PrivateKey to its corresponding PublicKey
* @param priv PrivateKey object
* @return the corresponding PublicKey object
* @throws IllegalArgumentException on bad key
*/
public static PublicKey getPublicKey(PrivateKey priv) {
BigInteger a = new NativeBigInteger(1, priv.toByteArray());
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
PublicKey pub = new PublicKey();
try {
pub.setData(SigUtil.rectify(aalpha, PublicKey.KEYSIZE_BYTES));
} catch (InvalidKeyException ike) {
throw new IllegalArgumentException(ike);
}
return pub;
}
/** Generate a pair of DSA keys, where index 0 is a SigningPublicKey, and
* index 1 is a SigningPrivateKey.
* DSA-SHA1 only.
*
* @return pair of keys
*/
public Object[] generateSigningKeypair() {
return generateSigningKeys();
}
/**
* DSA-SHA1 only.
*
* Same as above but different return type
* @since 0.8.7
*/
public SimpleDataStructure[] generateSigningKeys() {
SimpleDataStructure[] keys = new SimpleDataStructure[2];
BigInteger x = null;
// make sure the random key is less than the DSA q and greater than zero
do {
x = new NativeBigInteger(160, _context.random());
} while (x.compareTo(CryptoConstants.dsaq) >= 0 || x.equals(BigInteger.ZERO));
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
keys[0] = new SigningPublicKey();
keys[1] = new SigningPrivateKey();
try {
keys[0].setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES));
keys[1].setData(SigUtil.rectify(x, SigningPrivateKey.KEYSIZE_BYTES));
} catch (InvalidKeyException ike) {
throw new IllegalStateException(ike);
}
return keys;
}
/**
* Generic signature type, supports DSA, ECDSA, EdDSA
* @since 0.9.9
*/
public SimpleDataStructure[] generateSigningKeys(SigType type) throws GeneralSecurityException {
if (type == SigType.DSA_SHA1)
return generateSigningKeys();
KeyPair kp;
if (type.getBaseAlgorithm() == SigAlgo.EdDSA) {
net.i2p.crypto.eddsa.KeyPairGenerator kpg = new net.i2p.crypto.eddsa.KeyPairGenerator();
kpg.initialize(type.getParams(), _context.random());
kp = kpg.generateKeyPair();
} else {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName());
try {
kpg.initialize(type.getParams(), _context.random());
kp = kpg.generateKeyPair();
} catch (ProviderException pe) {
// java.security.ProviderException: sun.security.pkcs11.wrapper.PKCS11Exception: CKR_DOMAIN_PARAMS_INVALID
// This is a RuntimeException, thx Sun
// Fails for P-192 only, on Ubuntu
Log log = _context.logManager().getLog(KeyGenerator.class);
String pname = kpg.getProvider().getName();
if ("BC".equals(pname)) {
if (log.shouldLog(Log.WARN))
log.warn("BC KPG failed for " + type, pe);
throw new GeneralSecurityException("BC KPG for " + type, pe);
}
if (!ECConstants.isBCAvailable())
throw new GeneralSecurityException(pname + " KPG failed for " + type, pe);
if (log.shouldLog(Log.WARN))
log.warn(pname + " KPG failed for " + type + ", trying BC" /* , pe */ );
try {
kpg = KeyPairGenerator.getInstance(type.getBaseAlgorithm().getName(), "BC");
kpg.initialize(type.getParams(), _context.random());
kp = kpg.generateKeyPair();
} catch (ProviderException pe2) {
if (log.shouldLog(Log.WARN))
log.warn("BC KPG failed for " + type + " also", pe2);
// throw original exception
throw new GeneralSecurityException(pname + " KPG for " + type, pe);
} catch (GeneralSecurityException gse) {
if (log.shouldLog(Log.WARN))
log.warn("BC KPG failed for " + type + " also", gse);
// throw original exception
throw new GeneralSecurityException(pname + " KPG for " + type, pe);
}
}
}
java.security.PublicKey pubkey = kp.getPublic();
java.security.PrivateKey privkey = kp.getPrivate();
SimpleDataStructure[] keys = new SimpleDataStructure[2];
keys[0] = SigUtil.fromJavaKey(pubkey, type);
keys[1] = SigUtil.fromJavaKey(privkey, type);
return keys;
}
/** Convert a SigningPrivateKey to a SigningPublicKey.
* As of 0.9.16, supports all key types.
*
* @param priv a SigningPrivateKey object
* @return a SigningPublicKey object
* @throws IllegalArgumentException on bad key or unknown type
*/
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
SigType type = priv.getType();
if (type == null)
throw new IllegalArgumentException("Unknown type");
try {
switch (type.getBaseAlgorithm()) {
case DSA:
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
SigningPublicKey pub = new SigningPublicKey();
pub.setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES));
return pub;
case EC:
ECPrivateKey ecpriv = SigUtil.toJavaECKey(priv);
BigInteger s = ecpriv.getS();
ECParameterSpec spec = (ECParameterSpec) type.getParams();
EllipticCurve curve = spec.getCurve();
ECPoint g = spec.getGenerator();
ECPoint w = ECUtil.scalarMult(g, s, curve);
ECPublicKeySpec ecks = new ECPublicKeySpec(w, ecpriv.getParams());
KeyFactory eckf = KeyFactory.getInstance("EC");
ECPublicKey ecpub = (ECPublicKey) eckf.generatePublic(ecks);
return SigUtil.fromJavaKey(ecpub, type);
case RSA:
RSAPrivateKey rsapriv = SigUtil.toJavaRSAKey(priv);
BigInteger exp = ((RSAKeyGenParameterSpec)type.getParams()).getPublicExponent();
RSAPublicKeySpec rsaks = new RSAPublicKeySpec(rsapriv.getModulus(), exp);
KeyFactory rsakf = KeyFactory.getInstance("RSA");
RSAPublicKey rsapub = (RSAPublicKey) rsakf.generatePublic(rsaks);
return SigUtil.fromJavaKey(rsapub, type);
case EdDSA:
EdDSAPrivateKey epriv = SigUtil.toJavaEdDSAKey(priv);
EdDSAPublicKey epub = new EdDSAPublicKey(new EdDSAPublicKeySpec(epriv.getA(), epriv.getParams()));
return SigUtil.fromJavaKey(epub, type);
default:
throw new IllegalArgumentException("Unsupported algorithm");
}
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException("Conversion failed", gse);
}
}
/**
* Usage: KeyGenerator [sigtype...]
*/
public static void main(String args[]) {
try {
main2(args);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
/**
* Usage: KeyGenerator [sigtype...]
*/
private static void main2(String args[]) {
RandomSource.getInstance().nextBoolean();
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
int runs = 200; // warmup
Collection<SigType> toTest;
if (args.length > 0) {
toTest = new ArrayList<SigType>();
for (int i = 0; i < args.length; i++) {
SigType type = SigType.parseSigType(args[i]);
if (type != null)
toTest.add(type);
else
System.out.println("Unknown type: " + args[i]);
}
if (toTest.isEmpty()) {
System.out.println("No types to test");
return;
}
} else {
toTest = Arrays.asList(SigType.values());
}
for (int j = 0; j < 2; j++) {
for (SigType type : toTest) {
if (!type.isAvailable()) {
System.out.println("Skipping unavailable: " + type);
continue;
}
try {
System.out.println("Testing " + type);
testSig(type, runs);
} catch (GeneralSecurityException e) {
System.out.println("error testing " + type);
e.printStackTrace();
}
}
runs = 1000;
}
}
private static void testSig(SigType type, int runs) throws GeneralSecurityException {
byte src[] = new byte[512];
double gtime = 0;
long stime = 0;
long vtime = 0;
SimpleDataStructure keys[] = null;
long st = System.nanoTime();
// RSA super slow, limit to 5
int genruns = (type.getBaseAlgorithm() == SigAlgo.RSA) ? Math.min(runs, 5) : runs;
for (int i = 0; i < genruns; i++) {
keys = KeyGenerator.getInstance().generateSigningKeys(type);
}
long en = System.nanoTime();
gtime = ((en - st) / (1000*1000d)) / genruns;
System.out.println(type + " key gen " + genruns + " times: " + gtime + " ms each");
SigningPublicKey pubkey = (SigningPublicKey) keys[0];
SigningPrivateKey privkey = (SigningPrivateKey) keys[1];
SigningPublicKey pubkey2 = getSigningPublicKey(privkey);
if (pubkey.equals(pubkey2))
System.out.println(type + " private-to-public test PASSED");
else
System.out.println(type + " private-to-public test FAILED");
//System.out.println("privkey " + keys[1]);
MessageDigest md = type.getDigestInstance();
for (int i = 0; i < runs; i++) {
RandomSource.getInstance().nextBytes(src);
md.update(src);
byte[] sha = md.digest();
SimpleDataStructure hash = type.getHashInstance();
hash.setData(sha);
long start = System.nanoTime();
Signature sig = DSAEngine.getInstance().sign(src, privkey);
Signature sig2 = DSAEngine.getInstance().sign(hash, privkey);
if (sig == null)
throw new GeneralSecurityException("signature generation failed");
if (sig2 == null)
throw new GeneralSecurityException("signature generation (H) failed");
long mid = System.nanoTime();
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, pubkey);
boolean ok2 = DSAEngine.getInstance().verifySignature(sig2, hash, pubkey);
long end = System.nanoTime();
stime += mid - start;
vtime += end - mid;
if (!ok)
throw new GeneralSecurityException(type + " V(S(data)) fail");
if (!ok2)
throw new GeneralSecurityException(type + " V(S(H(data))) fail");
}
stime /= 1000*1000;
vtime /= 1000*1000;
System.out.println(type + " sign/verify " + runs + " times: " + (vtime+stime) + " ms = " +
(((double) stime) / runs) + " each sign, " +
(((double) vtime) / runs) + " each verify, " +
(((double) (stime + vtime)) / runs) + " s+v");
}
/******
public static void main(String args[]) {
Log log = new Log("keygenTest");
RandomSource.getInstance().nextBoolean();
byte src[] = new byte[200];
RandomSource.getInstance().nextBytes(src);
I2PAppContext ctx = new I2PAppContext();
long time = 0;
for (int i = 0; i < 10; i++) {
long start = Clock.getInstance().now();
Object keys[] = KeyGenerator.getInstance().generatePKIKeypair();
long end = Clock.getInstance().now();
byte ctext[] = ctx.elGamalEngine().encrypt(src, (PublicKey) keys[0]);
byte ptext[] = ctx.elGamalEngine().decrypt(ctext, (PrivateKey) keys[1]);
time += end - start;
if (DataHelper.eq(ptext, src))
log.debug("D(E(data)) == data");
else
log.error("D(E(data)) != data!!!!!!");
}
log.info("Keygen 10 times: " + time + "ms");
Object obj[] = KeyGenerator.getInstance().generateSigningKeypair();
SigningPublicKey fake = (SigningPublicKey) obj[0];
time = 0;
for (int i = 0; i < 10; i++) {
long start = Clock.getInstance().now();
Object keys[] = KeyGenerator.getInstance().generateSigningKeypair();
long end = Clock.getInstance().now();
Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey) keys[1]);
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey) keys[0]);
boolean fakeOk = DSAEngine.getInstance().verifySignature(sig, src, fake);
time += end - start;
log.debug("V(S(data)) == " + ok + " fake verify correctly failed? " + (fakeOk == false));
}
log.info("Signing Keygen 10 times: " + time + "ms");
time = 0;
for (int i = 0; i < 1000; i++) {
long start = Clock.getInstance().now();
KeyGenerator.getInstance().generateSessionKey();
long end = Clock.getInstance().now();
time += end - start;
}
log.info("Session keygen 1000 times: " + time + "ms");
try {
Thread.sleep(5000);
} catch (InterruptedException ie) { // nop
}
}
******/
}