diff -r fbd3585af53e -r 0ff0059f2ec3 src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java --- /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; + + } +}