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