1 /** 2 * HKDF 3 * 4 * Copyright: 5 * (C) 2013 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.prf.hkdf; 12 13 import botan.constants; 14 static if (BOTAN_HAS_HKDF): 15 16 import botan.mac.mac; 17 import botan.hash.hash; 18 import botan.utils.types; 19 import botan.utils.mem_ops; 20 import std.algorithm : min; 21 22 /** 23 * HKDF, see @rfc 5869 for details 24 */ 25 final class HKDF 26 { 27 public: 28 this(MessageAuthenticationCode extractor, 29 MessageAuthenticationCode prf) 30 { 31 m_extractor = extractor; 32 m_prf = prf; 33 } 34 35 this(MessageAuthenticationCode prf) 36 { 37 m_extractor = prf; 38 m_prf = m_extractor.clone(); 39 } 40 41 void startExtract(const(ubyte)* salt, size_t salt_len) 42 { 43 m_extractor.setKey(salt, salt_len); 44 } 45 46 void extract(const(ubyte)* input, size_t input_len) 47 { 48 m_extractor.update(input, input_len); 49 } 50 51 void finishExtract() 52 { 53 m_prf.setKey(m_extractor.finished()); 54 } 55 56 57 /** 58 * Only call after extract 59 * Params: 60 * output = the output 61 * output_len = must be less than 256*hashlen 62 * info = the underlying data 63 * info_len = length of underlying data 64 */ 65 void expand(ubyte* output, size_t output_len, 66 const(ubyte)* info, size_t info_len) 67 { 68 if (output_len > m_prf.outputLength * 255) 69 throw new InvalidArgument("HKDF requested output too large"); 70 71 ubyte counter = 1; 72 73 SecureVector!ubyte T; 74 75 while (output_len) 76 { 77 m_prf.update(T); 78 m_prf.update(info, info_len); 79 m_prf.update(counter++); 80 T = m_prf.finished(); 81 82 const size_t to_write = min(T.length, output_len); 83 copyMem(output, T.ptr, to_write); 84 output += to_write; 85 output_len -= to_write; 86 } 87 } 88 89 90 @property string name() const 91 { 92 return "HKDF(" ~ m_prf.name ~ ")"; 93 } 94 95 void clear() 96 { 97 m_extractor.clear(); 98 m_prf.clear(); 99 } 100 private: 101 Unique!MessageAuthenticationCode m_extractor; 102 Unique!MessageAuthenticationCode m_prf; 103 } 104 105 106 static if (BOTAN_TEST): 107 108 import botan.test; 109 import botan.codec.hex; 110 import botan.libstate.libstate; 111 import memutils.hashmap; 112 113 private shared size_t total_tests; 114 115 SecureVector!ubyte hkdf()(string hkdf_algo, 116 auto const ref SecureVector!ubyte ikm, 117 auto const ref SecureVector!ubyte salt, 118 auto const ref SecureVector!ubyte info, 119 size_t L) 120 { 121 AlgorithmFactory af = globalState().algorithmFactory(); 122 123 const string algo = hkdf_algo[5 .. hkdf_algo.length-1]; 124 //logTrace("Testing: HMAC(", algo, ")"); 125 const MessageAuthenticationCode mac_proto = af.prototypeMac("HMAC(" ~ algo ~ ")"); 126 127 if (!mac_proto) 128 throw new InvalidArgument("Bad HKDF hash '" ~ algo ~ "'"); 129 130 auto hkdf = scoped!HKDF(mac_proto.clone(), mac_proto.clone()); 131 132 hkdf.startExtract(salt.ptr, salt.length); 133 hkdf.extract(ikm.ptr, ikm.length); 134 hkdf.finishExtract(); 135 136 SecureVector!ubyte key = SecureVector!ubyte(L); 137 hkdf.expand(key.ptr, key.length, info.ptr, info.length); 138 return key; 139 } 140 141 size_t hkdfTest(string algo, string ikm, string salt, string info, string okm, size_t L) 142 { 143 import core.atomic; 144 atomicOp!"+="(total_tests, 1); 145 const string got = hexEncode(hkdf(algo, 146 hexDecodeLocked(ikm), 147 hexDecodeLocked(salt), 148 hexDecodeLocked(info), 149 L)); 150 151 if (got != okm) 152 { 153 logTrace("HKDF got " ~ got ~ " expected " ~ okm); 154 return 1; 155 } 156 157 return 0; 158 } 159 160 static if (BOTAN_HAS_TESTS && !SKIP_HKDF_TEST) unittest 161 { 162 logDebug("Testing hkdf.d ..."); 163 File vec = File("test_data/hkdf.vec", "r"); 164 165 size_t fails = runTestsBb(vec, "HKDF", "OKM", true, 166 (ref HashMap!(string, string) m) 167 { 168 return hkdfTest(m["HKDF"], m["IKM"], m.get("salt"), m.get("info"), m["OKM"], to!uint(m["L"])); 169 }); 170 171 testReport("hkdf", total_tests, fails); 172 }