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