mobile/android/base/sync/crypto/KeyBundle.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/sync/crypto/KeyBundle.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,139 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.sync.crypto;
     1.9 +
    1.10 +import java.io.UnsupportedEncodingException;
    1.11 +import java.security.InvalidKeyException;
    1.12 +import java.security.NoSuchAlgorithmException;
    1.13 +import java.util.Arrays;
    1.14 +
    1.15 +import javax.crypto.KeyGenerator;
    1.16 +import javax.crypto.Mac;
    1.17 +
    1.18 +import org.mozilla.apache.commons.codec.binary.Base64;
    1.19 +import org.mozilla.gecko.sync.Utils;
    1.20 +
    1.21 +public class KeyBundle {
    1.22 +    private static final String KEY_ALGORITHM_SPEC = "AES";
    1.23 +    private static final int    KEY_SIZE           = 256;
    1.24 +
    1.25 +    private byte[] encryptionKey;
    1.26 +    private byte[] hmacKey;
    1.27 +
    1.28 +    // These are the same for every sync key bundle.
    1.29 +    private static final byte[] EMPTY_BYTES      = {};
    1.30 +    private static final byte[] ENCR_INPUT_BYTES = {1};
    1.31 +    private static final byte[] HMAC_INPUT_BYTES = {2};
    1.32 +
    1.33 +    /*
    1.34 +     * Mozilla's use of HKDF for getting keys from the Sync Key string.
    1.35 +     *
    1.36 +     * We do exactly 2 HKDF iterations and make the first iteration the
    1.37 +     * encryption key and the second iteration the HMAC key.
    1.38 +     *
    1.39 +     */
    1.40 +    public KeyBundle(String username, String base32SyncKey) throws CryptoException {
    1.41 +      if (base32SyncKey == null) {
    1.42 +        throw new IllegalArgumentException("No sync key provided.");
    1.43 +      }
    1.44 +      if (username == null || username.equals("")) {
    1.45 +        throw new IllegalArgumentException("No username provided.");
    1.46 +      }
    1.47 +      // Hash appropriately.
    1.48 +      try {
    1.49 +        username = Utils.usernameFromAccount(username);
    1.50 +      } catch (NoSuchAlgorithmException e) {
    1.51 +        throw new IllegalArgumentException("Invalid username.");
    1.52 +      } catch (UnsupportedEncodingException e) {
    1.53 +        throw new IllegalArgumentException("Invalid username.");
    1.54 +      }
    1.55 +
    1.56 +      byte[] syncKey = Utils.decodeFriendlyBase32(base32SyncKey);
    1.57 +      byte[] user    = username.getBytes();
    1.58 +
    1.59 +      Mac hmacHasher;
    1.60 +      try {
    1.61 +        hmacHasher = HKDF.makeHMACHasher(syncKey);
    1.62 +      } catch (NoSuchAlgorithmException e) {
    1.63 +        throw new CryptoException(e);
    1.64 +      } catch (InvalidKeyException e) {
    1.65 +        throw new CryptoException(e);
    1.66 +      }
    1.67 +      assert(hmacHasher != null); // If makeHMACHasher doesn't throw, then hmacHasher is non-null.
    1.68 +
    1.69 +      byte[] encrBytes = Utils.concatAll(EMPTY_BYTES, HKDF.HMAC_INPUT, user, ENCR_INPUT_BYTES);
    1.70 +      byte[] encrKey   = HKDF.digestBytes(encrBytes, hmacHasher);
    1.71 +      byte[] hmacBytes = Utils.concatAll(encrKey, HKDF.HMAC_INPUT, user, HMAC_INPUT_BYTES);
    1.72 +
    1.73 +      this.hmacKey       = HKDF.digestBytes(hmacBytes, hmacHasher);
    1.74 +      this.encryptionKey = encrKey;
    1.75 +    }
    1.76 +
    1.77 +    public KeyBundle(byte[] encryptionKey, byte[] hmacKey) {
    1.78 +       this.setEncryptionKey(encryptionKey);
    1.79 +       this.setHMACKey(hmacKey);
    1.80 +    }
    1.81 +
    1.82 +    /**
    1.83 +     * Make a KeyBundle with the specified base64-encoded keys.
    1.84 +     *
    1.85 +     * @return A KeyBundle with the specified keys.
    1.86 +     */
    1.87 +    public static KeyBundle fromBase64EncodedKeys(String base64EncryptionKey, String base64HmacKey) throws UnsupportedEncodingException {
    1.88 +      return new KeyBundle(Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8")),
    1.89 +                           Base64.decodeBase64(base64HmacKey.getBytes("UTF-8")));
    1.90 +    }
    1.91 +
    1.92 +    /**
    1.93 +     * Make a KeyBundle with two random 256 bit keys (encryption and HMAC).
    1.94 +     *
    1.95 +     * @return A KeyBundle with random keys.
    1.96 +     */
    1.97 +    public static KeyBundle withRandomKeys() throws CryptoException {
    1.98 +      KeyGenerator keygen;
    1.99 +      try {
   1.100 +        keygen = KeyGenerator.getInstance(KEY_ALGORITHM_SPEC);
   1.101 +      } catch (NoSuchAlgorithmException e) {
   1.102 +        throw new CryptoException(e);
   1.103 +      }
   1.104 +
   1.105 +      keygen.init(KEY_SIZE);
   1.106 +      byte[] encryptionKey = keygen.generateKey().getEncoded();
   1.107 +      byte[] hmacKey = keygen.generateKey().getEncoded();
   1.108 +
   1.109 +      return new KeyBundle(encryptionKey, hmacKey);
   1.110 +    }
   1.111 +
   1.112 +    public byte[] getEncryptionKey() {
   1.113 +        return encryptionKey;
   1.114 +    }
   1.115 +
   1.116 +    public void setEncryptionKey(byte[] encryptionKey) {
   1.117 +        this.encryptionKey = encryptionKey;
   1.118 +    }
   1.119 +
   1.120 +    public byte[] getHMACKey() {
   1.121 +        return hmacKey;
   1.122 +    }
   1.123 +
   1.124 +    public void setHMACKey(byte[] hmacKey) {
   1.125 +        this.hmacKey = hmacKey;
   1.126 +    }
   1.127 +
   1.128 +    @Override
   1.129 +    public boolean equals(Object o) {
   1.130 +      if (!(o instanceof KeyBundle)) {
   1.131 +        return false;
   1.132 +      }
   1.133 +      KeyBundle other = (KeyBundle) o;
   1.134 +      return Arrays.equals(other.encryptionKey, this.encryptionKey) &&
   1.135 +             Arrays.equals(other.hmacKey, this.hmacKey);
   1.136 +    }
   1.137 +
   1.138 +    @Override
   1.139 +    public int hashCode() {
   1.140 +      throw new UnsupportedOperationException("No hashCode for KeyBundle.");
   1.141 +    }
   1.142 +}

mercurial