Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(39)

Side by Side Diff: base/crypto/symmetric_key_win.cc

Issue 1558018: Implements support for PBKDF2-based key derivation, random key generation, an... (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: Style fixup Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « base/crypto/symmetric_key_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/crypto/symmetric_key.h" 5 #include "base/crypto/symmetric_key.h"
6 6
7 #include <windows.h>
8 #include <wincrypt.h>
9
10 #include "base/basictypes.h"
11 #include "base/scoped_ptr.h"
12
7 namespace base { 13 namespace base {
8 14
9 // TODO(albertb): Implement on Windows. 15 namespace {
16
17 #pragma pack(push, 1)
18 // The following is a non-public Microsoft header documented in MSDN under
19 // CryptImportKey / CryptExportKey. Following the header is the byte array of
20 // the actual plaintext key.
21 struct PlaintextBlobHeader {
22 BLOBHEADER hdr;
23 DWORD cbKeySize;
24 };
25 #pragma pack(pop)
26
27
28 // CryptoAPI makes use of three distinct ALG_IDs for AES, rather than just
29 // CALG_AES (which exists, but depending on the functions you are calling, may
30 // result in function failure, wheras the subtype would succeed)
31 ALG_ID GetAESAlgIDForKeySize(size_t key_size_in_bits) {
32 // Only AES-128/-192/-256 is supported in CryptoAPI.
33 switch (key_size_in_bits) {
34 case 128:
35 return CALG_AES_128;
36 case 192:
37 return CALG_AES_192;
38 case 256:
39 return CALG_AES_256;
40 default:
41 return 0;
42 }
43 };
44
45 // Imports a raw/plaintext key of |key_size| stored in |*key_data| into a new
46 // key created for the specified |provider|. |alg| contains the algorithm of
47 // the key being imported.
48 // If |key_data| is intended to be used as an HMAC key, then |alg| should be
49 // CALG_HMAC.
50 // If successful, returns true and stores the imported key in |*key|.
51 bool ImportRawKey(HCRYPTPROV provider, ALG_ID alg, BYTE* key_data,
52 DWORD key_size, ScopedHCRYPTKEY* key) {
53 DCHECK_GT(key_size, 0);
54
55 BYTE* actual_key = key_data;
56 DWORD actual_size = key_size;
57
58 scoped_array<BYTE> tmp_data;
59
60 actual_size = sizeof(PlaintextBlobHeader) + key_size;
61
62 tmp_data.reset(new BYTE[actual_size]);
63 actual_key = tmp_data.get();
64 memcpy(actual_key + sizeof(PlaintextBlobHeader), key_data, key_size);
65 PlaintextBlobHeader* key_header
66 = reinterpret_cast<PlaintextBlobHeader*>(actual_key);
67 memset(key_header, 0, sizeof(PlaintextBlobHeader));
68
69 key_header->cbKeySize = key_size;
70
71 key_header->hdr.bType = PLAINTEXTKEYBLOB;
72 key_header->hdr.bVersion = CUR_BLOB_VERSION;
73 key_header->hdr.aiKeyAlg = alg;
74
75 HCRYPTKEY unsafe_key = NULL;
76 DWORD dwFlags = CRYPT_EXPORTABLE;
77 if (alg == CALG_HMAC) {
78 // Though it may appear odd that IPSEC and RC2 are being used, this is
79 // done in accordance with Microsoft's FIPS 140-2 Security Policy for the
80 // RSA Enhanced Provider, as the approved means of using arbitrary HMAC
81 // key material.
82 key_header->hdr.aiKeyAlg = CALG_RC2;
83 dwFlags |= CRYPT_IPSEC_HMAC_KEY;
84 }
85
86 BOOL ok = CryptImportKey(provider, actual_key, actual_size, NULL,
87 dwFlags, &unsafe_key);
88
89 // Clean-up the temporary copy of key, regardless of whether it was imported
90 // sucessfully or not
91 SecureZeroMemory(tmp_data.get(), actual_size);
92
93 if (!ok)
94 return false;
95
96 key->reset(unsafe_key);
97 return true;
98 }
99
100 // Attempts to generate a random AES key of |key_size_in_bits. Returns true if
101 // generation is successful, storing the generated key in |*key| and the key
102 // provider (CSP) in |*provider|.
103 bool GenerateAESKey(size_t key_size_in_bits, ScopedHCRYPTPROV* provider,
104 ScopedHCRYPTKEY* key) {
105 DCHECK(provider);
106 DCHECK(key);
107
108 ALG_ID alg = GetAESAlgIDForKeySize(key_size_in_bits);
109 if (alg == 0)
110 return false;
111
112 ScopedHCRYPTPROV safe_provider;
113 // Note: The only time NULL is safe to be passed as pszContainerName is when
114 // dwFlags contains CRYPT_VERIFYCONTEXT, as all keys generated and/or used
115 // will be treated as ephemeral keys and not persisted.
116 BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL,
117 PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
118 if (!ok)
119 return false;
120
121 ScopedHCRYPTKEY safe_key;
122 // In the FIPS 140-2 Security Policy for CAPI on XP/Vista+, Microsoft notes
123 // that CryptGenKey makes use of the same functionality exposed via
124 // CryptGenRandom. The reason this is being used, as opposed to
125 // CryptGenRandom and CryptImportKey is for compliance with the security
126 // policy
127 ok = CryptGenKey(safe_provider.get(), alg, CRYPT_EXPORTABLE,
128 safe_key.receive());
129 if (!ok)
130 return false;
131
132 key->swap(safe_key);
133 provider->swap(safe_provider);
134
135 return true;
136 }
137
138 // Attempts to generate a random, |key_size_in_bits|-long HMAC key, for use
139 // with the hash function |alg|.
140 // |key_size_in_bits| must be >= 1/2 the hash size of |alg|, for security.
141 // Returns true if generation is successful, storing the generated key in
142 // |*key| and the key provider (CSP) in |*provider|.
143 bool GenerateHMACKey(size_t key_size_in_bits, ALG_ID alg,
144 ScopedHCRYPTPROV* provider, ScopedHCRYPTKEY* key,
145 scoped_array<BYTE>* raw_key) {
146 DCHECK(provider);
147 DCHECK(key);
148
149 ScopedHCRYPTPROV safe_provider;
150 // See comment in GenerateAESKey as to why NULL is acceptable for the
151 // container name.
152 BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL,
153 PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
154 if (!ok)
155 return false;
156
157 ScopedHCRYPTHASH safe_hash;
158 ok = CryptCreateHash(safe_provider.get(), alg, NULL, 0, safe_hash.receive());
159 if (!ok)
160 return false;
161
162 DWORD hash_size = 0;
163 DWORD param_size = sizeof(hash_size);
164 ok = CryptGetHashParam(safe_hash, HP_HASHSIZE,
165 reinterpret_cast<BYTE*>(&hash_size), &param_size, 0);
166 if (!ok || hash_size == 0)
167 return false;
168
169 // An HMAC key must be >= L/2, where L is the output size of the hash
170 // function being used.
171 if (key_size_in_bits < (hash_size / 2 * 8) || (key_size_in_bits % 8) != 0)
172 return false;
173
174
175 DWORD key_size_in_bytes = key_size_in_bits / 8;
176 scoped_array<BYTE> random(new BYTE[key_size_in_bytes]);
177 ok = CryptGenRandom(safe_provider, key_size_in_bytes,
178 random.get());
179 if (!ok)
180 return false;
181
182 ScopedHCRYPTKEY safe_key;
183 if (!ImportRawKey(safe_provider, CALG_HMAC, random.get(),
184 key_size_in_bytes, &safe_key))
185 return false;
186
187 key->swap(safe_key);
188 provider->swap(safe_provider);
189 raw_key->swap(random);
190
191 return true;
192 }
193
194 // Attempts to create an HMAC hash instance using the specified |provider|
195 // and |key|. The inner hash function will be |hash_alg|. If successful,
196 // returns true and stores the hash in |*hash|.
197 bool CreateHMACHash(HCRYPTPROV provider, HCRYPTKEY key, ALG_ID hash_alg,
198 ScopedHCRYPTHASH* hash) {
199 ScopedHCRYPTHASH safe_hash;
200 BOOL ok = FALSE;
201
202 ok = CryptCreateHash(provider, CALG_HMAC, key, 0, safe_hash.receive());
203 if (!ok)
204 return false;
205
206 HMAC_INFO hmac_info;
207 memset(&hmac_info, 0, sizeof(hmac_info));
208 hmac_info.HashAlgid = hash_alg;
209
210 ok = CryptSetHashParam(safe_hash, HP_HMAC_INFO,
211 reinterpret_cast<const BYTE*>(&hmac_info), 0);
212 if (!ok)
213 return false;
214
215 hash->swap(safe_hash);
216 return true;
217 }
218
219 // Performs a single iteration of the PBKDF2 function F for the specified
220 // |block_number| using the PRF |hash|, writing the output to |*output_buf|.
221 // |output_buf| must have enough space to accomodate the output of the PRF
222 // specified by |hash|.
223 // Returns true if the block was successfully computed.
224 bool ComputePBKDF2Block(HCRYPTPROV provider, HCRYPTKEY key, HCRYPTHASH hash,
225 const std::string& salt, size_t iterations,
226 size_t block_number, BYTE* output_buf) {
227 // From RFC2898:
228 // 3. <snip> The function F is defined as the exclusive-or sum of the first
229 // c iterates of the underlying pseudorandom function PRF applied to the
230 // password P and the concatenation of the salt S and the block index i:
231 // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
232 // where
233 // U_1 = PRF(P, S || INT (i))
234 // U_2 = PRF(P, U_1)
235 // ...
236 // U_c = PRF(P, U_{c-1})
237 ScopedHCRYPTHASH safe_hash;
238 BOOL ok = CryptDuplicateHash(hash, 0, 0, safe_hash.receive());
239 if (!ok)
240 return false;
241
242 DWORD hash_size = 0;
243 DWORD param_size = sizeof(hash_size);
244
245 ok = CryptGetHashParam(safe_hash, HP_HASHSIZE,
246 reinterpret_cast<BYTE*>(&hash_size), &param_size, 0);
247 if (!ok || hash_size == 0)
248 return false;
249
250 // Iteration U_1: Compute PRF for S.
251 ok = CryptHashData(safe_hash,
252 reinterpret_cast<const BYTE*>(salt.data()), salt.size(),
253 0);
254 if (!ok)
255 return false;
256
257 // Iteration U_1: and append (big-endian) INT (i).
258 // TODO(rsleevi): Platform endian checks?
259 uint32 big_endian_block_number = ((block_number << 24) & 0xFF000000) |
260 ((block_number << 8) & 0x00FF0000) |
261 ((block_number >> 8) & 0x0000FF00) |
262 ((block_number >> 24) & 0x000000FF);
263 ok = CryptHashData(safe_hash,
264 reinterpret_cast<const BYTE*>(&big_endian_block_number),
265 4, 0);
266
267 scoped_array<BYTE> output(new BYTE[hash_size]);
268 scoped_array<BYTE> hash_val(new BYTE[hash_size]);
269
270 DWORD size = hash_size;
271 ok = CryptGetHashParam(safe_hash, HP_HASHVAL, hash_val.get(),
272 &size, 0);
273 if (!ok)
274 return false;
275
276 memcpy(output.get(), hash_val.get(), hash_size);
277
278 // Iteration 2 - c: Compute U_{iteration} by applying the HMAC-SHA1 PRF to
279 // U_{iteration - 1} with key |key|, then xor the resultant hash with
280 // |output|, which contains U_1 ^ U_2 ^ ... ^ U_{iteration - 1}
281 for (size_t iteration = 2; iteration <= iterations; ++iteration) {
282 safe_hash.reset();
283 ok = CryptDuplicateHash(hash, 0, 0, safe_hash.receive());
284 if (!ok)
285 return false;
286
287 ok = CryptHashData(safe_hash, hash_val.get(), hash_size, 0);
288 if (!ok)
289 return false;
290
291 size = hash_size;
292 ok = CryptGetHashParam(safe_hash, HP_HASHVAL, hash_val.get(), &size,
293 0);
294 if (!ok || size != hash_size)
295 return false;
296
297 for (int i = 0; i < hash_size; ++i)
298 output[i] ^= hash_val[i];
299 }
300
301 memcpy(output_buf, output.get(), hash_size);
302 return true;
303 }
304
305 } // namespace
10 306
11 // static 307 // static
12 SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, 308 SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm,
13 size_t key_size_in_bits) { 309 size_t key_size_in_bits) {
310 DCHECK_GE(key_size_in_bits, 8);
311
312 ScopedHCRYPTPROV provider_handle;
313 ScopedHCRYPTKEY key_handle;
314
315 bool ok = false;
316
317 scoped_array<BYTE> raw_key;
318 if (algorithm == AES) {
319 ok = GenerateAESKey(key_size_in_bits, &provider_handle, &key_handle);
320 } else if (algorithm == HMAC_SHA1) {
321 ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider_handle,
322 &key_handle, &raw_key);
323 }
324
325 if (ok) {
326 size_t key_size_in_bytes = key_size_in_bits / 8;
327 if (raw_key == NULL)
328 key_size_in_bytes = 0;
329
330 SymmetricKey* result = new SymmetricKey(provider_handle.release(),
331 key_handle.release(),
332 raw_key.get(),
333 key_size_in_bytes);
334 if (raw_key != NULL)
335 SecureZeroMemory(raw_key.get(), key_size_in_bytes);
336
337 return result;
338 }
339
340 NOTREACHED();
14 return NULL; 341 return NULL;
15 } 342 }
16 343
17 // static 344 // static
18 SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm, 345 SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm,
19 const std::string& password, 346 const std::string& password,
20 const std::string& salt, 347 const std::string& salt,
21 size_t iterations, 348 size_t iterations,
22 size_t key_size_in_bits) { 349 size_t key_size_in_bits) {
23 return NULL; 350 // CryptoAPI lacks native routines to perform PBKDF2 derivation as specified
351 // in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is
352 // supported as the PRF.
353 if (algorithm != HMAC_SHA1 && algorithm != AES)
354 return NULL;
355
356 // While not used until the end, sanity-check the input before proceding with
357 // the expensive computation.
358 DWORD provider_type = 0;
359 ALG_ID alg = 0;
360 switch (algorithm) {
361 case AES:
362 provider_type = PROV_RSA_AES;
363 alg = GetAESAlgIDForKeySize(key_size_in_bits);
364 break;
365 case HMAC_SHA1:
366 provider_type = PROV_RSA_FULL;
367 alg = CALG_HMAC;
368 break;
369 default:
370 NOTREACHED();
371 }
372 if (alg == 0 || provider_type == 0)
373 return NULL;
374
375 ScopedHCRYPTPROV provider;
376 BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type,
377 CRYPT_VERIFYCONTEXT);
378 if (!ok)
379 return false;
380
381 // Convert the user password into a key suitable to be fed into the PRF
382 // function.
383 ScopedHCRYPTKEY password_as_key;
384 BYTE* password_as_bytes =
385 const_cast<BYTE*>(reinterpret_cast<const BYTE*>(password.data()));
386 if (!ImportRawKey(provider, CALG_HMAC, password_as_bytes,
387 password.size(), &password_as_key))
388 return NULL;
389
390 // Configure the PRF function. Only HMAC variants are supported, with the
391 // only hash function supported being SHA1.
392 // TODO(rsleevi): Support SHA-256 on XP SP3+.
393 ScopedHCRYPTHASH prf;
394 if (!CreateHMACHash(provider, password_as_key, CALG_SHA1, &prf))
395 return false;
396
397 DWORD hLen = 0;
398 DWORD param_size = sizeof(hLen);
399 ok = CryptGetHashParam(prf, HP_HASHSIZE,
400 reinterpret_cast<BYTE*>(&hLen), &param_size, 0);
401 if (!ok || hLen == 0)
402 return false;
403
404 // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop.
405 size_t dkLen = key_size_in_bits / 8;
406 DCHECK_GT(dkLen, 0);
407
408 if ((dkLen / hLen) > 0xFFFFFFFF) {
409 DLOG(ERROR) << "Derived key too long.";
410 return NULL;
411 }
412
413 // 2. Let l be the number of hLen-octet blocks in the derived key,
414 // rounding up, and let r be the number of octets in the last
415 // block:
416 size_t l = dkLen / hLen;
417 size_t r = hLen - (dkLen % hLen);
418 if (r != hLen) ++l;
419
420 DCHECK_GT(l, 0);
421
422 size_t total_generated_size = l * hLen;
423 scoped_array<BYTE> generated_key(new BYTE[total_generated_size]);
424 BYTE* block_offset = generated_key.get();
425
426 // 3. For each block of the derived key apply the function F defined below
427 // to the password P, the salt S, the iteration count c, and the block
428 // index to compute the block:
429 // T_1 = F (P, S, c, 1)
430 // T_2 = F (P, S, c, 2)
431 // ...
432 // T_l = F (P, S, c, l)
433 // <snip>
434 // 4. Concatenate the blocks and extract the first dkLen octets to produce
435 // a derived key DK:
436 // DK = T_1 || T_2 || ... || T_l<0..r-1>
437 bool error = false;
438 for (size_t cur_block = 1; cur_block <= l && !error; ++cur_block) {
439 if (!ComputePBKDF2Block(provider, password_as_key, prf,
440 salt, iterations, cur_block, block_offset)) {
441 error = true;
442 }
443 block_offset += hLen;
444 }
445
446 if (error)
447 return NULL;
448
449 // Convert the derived key bytes into a key handle for the desired algorithm.
450 ScopedHCRYPTKEY key;
451 if (!ImportRawKey(provider, alg, generated_key.get(),
452 dkLen, &key))
453 return NULL;
454
455 SymmetricKey* result = new SymmetricKey(provider.release(), key.release(),
456 generated_key.get(), dkLen);
457
458 SecureZeroMemory(generated_key.get(), total_generated_size);
459
460 return result;
24 } 461 }
25 462
26 bool SymmetricKey::GetRawKey(std::string* raw_key) { 463 bool SymmetricKey::GetRawKey(std::string* raw_key) {
27 return false; 464 // Short circuit for when the key was supplied during initialization
465 if (!raw_key_.empty()) {
466 *raw_key = raw_key_;
467 return true;
468 }
469
470 DWORD size = 0;
471 BOOL ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0, NULL, &size);
472 if (!ok && GetLastError() != ERROR_MORE_DATA)
473 return false;
474
475 scoped_array<BYTE> result(new BYTE[size]);
476
477 ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0,
478 result.get(), &size);
479 if (!ok)
480 return false;
481
482 PlaintextBlobHeader* header =
483 reinterpret_cast<PlaintextBlobHeader*>(result.get());
484 raw_key->assign(reinterpret_cast<char*>(result.get() + sizeof(*header)),
485 header->cbKeySize);
486
487 SecureZeroMemory(result.get(), size);
488
489 return true;
28 } 490 }
29 491
30 } // namespace base 492 } // namespace base
OLDNEW
« no previous file with comments | « base/crypto/symmetric_key_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698