michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.sync.crypto; michael@0: michael@0: import java.security.InvalidKeyException; michael@0: import java.security.Key; michael@0: import java.security.NoSuchAlgorithmException; michael@0: michael@0: import javax.crypto.Mac; michael@0: import javax.crypto.spec.SecretKeySpec; michael@0: michael@0: import org.mozilla.gecko.sync.Utils; michael@0: michael@0: /* michael@0: * A standards-compliant implementation of RFC 5869 michael@0: * for HMAC-based Key Derivation Function. michael@0: * HMAC uses HMAC SHA256 standard. michael@0: */ michael@0: public class HKDF { michael@0: public static String HMAC_ALGORITHM = "hmacSHA256"; michael@0: michael@0: /** michael@0: * Used for conversion in cases in which you *know* the encoding exists. michael@0: */ michael@0: public static final byte[] bytes(String in) { michael@0: try { michael@0: return in.getBytes("UTF-8"); michael@0: } catch (java.io.UnsupportedEncodingException e) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: public static final int BLOCKSIZE = 256 / 8; michael@0: public static final byte[] HMAC_INPUT = bytes("Sync-AES_256_CBC-HMAC256"); michael@0: michael@0: /* michael@0: * Step 1 of RFC 5869 michael@0: * Get sha256HMAC Bytes michael@0: * Input: salt (message), IKM (input keyring material) michael@0: * Output: PRK (pseudorandom key) michael@0: */ michael@0: public static byte[] hkdfExtract(byte[] salt, byte[] IKM) throws NoSuchAlgorithmException, InvalidKeyException { michael@0: return digestBytes(IKM, makeHMACHasher(salt)); michael@0: } michael@0: michael@0: /* michael@0: * Step 2 of RFC 5869. michael@0: * Input: PRK from step 1, info, length. michael@0: * Output: OKM (output keyring material). michael@0: */ michael@0: public static byte[] hkdfExpand(byte[] prk, byte[] info, int len) throws NoSuchAlgorithmException, InvalidKeyException { michael@0: Mac hmacHasher = makeHMACHasher(prk); michael@0: michael@0: byte[] T = {}; michael@0: byte[] Tn = {}; michael@0: michael@0: int iterations = (int) Math.ceil(((double)len) / ((double)BLOCKSIZE)); michael@0: for (int i = 0; i < iterations; i++) { michael@0: Tn = digestBytes(Utils.concatAll(Tn, info, Utils.hex2Byte(Integer.toHexString(i + 1))), michael@0: hmacHasher); michael@0: T = Utils.concatAll(T, Tn); michael@0: } michael@0: michael@0: byte[] result = new byte[len]; michael@0: System.arraycopy(T, 0, result, 0, len); michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: * Make HMAC key michael@0: * Input: key (salt) michael@0: * Output: Key HMAC-Key michael@0: */ michael@0: public static Key makeHMACKey(byte[] key) { michael@0: if (key.length == 0) { michael@0: key = new byte[BLOCKSIZE]; michael@0: } michael@0: return new SecretKeySpec(key, HMAC_ALGORITHM); michael@0: } michael@0: michael@0: /* michael@0: * Make an HMAC hasher michael@0: * Input: Key hmacKey michael@0: * Ouput: An HMAC Hasher michael@0: */ michael@0: public static Mac makeHMACHasher(byte[] key) throws NoSuchAlgorithmException, InvalidKeyException { michael@0: Mac hmacHasher = null; michael@0: hmacHasher = Mac.getInstance(HMAC_ALGORITHM); michael@0: michael@0: // If Mac.getInstance doesn't throw NoSuchAlgorithmException, hmacHasher is michael@0: // non-null. michael@0: assert(hmacHasher != null); michael@0: michael@0: hmacHasher.init(makeHMACKey(key)); michael@0: return hmacHasher; michael@0: } michael@0: michael@0: /* michael@0: * Hash bytes with given hasher michael@0: * Input: message to hash, HMAC hasher michael@0: * Output: hashed byte[]. michael@0: */ michael@0: public static byte[] digestBytes(byte[] message, Mac hasher) { michael@0: hasher.update(message); michael@0: byte[] ret = hasher.doFinal(); michael@0: hasher.reset(); michael@0: return ret; michael@0: } michael@0: michael@0: public static byte[] derive(byte[] skm, byte[] xts, byte[] ctxInfo, int dkLen) throws InvalidKeyException, NoSuchAlgorithmException { michael@0: return hkdfExpand(hkdfExtract(xts, skm), ctxInfo, dkLen); michael@0: } michael@0: michael@0: public static void deriveMany(byte[] skm, byte[] xts, byte[] ctxInfo, byte[]... keys) throws InvalidKeyException, NoSuchAlgorithmException { michael@0: int length = 0; michael@0: for (byte[] key : keys) { michael@0: length += key.length; michael@0: } michael@0: byte[] derived = hkdfExpand(hkdfExtract(xts, skm), ctxInfo, length); michael@0: int offset = 0; michael@0: for (byte[] key : keys) { michael@0: System.arraycopy(derived, offset, key, 0, key.length); michael@0: offset += key.length; michael@0: } michael@0: } michael@0: }