src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java
changeset 810 0ff0059f2ec3
child 895 b2e1b45382a4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java	Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,393 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.crypto;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHPrivateKeySpec;
+import javax.crypto.spec.DHPublicKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import net.java.otr4j.io.SerializationUtils;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.generators.DHKeyPairGenerator;
+import org.bouncycastle.crypto.modes.SICBlockCipher;
+import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * 
+ * @author George Politis
+ * 
+ */
+public class OtrCryptoEngineImpl implements OtrCryptoEngine {
+
+	public KeyPair generateDHKeyPair() throws OtrCryptoException {
+
+		// Generate a AsymmetricCipherKeyPair using BC.
+		DHParameters dhParams = new DHParameters(MODULUS, GENERATOR, null,
+				DH_PRIVATE_KEY_MINIMUM_BIT_LENGTH);
+		DHKeyGenerationParameters params = new DHKeyGenerationParameters(
+				new SecureRandom(), dhParams);
+		DHKeyPairGenerator kpGen = new DHKeyPairGenerator();
+
+		kpGen.init(params);
+		AsymmetricCipherKeyPair pair = kpGen.generateKeyPair();
+
+		// Convert this AsymmetricCipherKeyPair to a standard JCE KeyPair.
+		DHPublicKeyParameters pub = (DHPublicKeyParameters) pair.getPublic();
+		DHPrivateKeyParameters priv = (DHPrivateKeyParameters) pair
+				.getPrivate();
+
+		try {
+			KeyFactory keyFac = KeyFactory.getInstance("DH");
+
+			DHPublicKeySpec pubKeySpecs = new DHPublicKeySpec(pub.getY(),
+					MODULUS, GENERATOR);
+			DHPublicKey pubKey = (DHPublicKey) keyFac
+					.generatePublic(pubKeySpecs);
+
+			DHParameters dhParameters = priv.getParameters();
+			DHPrivateKeySpec privKeySpecs = new DHPrivateKeySpec(priv.getX(),
+					dhParameters.getP(), dhParameters.getG());
+			DHPrivateKey privKey = (DHPrivateKey) keyFac
+					.generatePrivate(privKeySpecs);
+
+			return new KeyPair(pubKey, privKey);
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+	}
+
+	public DHPublicKey getDHPublicKey(byte[] mpiBytes)
+			throws OtrCryptoException {
+		return getDHPublicKey(new BigInteger(mpiBytes));
+	}
+
+	public DHPublicKey getDHPublicKey(BigInteger mpi) throws OtrCryptoException {
+		DHPublicKeySpec pubKeySpecs = new DHPublicKeySpec(mpi, MODULUS,
+				GENERATOR);
+		try {
+			KeyFactory keyFac = KeyFactory.getInstance("DH");
+			return (DHPublicKey) keyFac.generatePublic(pubKeySpecs);
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+	}
+
+	public byte[] sha256Hmac(byte[] b, byte[] key) throws OtrCryptoException {
+		return this.sha256Hmac(b, key, 0);
+	}
+
+	public byte[] sha256Hmac(byte[] b, byte[] key, int length)
+			throws OtrCryptoException {
+
+		SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA256");
+		javax.crypto.Mac mac;
+		try {
+			mac = javax.crypto.Mac.getInstance("HmacSHA256");
+		} catch (NoSuchAlgorithmException e) {
+			throw new OtrCryptoException(e);
+		}
+		try {
+			mac.init(keyspec);
+		} catch (InvalidKeyException e) {
+			throw new OtrCryptoException(e);
+		}
+
+		byte[] macBytes = mac.doFinal(b);
+
+		if (length > 0) {
+			byte[] bytes = new byte[length];
+			ByteBuffer buff = ByteBuffer.wrap(macBytes);
+			buff.get(bytes);
+			return bytes;
+		} else {
+			return macBytes;
+		}
+	}
+
+	public byte[] sha1Hmac(byte[] b, byte[] key, int length)
+			throws OtrCryptoException {
+
+		try {
+			SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA1");
+			javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1");
+			mac.init(keyspec);
+
+			byte[] macBytes = mac.doFinal(b);
+
+			if (length > 0) {
+				byte[] bytes = new byte[length];
+				ByteBuffer buff = ByteBuffer.wrap(macBytes);
+				buff.get(bytes);
+				return bytes;
+			} else {
+				return macBytes;
+			}
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+	}
+
+	public byte[] sha256Hmac160(byte[] b, byte[] key) throws OtrCryptoException {
+		return sha256Hmac(b, key, 20);
+	}
+
+	public byte[] sha256Hash(byte[] b) throws OtrCryptoException {
+		try {
+			MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+			sha256.update(b, 0, b.length);
+			return sha256.digest();
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+	}
+
+	public byte[] sha1Hash(byte[] b) throws OtrCryptoException {
+		try {
+			MessageDigest sha256 = MessageDigest.getInstance("SHA-1");
+			sha256.update(b, 0, b.length);
+			return sha256.digest();
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+	}
+
+	public byte[] aesDecrypt(byte[] key, byte[] ctr, byte[] b)
+			throws OtrCryptoException {
+
+		AESFastEngine aesDec = new AESFastEngine();
+		SICBlockCipher sicAesDec = new SICBlockCipher(aesDec);
+		BufferedBlockCipher bufSicAesDec = new BufferedBlockCipher(sicAesDec);
+
+		// Create initial counter value 0.
+		if (ctr == null)
+			ctr = ZERO_CTR;
+		bufSicAesDec.init(false, new ParametersWithIV(new KeyParameter(key),
+				ctr));
+		byte[] aesOutLwDec = new byte[b.length];
+		int done = bufSicAesDec.processBytes(b, 0, b.length, aesOutLwDec, 0);
+		try {
+			bufSicAesDec.doFinal(aesOutLwDec, done);
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+
+		return aesOutLwDec;
+	}
+
+	public byte[] aesEncrypt(byte[] key, byte[] ctr, byte[] b)
+			throws OtrCryptoException {
+
+		AESFastEngine aesEnc = new AESFastEngine();
+		SICBlockCipher sicAesEnc = new SICBlockCipher(aesEnc);
+		BufferedBlockCipher bufSicAesEnc = new BufferedBlockCipher(sicAesEnc);
+
+		// Create initial counter value 0.
+		if (ctr == null)
+			ctr = ZERO_CTR;
+		bufSicAesEnc.init(true,
+				new ParametersWithIV(new KeyParameter(key), ctr));
+		byte[] aesOutLwEnc = new byte[b.length];
+		int done = bufSicAesEnc.processBytes(b, 0, b.length, aesOutLwEnc, 0);
+		try {
+			bufSicAesEnc.doFinal(aesOutLwEnc, done);
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+		return aesOutLwEnc;
+	}
+
+	public BigInteger generateSecret(PrivateKey privKey, PublicKey pubKey)
+			throws OtrCryptoException {
+		try {
+			KeyAgreement ka = KeyAgreement.getInstance("DH");
+			ka.init(privKey);
+			ka.doPhase(pubKey, true);
+			byte[] sb = ka.generateSecret();
+			BigInteger s = new BigInteger(1, sb);
+			return s;
+
+		} catch (Exception e) {
+			throw new OtrCryptoException(e);
+		}
+	}
+
+	public byte[] sign(byte[] b, PrivateKey privatekey)
+			throws OtrCryptoException {
+
+		if (!(privatekey instanceof DSAPrivateKey))
+			throw new IllegalArgumentException();
+
+		DSAParams dsaParams = ((DSAPrivateKey) privatekey).getParams();
+		DSAParameters bcDSAParameters = new DSAParameters(dsaParams.getP(),
+				dsaParams.getQ(), dsaParams.getG());
+
+		DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privatekey;
+		DSAPrivateKeyParameters bcDSAPrivateKeyParms = new DSAPrivateKeyParameters(
+				dsaPrivateKey.getX(), bcDSAParameters);
+
+		DSASigner dsaSigner = new DSASigner();
+		dsaSigner.init(true, bcDSAPrivateKeyParms);
+
+		BigInteger q = dsaParams.getQ();
+
+		// Ian: Note that if you can get the standard DSA implementation you're
+		// using to not hash its input, you should be able to pass it ((256-bit
+		// value) mod q), (rather than truncating the 256-bit value) and all
+		// should be well.
+		// ref: Interop problems with libotr - DSA signature
+		BigInteger bmpi = new BigInteger(1, b);
+		BigInteger[] rs = dsaSigner.generateSignature(BigIntegers
+				.asUnsignedByteArray(bmpi.mod(q)));
+
+		int siglen = q.bitLength() / 4;
+		int rslen = siglen / 2;
+		byte[] rb = BigIntegers.asUnsignedByteArray(rs[0]);
+		byte[] sb = BigIntegers.asUnsignedByteArray(rs[1]);
+
+		// Create the final signature array, padded with zeros if necessary.
+		byte[] sig = new byte[siglen];
+		Boolean writeR = false;
+		Boolean writeS = false;
+		for (int i = 0; i < siglen; i++) {
+			if (i < rslen) {
+				if (!writeR)
+					writeR = rb.length >= rslen - i;
+				sig[i] = (writeR) ? rb[i] : (byte) 0x0;
+			} else {
+				int j = i - rslen; // Rebase.
+				if (!writeS)
+					writeS = sb.length >= rslen - j;
+				sig[i] = (writeS) ? sb[j] : (byte) 0x0;
+			}
+		}
+		return sig;
+	}
+
+	public boolean verify(byte[] b, PublicKey pubKey, byte[] rs)
+			throws OtrCryptoException {
+
+		if (!(pubKey instanceof DSAPublicKey))
+			throw new IllegalArgumentException();
+
+		DSAParams dsaParams = ((DSAPublicKey) pubKey).getParams();
+		int qlen = dsaParams.getQ().bitLength() / 8;
+		ByteBuffer buff = ByteBuffer.wrap(rs);
+		byte[] r = new byte[qlen];
+		buff.get(r);
+		byte[] s = new byte[qlen];
+		buff.get(s);
+		return verify(b, pubKey, r, s);
+	}
+
+	private Boolean verify(byte[] b, PublicKey pubKey, byte[] r, byte[] s)
+			throws OtrCryptoException {
+		Boolean result = verify(b, pubKey, new BigInteger(1, r),
+				new BigInteger(1, s));
+		return result;
+	}
+
+	private Boolean verify(byte[] b, PublicKey pubKey, BigInteger r,
+			BigInteger s) throws OtrCryptoException {
+
+		if (!(pubKey instanceof DSAPublicKey))
+			throw new IllegalArgumentException();
+
+		DSAParams dsaParams = ((DSAPublicKey) pubKey).getParams();
+
+		BigInteger q = dsaParams.getQ();
+		DSAParameters bcDSAParams = new DSAParameters(dsaParams.getP(), q,
+				dsaParams.getG());
+
+		DSAPublicKey dsaPrivateKey = (DSAPublicKey) pubKey;
+		DSAPublicKeyParameters dsaPrivParms = new DSAPublicKeyParameters(
+				dsaPrivateKey.getY(), bcDSAParams);
+
+		// Ian: Note that if you can get the standard DSA implementation you're
+		// using to not hash its input, you should be able to pass it ((256-bit
+		// value) mod q), (rather than truncating the 256-bit value) and all
+		// should be well.
+		// ref: Interop problems with libotr - DSA signature
+		DSASigner dsaSigner = new DSASigner();
+		dsaSigner.init(false, dsaPrivParms);
+
+		BigInteger bmpi = new BigInteger(1, b);
+		Boolean result = dsaSigner.verifySignature(BigIntegers
+				.asUnsignedByteArray(bmpi.mod(q)), r, s);
+		return result;
+	}
+
+	public String getFingerprint(PublicKey pubKey) throws OtrCryptoException {
+		byte[] b;
+		try {
+			byte[] bRemotePubKey = SerializationUtils.writePublicKey(pubKey);
+
+			if (pubKey.getAlgorithm().equals("DSA")) {
+				byte[] trimmed = new byte[bRemotePubKey.length - 2];
+				System.arraycopy(bRemotePubKey, 2, trimmed, 0, trimmed.length);
+				b = new OtrCryptoEngineImpl().sha1Hash(trimmed);
+			} else
+				b = new OtrCryptoEngineImpl().sha1Hash(bRemotePubKey);
+		} catch (IOException e) {
+			throw new OtrCryptoException(e);
+		}
+		return this.byteArrayToHexString(b);
+	}
+
+	private String byteArrayToHexString(byte in[]) {
+		byte ch = 0x00;
+		int i = 0;
+		if (in == null || in.length <= 0)
+			return null;
+		String pseudo[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+				"A", "B", "C", "D", "E", "F" };
+		StringBuffer out = new StringBuffer(in.length * 2);
+		while (i < in.length) {
+			ch = (byte) (in[i] & 0xF0);
+			ch = (byte) (ch >>> 4);
+			ch = (byte) (ch & 0x0F);
+			out.append(pseudo[(int) ch]);
+			ch = (byte) (in[i] & 0x0F);
+			out.append(pseudo[(int) ch]);
+			i++;
+		}
+
+		String rslt = new String(out);
+		return rslt;
+
+	}
+}