OLD | NEW |
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), ¶m_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), ¶m_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), ¶m_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 |
OLD | NEW |