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

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

Issue 1528021: Implement PBKDF2-based key derivation, random key generation,... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Make albertb's suggested changes. 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 <winsock2.h> // For htonl.
8
9 #include <vector>
10
11 // TODO(wtc): replace scoped_array by std::vector.
12 #include "base/scoped_ptr.h"
13
7 namespace base { 14 namespace base {
8 15
9 // TODO(albertb): Implement on Windows. 16 namespace {
17
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
26 // CryptoAPI makes use of three distinct ALG_IDs for AES, rather than just
27 // CALG_AES (which exists, but depending on the functions you are calling, may
28 // result in function failure, whereas the subtype would succeed).
29 ALG_ID GetAESAlgIDForKeySize(size_t key_size_in_bits) {
30 // Only AES-128/-192/-256 is supported in CryptoAPI.
31 switch (key_size_in_bits) {
32 case 128:
33 return CALG_AES_128;
34 case 192:
35 return CALG_AES_192;
36 case 256:
37 return CALG_AES_256;
38 default:
39 NOTREACHED();
40 return 0;
41 }
42 };
43
44 // Imports a raw/plaintext key of |key_size| stored in |*key_data| into a new
45 // key created for the specified |provider|. |alg| contains the algorithm of
46 // the key being imported.
47 // If |key_data| is intended to be used as an HMAC key, then |alg| should be
48 // CALG_HMAC.
49 // If successful, returns true and stores the imported key in |*key|.
50 // TODO(wtc): use this function in hmac_win.cc.
51 bool ImportRawKey(HCRYPTPROV provider, ALG_ID alg, const void* key_data,
52 DWORD key_size, ScopedHCRYPTKEY* key) {
53 DCHECK_GT(key_size, 0);
54
55 DWORD actual_size = sizeof(PlaintextBlobHeader) + key_size;
56 std::vector<BYTE> tmp_data(actual_size);
57 BYTE* actual_key = &tmp_data[0];
58 memcpy(actual_key + sizeof(PlaintextBlobHeader), key_data, key_size);
59 PlaintextBlobHeader* key_header =
60 reinterpret_cast<PlaintextBlobHeader*>(actual_key);
61 memset(key_header, 0, sizeof(PlaintextBlobHeader));
62
63 key_header->hdr.bType = PLAINTEXTKEYBLOB;
64 key_header->hdr.bVersion = CUR_BLOB_VERSION;
65 key_header->hdr.aiKeyAlg = alg;
66
67 key_header->cbKeySize = key_size;
68
69 HCRYPTKEY unsafe_key = NULL;
70 DWORD flags = CRYPT_EXPORTABLE;
71 if (alg == CALG_HMAC) {
72 // Though it may appear odd that IPSEC and RC2 are being used, this is
73 // done in accordance with Microsoft's FIPS 140-2 Security Policy for the
74 // RSA Enhanced Provider, as the approved means of using arbitrary HMAC
75 // key material.
76 key_header->hdr.aiKeyAlg = CALG_RC2;
77 flags |= CRYPT_IPSEC_HMAC_KEY;
78 }
79
80 BOOL ok = CryptImportKey(provider, actual_key, actual_size, NULL,
81 flags, &unsafe_key);
82
83 // Clean up the temporary copy of key, regardless of whether it was imported
84 // sucessfully or not.
85 SecureZeroMemory(actual_key, actual_size);
86
87 if (!ok)
88 return false;
89
90 key->reset(unsafe_key);
91 return true;
92 }
93
94 // Attempts to generate a random AES key of |key_size_in_bits|. Returns true
95 // if generation is successful, storing the generated key in |*key| and the
96 // key provider (CSP) in |*provider|.
97 bool GenerateAESKey(size_t key_size_in_bits, ScopedHCRYPTPROV* provider,
98 ScopedHCRYPTKEY* key) {
99 DCHECK(provider);
100 DCHECK(key);
101
102 ALG_ID alg = GetAESAlgIDForKeySize(key_size_in_bits);
103 if (alg == 0)
104 return false;
105
106 ScopedHCRYPTPROV safe_provider;
107 // Note: The only time NULL is safe to be passed as pszContainer is when
108 // dwFlags contains CRYPT_VERIFYCONTEXT, as all keys generated and/or used
109 // will be treated as ephemeral keys and not persisted.
110 BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL,
111 PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
112 if (!ok)
113 return false;
114
115 ScopedHCRYPTKEY safe_key;
116 // In the FIPS 140-2 Security Policy for CAPI on XP/Vista+, Microsoft notes
117 // that CryptGenKey makes use of the same functionality exposed via
118 // CryptGenRandom. The reason this is being used, as opposed to
119 // CryptGenRandom and CryptImportKey is for compliance with the security
120 // policy
121 ok = CryptGenKey(safe_provider.get(), alg, CRYPT_EXPORTABLE,
122 safe_key.receive());
123 if (!ok)
124 return false;
125
126 key->swap(safe_key);
127 provider->swap(safe_provider);
128
129 return true;
130 }
131
132 // Returns true if the HMAC key size meets the requirement of FIPS 198
133 // Section 3. |alg| is the hash function used in the HMAC.
134 bool CheckHMACKeySize(size_t key_size_in_bits, ALG_ID alg) {
135 DWORD hash_size = 0;
136 switch (alg) {
137 case CALG_SHA1:
138 hash_size = 20;
139 break;
140 case CALG_SHA_256:
141 hash_size = 32;
142 break;
143 case CALG_SHA_384:
144 hash_size = 48;
145 break;
146 case CALG_SHA_512:
147 hash_size = 64;
148 break;
149 }
150 if (hash_size == 0)
151 return false;
152
153 // An HMAC key must be >= L/2, where L is the output size of the hash
154 // function being used.
155 return (key_size_in_bits >= (hash_size / 2 * 8) &&
156 (key_size_in_bits % 8) == 0);
157 }
158
159 // Attempts to generate a random, |key_size_in_bits|-long HMAC key, for use
160 // with the hash function |alg|.
161 // |key_size_in_bits| must be >= 1/2 the hash size of |alg| for security.
162 // Returns true if generation is successful, storing the generated key in
163 // |*key| and the key provider (CSP) in |*provider|.
164 bool GenerateHMACKey(size_t key_size_in_bits, ALG_ID alg,
165 ScopedHCRYPTPROV* provider, ScopedHCRYPTKEY* key,
166 scoped_array<BYTE>* raw_key) {
167 DCHECK(provider);
168 DCHECK(key);
169 DCHECK(raw_key);
170
171 if (!CheckHMACKeySize(key_size_in_bits, alg))
172 return false;
173
174 ScopedHCRYPTPROV safe_provider;
175 // See comment in GenerateAESKey as to why NULL is acceptable for the
176 // container name.
177 BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL,
178 PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
179 if (!ok)
180 return false;
181
182 DWORD key_size_in_bytes = key_size_in_bits / 8;
183 scoped_array<BYTE> random(new BYTE[key_size_in_bytes]);
184 ok = CryptGenRandom(safe_provider, key_size_in_bytes, random.get());
185 if (!ok)
186 return false;
187
188 ScopedHCRYPTKEY safe_key;
189 bool rv = ImportRawKey(safe_provider, CALG_HMAC, random.get(),
190 key_size_in_bytes, &safe_key);
191 if (rv) {
192 key->swap(safe_key);
193 provider->swap(safe_provider);
194 raw_key->swap(random);
195 }
196
197 SecureZeroMemory(random.get(), key_size_in_bytes);
198 return rv;
199 }
200
201 // Attempts to create an HMAC hash instance using the specified |provider|
202 // and |key|. The inner hash function will be |hash_alg|. If successful,
203 // returns true and stores the hash in |*hash|.
204 // TODO(wtc): use this function in hmac_win.cc.
205 bool CreateHMACHash(HCRYPTPROV provider, HCRYPTKEY key, ALG_ID hash_alg,
206 ScopedHCRYPTHASH* hash) {
207 ScopedHCRYPTHASH safe_hash;
208 BOOL ok = CryptCreateHash(provider, CALG_HMAC, key, 0, safe_hash.receive());
209 if (!ok)
210 return false;
211
212 HMAC_INFO hmac_info;
213 memset(&hmac_info, 0, sizeof(hmac_info));
214 hmac_info.HashAlgid = hash_alg;
215
216 ok = CryptSetHashParam(safe_hash, HP_HMAC_INFO,
217 reinterpret_cast<const BYTE*>(&hmac_info), 0);
218 if (!ok)
219 return false;
220
221 hash->swap(safe_hash);
222 return true;
223 }
224
225 // Computes a block of the derived key using the PBKDF2 function F for the
226 // specified |block_index| using the PRF |hash|, writing the output to
227 // |output_buf|.
228 // |output_buf| must have enough space to accomodate the output of the PRF
229 // specified by |hash|.
230 // Returns true if the block was successfully computed.
231 bool ComputePBKDF2Block(HCRYPTHASH hash, DWORD hash_size,
232 const std::string& salt, size_t iterations,
233 uint32 block_index, BYTE* output_buf) {
234 // From RFC 2898:
235 // 3. <snip> The function F is defined as the exclusive-or sum of the first
236 // c iterates of the underlying pseudorandom function PRF applied to the
237 // password P and the concatenation of the salt S and the block index i:
238 // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
239 // where
240 // U_1 = PRF(P, S || INT (i))
241 // U_2 = PRF(P, U_1)
242 // ...
243 // U_c = PRF(P, U_{c-1})
244 ScopedHCRYPTHASH safe_hash;
245 BOOL ok = CryptDuplicateHash(hash, NULL, 0, safe_hash.receive());
246 if (!ok)
247 return false;
248
249 // Iteration U_1: Compute PRF for S.
250 ok = CryptHashData(safe_hash,
251 reinterpret_cast<const BYTE*>(salt.data()), salt.size(),
252 0);
253 if (!ok)
254 return false;
255
256 // Iteration U_1: and append (big-endian) INT (i).
257 uint32 big_endian_block_index = htonl(block_index);
258 ok = CryptHashData(safe_hash,
259 reinterpret_cast<BYTE*>(&big_endian_block_index),
260 sizeof(big_endian_block_index), 0);
261
262 std::vector<BYTE> hash_value(hash_size);
263
264 DWORD size = hash_size;
265 ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0);
266 if (!ok || size != hash_size)
267 return false;
268
269 memcpy(output_buf, &hash_value[0], hash_size);
270
271 // Iteration 2 - c: Compute U_{iteration} by applying the PRF to
272 // U_{iteration - 1}, then xor the resultant hash with |output|, which
273 // contains U_1 ^ U_2 ^ ... ^ U_{iteration - 1}.
274 for (size_t iteration = 2; iteration <= iterations; ++iteration) {
275 safe_hash.reset();
276 ok = CryptDuplicateHash(hash, NULL, 0, safe_hash.receive());
277 if (!ok)
278 return false;
279
280 ok = CryptHashData(safe_hash, &hash_value[0], hash_size, 0);
281 if (!ok)
282 return false;
283
284 size = hash_size;
285 ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0);
286 if (!ok || size != hash_size)
287 return false;
288
289 for (int i = 0; i < hash_size; ++i)
290 output_buf[i] ^= hash_value[i];
291 }
292
293 return true;
294 }
295
296 } // namespace
297
298 SymmetricKey::~SymmetricKey() {
299 // TODO(wtc): create a "secure" string type that zeroes itself in the
300 // destructor.
301 if (!raw_key_.empty())
302 SecureZeroMemory(const_cast<char *>(raw_key_.data()), raw_key_.size());
303 }
10 304
11 // static 305 // static
12 SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, 306 SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm,
13 size_t key_size_in_bits) { 307 size_t key_size_in_bits) {
14 return NULL; 308 DCHECK_GE(key_size_in_bits, 8);
309
310 ScopedHCRYPTPROV provider;
311 ScopedHCRYPTKEY key;
312
313 bool ok = false;
314 scoped_array<BYTE> raw_key;
315
316 switch (algorithm) {
317 case AES:
318 ok = GenerateAESKey(key_size_in_bits, &provider, &key);
319 break;
320 case HMAC_SHA1:
321 ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider,
322 &key, &raw_key);
323 break;
324 }
325
326 if (!ok) {
327 NOTREACHED();
328 return NULL;
329 }
330
331 size_t key_size_in_bytes = key_size_in_bits / 8;
332 if (raw_key == NULL)
333 key_size_in_bytes = 0;
334
335 SymmetricKey* result = new SymmetricKey(provider.release(),
336 key.release(),
337 raw_key.get(),
338 key_size_in_bytes);
339 if (raw_key != NULL)
340 SecureZeroMemory(raw_key.get(), key_size_in_bytes);
341
342 return result;
15 } 343 }
16 344
17 // static 345 // static
18 SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm, 346 SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm,
19 const std::string& password, 347 const std::string& password,
20 const std::string& salt, 348 const std::string& salt,
21 size_t iterations, 349 size_t iterations,
22 size_t key_size_in_bits) { 350 size_t key_size_in_bits) {
23 return NULL; 351 // CryptoAPI lacks routines to perform PBKDF2 derivation as specified
352 // in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is
353 // supported as the PRF.
354
355 // While not used until the end, sanity-check the input before proceeding
356 // with the expensive computation.
357 DWORD provider_type = 0;
358 ALG_ID alg = 0;
359 switch (algorithm) {
360 case AES:
361 provider_type = PROV_RSA_AES;
362 alg = GetAESAlgIDForKeySize(key_size_in_bits);
363 break;
364 case HMAC_SHA1:
365 provider_type = PROV_RSA_FULL;
366 alg = CALG_HMAC;
367 break;
368 default:
369 NOTREACHED();
370 break;
371 }
372 if (provider_type == 0 || alg == 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 NULL;
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 NULL;
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 NULL;
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 - 1) / hLen;
417 DCHECK_GT(L, 0);
418
419 size_t total_generated_size = L * hLen;
420 std::vector<BYTE> generated_key(total_generated_size);
421 BYTE* block_offset = &generated_key[0];
422
423 // 3. For each block of the derived key apply the function F defined below
424 // to the password P, the salt S, the iteration count c, and the block
425 // index to compute the block:
426 // T_1 = F (P, S, c, 1)
427 // T_2 = F (P, S, c, 2)
428 // ...
429 // T_l = F (P, S, c, l)
430 // <snip>
431 // 4. Concatenate the blocks and extract the first dkLen octets to produce
432 // a derived key DK:
433 // DK = T_1 || T_2 || ... || T_l<0..r-1>
434 for (uint32 block_index = 1; block_index <= L; ++block_index) {
435 if (!ComputePBKDF2Block(prf, hLen, salt, iterations,
436 block_index, block_offset)) {
437 return NULL;
438 }
439 block_offset += hLen;
440 }
441
442 // Convert the derived key bytes into a key handle for the desired algorithm.
443 ScopedHCRYPTKEY key;
444 if (!ImportRawKey(provider, alg, &generated_key[0], dkLen, &key))
445 return NULL;
446
447 SymmetricKey* result = new SymmetricKey(provider.release(), key.release(),
448 &generated_key[0], dkLen);
449
450 SecureZeroMemory(&generated_key[0], total_generated_size);
451
452 return result;
453 }
454
455 // static
456 SymmetricKey* SymmetricKey::Import(Algorithm algorithm,
457 const void* key_data,
458 size_t key_size_in_bytes) {
459 // TODO(wtc): support HMAC.
460 DCHECK_EQ(algorithm, AES);
461
462 ScopedHCRYPTPROV provider;
463 BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL,
464 PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
465 if (!ok)
466 return NULL;
467
468 ALG_ID alg = GetAESAlgIDForKeySize(key_size_in_bytes * 8);
469 if (alg == 0)
470 return NULL;
471
472 ScopedHCRYPTKEY key;
473 if (!ImportRawKey(provider, alg, key_data, key_size_in_bytes, &key))
474 return NULL;
475
476 return new SymmetricKey(provider.release(), key.release(),
477 key_data, key_size_in_bytes);
24 } 478 }
25 479
26 bool SymmetricKey::GetRawKey(std::string* raw_key) { 480 bool SymmetricKey::GetRawKey(std::string* raw_key) {
27 return false; 481 // Short circuit for when the key was supplied to the constructor.
482 if (!raw_key_.empty()) {
483 *raw_key = raw_key_;
484 return true;
485 }
486
487 DWORD size = 0;
488 BOOL ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0, NULL, &size);
489 if (!ok)
490 return false;
491
492 std::vector<BYTE> result(size);
493
494 ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0, &result[0], &size);
495 if (!ok)
496 return false;
497
498 PlaintextBlobHeader* header =
499 reinterpret_cast<PlaintextBlobHeader*>(&result[0]);
500 raw_key->assign(reinterpret_cast<char*>(&result[sizeof(*header)]),
501 header->cbKeySize);
502
503 SecureZeroMemory(&result[0], size);
504
505 return true;
506 }
507
508 SymmetricKey::SymmetricKey(HCRYPTPROV provider, HCRYPTKEY key,
509 const void* key_data, size_t key_size_in_bytes)
510 : provider_(provider), key_(key) {
511 if (key_data) {
512 raw_key_.assign(reinterpret_cast<const char*>(key_data),
513 key_size_in_bytes);
514 }
28 } 515 }
29 516
30 } // namespace base 517 } // 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