mobile/android/base/sync/jpake/JPakeCrypto.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.sync.jpake;
michael@0 6
michael@0 7 import java.io.UnsupportedEncodingException;
michael@0 8 import java.math.BigInteger;
michael@0 9 import java.security.GeneralSecurityException;
michael@0 10 import java.security.InvalidKeyException;
michael@0 11 import java.security.MessageDigest;
michael@0 12 import java.security.NoSuchAlgorithmException;
michael@0 13
michael@0 14 import javax.crypto.Mac;
michael@0 15 import javax.crypto.spec.SecretKeySpec;
michael@0 16
michael@0 17 import org.mozilla.gecko.background.common.log.Logger;
michael@0 18 import org.mozilla.gecko.sync.crypto.HKDF;
michael@0 19 import org.mozilla.gecko.sync.crypto.KeyBundle;
michael@0 20
michael@0 21 public class JPakeCrypto {
michael@0 22 private static final String LOG_TAG = "JPakeCrypto";
michael@0 23
michael@0 24 /*
michael@0 25 * Primes P and Q, and generator G - from original Mozilla J-PAKE
michael@0 26 * implementation.
michael@0 27 */
michael@0 28 public static final BigInteger P = new BigInteger(
michael@0 29 "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" +
michael@0 30 "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" +
michael@0 31 "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" +
michael@0 32 "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" +
michael@0 33 "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" +
michael@0 34 "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" +
michael@0 35 "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" +
michael@0 36 "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" +
michael@0 37 "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" +
michael@0 38 "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" +
michael@0 39 "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" +
michael@0 40 "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73",
michael@0 41 16);
michael@0 42
michael@0 43 public static final BigInteger Q = new BigInteger(
michael@0 44 "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D",
michael@0 45 16);
michael@0 46
michael@0 47 public static final BigInteger G = new BigInteger(
michael@0 48 "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" +
michael@0 49 "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" +
michael@0 50 "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" +
michael@0 51 "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" +
michael@0 52 "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" +
michael@0 53 "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" +
michael@0 54 "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" +
michael@0 55 "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" +
michael@0 56 "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" +
michael@0 57 "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" +
michael@0 58 "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" +
michael@0 59 "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B",
michael@0 60 16);
michael@0 61
michael@0 62 /**
michael@0 63 *
michael@0 64 * Round 1 of J-PAKE protocol.
michael@0 65 * Generate x1, x2, and ZKP for other party.
michael@0 66 */
michael@0 67 public static void round1(JPakeParty jp, JPakeNumGenerator gen) throws NoSuchAlgorithmException, UnsupportedEncodingException {
michael@0 68 // Randomly select x1 from [0,q), x2 from [1,q).
michael@0 69 BigInteger x1 = gen.generateFromRange(Q); // [0, q)
michael@0 70 BigInteger x2 = jp.x2 = BigInteger.ONE.add(gen.generateFromRange(Q
michael@0 71 .subtract(BigInteger.ONE))); // [1, q)
michael@0 72
michael@0 73 BigInteger gx1 = G.modPow(x1, P);
michael@0 74 BigInteger gx2 = G.modPow(x2, P);
michael@0 75
michael@0 76 jp.gx1 = gx1;
michael@0 77 jp.gx2 = gx2;
michael@0 78
michael@0 79 // Generate and store zero knowledge proofs.
michael@0 80 jp.zkp1 = createZkp(G, x1, gx1, jp.signerId, gen);
michael@0 81 jp.zkp2 = createZkp(G, x2, gx2, jp.signerId, gen);
michael@0 82 }
michael@0 83
michael@0 84 /**
michael@0 85 * Round 2 of J-PAKE protocol.
michael@0 86 * Generate A and ZKP for A.
michael@0 87 * Verify ZKP from other party. Does not check for replay ZKP.
michael@0 88 */
michael@0 89 public static void round2(BigInteger secretValue, JPakeParty jp, JPakeNumGenerator gen)
michael@0 90 throws IncorrectZkpException, NoSuchAlgorithmException,
michael@0 91 Gx3OrGx4IsZeroOrOneException, UnsupportedEncodingException {
michael@0 92
michael@0 93 Logger.debug(LOG_TAG, "round2 started.");
michael@0 94
michael@0 95 // checkZkp does some additional checks, but we can throw a more informative exception here.
michael@0 96 if (BigInteger.ZERO.compareTo(jp.gx3) == 0 || BigInteger.ONE.compareTo(jp.gx3) == 0 ||
michael@0 97 BigInteger.ZERO.compareTo(jp.gx4) == 0 || BigInteger.ONE.compareTo(jp.gx4) == 0) {
michael@0 98 throw new Gx3OrGx4IsZeroOrOneException();
michael@0 99 }
michael@0 100
michael@0 101 // Check ZKP.
michael@0 102 checkZkp(G, jp.gx3, jp.zkp3);
michael@0 103 checkZkp(G, jp.gx4, jp.zkp4);
michael@0 104
michael@0 105 // Compute a = g^[(x1+x3+x4)*(x2*secret)].
michael@0 106 BigInteger y1 = jp.gx3.multiply(jp.gx4).mod(P).multiply(jp.gx1).mod(P);
michael@0 107 BigInteger y2 = jp.x2.multiply(secretValue).mod(P);
michael@0 108
michael@0 109 BigInteger a = y1.modPow(y2, P);
michael@0 110 jp.thisZkpA = createZkp(y1, y2, a, jp.signerId, gen);
michael@0 111 jp.thisA = a;
michael@0 112
michael@0 113 Logger.debug(LOG_TAG, "round2 finished.");
michael@0 114 }
michael@0 115
michael@0 116 /**
michael@0 117 * Final round of J-PAKE protocol.
michael@0 118 */
michael@0 119 public static KeyBundle finalRound(BigInteger secretValue, JPakeParty jp)
michael@0 120 throws IncorrectZkpException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
michael@0 121 Logger.debug(LOG_TAG, "Final round started.");
michael@0 122 BigInteger gb = jp.gx1.multiply(jp.gx2).mod(P).multiply(jp.gx3)
michael@0 123 .mod(P);
michael@0 124 checkZkp(gb, jp.otherA, jp.otherZkpA);
michael@0 125
michael@0 126 // Calculate shared key g^(x1+x3)*x2*x4*secret, which is equivalent to
michael@0 127 // (B/g^(x2*x4*s))^x2 = (B*(g^x4)^x2^s^-1)^2.
michael@0 128 BigInteger k = jp.gx4.modPow(jp.x2.multiply(secretValue).negate().mod(Q), P).multiply(jp.otherA)
michael@0 129 .modPow(jp.x2, P);
michael@0 130
michael@0 131 byte[] enc = new byte[32];
michael@0 132 byte[] hmac = new byte[32];
michael@0 133 generateKeyAndHmac(k, enc, hmac);
michael@0 134
michael@0 135 Logger.debug(LOG_TAG, "Final round finished; returning key.");
michael@0 136 return new KeyBundle(enc, hmac);
michael@0 137 }
michael@0 138
michael@0 139 // TODO Replace this function with the one in the crypto library
michael@0 140 private static byte[] HMACSHA256(byte[] data, byte[] key) {
michael@0 141 byte[] result = null;
michael@0 142 try {
michael@0 143 Mac hmacSha256;
michael@0 144 hmacSha256 = Mac.getInstance("HmacSHA256");
michael@0 145 SecretKeySpec secret_key = new SecretKeySpec(key, "HmacSHA256");
michael@0 146 hmacSha256.init(secret_key);
michael@0 147 result = hmacSha256.doFinal(data);
michael@0 148 } catch (GeneralSecurityException e) {
michael@0 149 Logger.error(LOG_TAG, "Got exception calculating HMAC.", e);
michael@0 150 }
michael@0 151 return result;
michael@0 152 }
michael@0 153
michael@0 154 /* Helper Methods */
michael@0 155
michael@0 156 /*
michael@0 157 * Generate the ZKP b = r - x*h, and g^r, where h = hash(g, g^r, g^x, id). (We
michael@0 158 * pass in gx to save on an exponentiation of g^x)
michael@0 159 */
michael@0 160 private static Zkp createZkp(BigInteger g, BigInteger x, BigInteger gx,
michael@0 161 String id, JPakeNumGenerator gen) throws NoSuchAlgorithmException, UnsupportedEncodingException {
michael@0 162 // Generate random r for exponent.
michael@0 163 BigInteger r = gen.generateFromRange(Q);
michael@0 164
michael@0 165 // Calculate g^r for ZKP.
michael@0 166 BigInteger gr = g.modPow(r, P);
michael@0 167
michael@0 168 // Calculate the ZKP b value = (r-x*h) % q.
michael@0 169 BigInteger h = computeBHash(g, gr, gx, id);
michael@0 170 Logger.debug(LOG_TAG, "myhash: " + h.toString(16));
michael@0 171
michael@0 172 // ZKP value = b = r-x*h
michael@0 173 BigInteger b = r.subtract(x.multiply(h)).mod(Q);
michael@0 174
michael@0 175 return new Zkp(gr, b, id);
michael@0 176 }
michael@0 177
michael@0 178 /*
michael@0 179 * Verify ZKP.
michael@0 180 */
michael@0 181 private static void checkZkp(BigInteger g, BigInteger gx, Zkp zkp)
michael@0 182 throws IncorrectZkpException, NoSuchAlgorithmException, UnsupportedEncodingException {
michael@0 183
michael@0 184 BigInteger h = computeBHash(g, zkp.gr, gx, zkp.id);
michael@0 185
michael@0 186 // Check parameters of zkp, and compare to computed hash. These shouldn't
michael@0 187 // fail.
michael@0 188 if (gx.compareTo(BigInteger.ONE) < 1) { // g^x > 1.
michael@0 189 Logger.error(LOG_TAG, "g^x > 1 fails.");
michael@0 190 throw new IncorrectZkpException();
michael@0 191 }
michael@0 192 if (gx.compareTo(P.subtract(BigInteger.ONE)) > -1) { // g^x < p-1
michael@0 193 Logger.error(LOG_TAG, "g^x < p-1 fails.");
michael@0 194 throw new IncorrectZkpException();
michael@0 195 }
michael@0 196 if (gx.modPow(Q, P).compareTo(BigInteger.ONE) != 0) {
michael@0 197 Logger.error(LOG_TAG, "g^x^q % p = 1 fails.");
michael@0 198 throw new IncorrectZkpException();
michael@0 199 }
michael@0 200 if (zkp.gr.compareTo(g.modPow(zkp.b, P).multiply(gx.modPow(h, P)).mod(P)) != 0) {
michael@0 201 // b = r-h*x ==> g^r = g^b*g^x^(h)
michael@0 202 Logger.debug(LOG_TAG, "gb*g(xh) = " + g.modPow(zkp.b, P).multiply(gx.modPow(h, P)).mod(P).toString(16));
michael@0 203 Logger.debug(LOG_TAG, "gr = " + zkp.gr.toString(16));
michael@0 204 Logger.debug(LOG_TAG, "b = " + zkp.b.toString(16));
michael@0 205 Logger.debug(LOG_TAG, "g^b = " + g.modPow(zkp.b, P).toString(16));
michael@0 206 Logger.debug(LOG_TAG, "g^(xh) = " + gx.modPow(h, P).toString(16));
michael@0 207 Logger.debug(LOG_TAG, "gx = " + gx.toString(16));
michael@0 208 Logger.debug(LOG_TAG, "h = " + h.toString(16));
michael@0 209 Logger.error(LOG_TAG, "zkp calculation incorrect.");
michael@0 210 throw new IncorrectZkpException();
michael@0 211 }
michael@0 212 Logger.debug(LOG_TAG, "*** ZKP SUCCESS ***");
michael@0 213 }
michael@0 214
michael@0 215 /*
michael@0 216 * Use SHA-256 to compute a BigInteger hash of g, gr, gx values with
michael@0 217 * mySignerId to prevent replay. Does not make a twos-complement BigInteger
michael@0 218 * form hash.
michael@0 219 */
michael@0 220 private static BigInteger computeBHash(BigInteger g, BigInteger gr, BigInteger gx,
michael@0 221 String id) throws NoSuchAlgorithmException, UnsupportedEncodingException {
michael@0 222 MessageDigest sha = MessageDigest.getInstance("SHA-256");
michael@0 223 sha.reset();
michael@0 224
michael@0 225 /*
michael@0 226 * Note: you should ensure the items in H(...) have clear boundaries. It
michael@0 227 * is simple if the other party knows sizes of g, gr, gx and signerID and
michael@0 228 * hence the boundary is unambiguous. If not, you'd better prepend each
michael@0 229 * item with its byte length, but I've omitted that here.
michael@0 230 */
michael@0 231
michael@0 232 hashByteArrayWithLength(sha, BigIntegerHelper.BigIntegerToByteArrayWithoutSign(g));
michael@0 233 hashByteArrayWithLength(sha, BigIntegerHelper.BigIntegerToByteArrayWithoutSign(gr));
michael@0 234 hashByteArrayWithLength(sha, BigIntegerHelper.BigIntegerToByteArrayWithoutSign(gx));
michael@0 235 hashByteArrayWithLength(sha, id.getBytes("UTF-8"));
michael@0 236
michael@0 237 byte[] hash = sha.digest();
michael@0 238
michael@0 239 return BigIntegerHelper.ByteArrayToBigIntegerWithoutSign(hash);
michael@0 240 }
michael@0 241
michael@0 242 /*
michael@0 243 * Update a hash with a byte array's length and the byte array.
michael@0 244 */
michael@0 245 private static void hashByteArrayWithLength(MessageDigest sha, byte[] data) {
michael@0 246 int length = data.length;
michael@0 247 byte[] b = new byte[] { (byte) (length >>> 8), (byte) (length & 0xff) };
michael@0 248 sha.update(b);
michael@0 249 sha.update(data);
michael@0 250 }
michael@0 251
michael@0 252 /*
michael@0 253 * Helper function to generate encryption key and HMAC from a byte array.
michael@0 254 */
michael@0 255 public static void generateKeyAndHmac(BigInteger k, byte[] encOut, byte[] hmacOut) throws NoSuchAlgorithmException, InvalidKeyException {
michael@0 256 // Generate HMAC and Encryption keys from synckey.
michael@0 257 byte[] zerokey = new byte[32];
michael@0 258 byte[] prk = HMACSHA256(BigIntegerHelper.BigIntegerToByteArrayWithoutSign(k), zerokey);
michael@0 259
michael@0 260 byte[] okm = HKDF.hkdfExpand(prk, HKDF.HMAC_INPUT, 32 * 2);
michael@0 261 System.arraycopy(okm, 0, encOut, 0, 32);
michael@0 262 System.arraycopy(okm, 32, hmacOut, 0, 32);
michael@0 263 }
michael@0 264 }

mercurial