Index: base/crypto/symmetric_key_win.cc |
=================================================================== |
--- base/crypto/symmetric_key_win.cc (revision 43354) |
+++ base/crypto/symmetric_key_win.cc (working copy) |
@@ -4,13 +4,340 @@ |
#include "base/crypto/symmetric_key.h" |
+#include <windows.h> |
+#include <wincrypt.h> |
+ |
+#include "base/basictypes.h" |
+#include "base/scoped_ptr.h" |
+ |
namespace base { |
-// TODO(albertb): Implement on Windows. |
+namespace { |
+#pragma pack(push, 1) |
+// The following is a non-public Microsoft header documented in MSDN under |
+// CryptImportKey / CryptExportKey. Following the header is the byte array of |
+// the actual plaintext key. |
+struct PlaintextBlobHeader { |
+ BLOBHEADER hdr; |
+ DWORD cbKeySize; |
+}; |
+#pragma pack(pop) |
+ |
+ |
+// CryptoAPI makes use of three distinct ALG_IDs for AES, rather than just |
+// CALG_AES (which exists, but depending on the functions you are calling, may |
+// result in function failure, wheras the subtype would succeed) |
+ALG_ID GetAESAlgIDForKeySize(size_t key_size_in_bits) { |
+ // Only AES-128/-192/-256 is supported in CryptoAPI. |
+ switch (key_size_in_bits) { |
+ case 128: |
+ return CALG_AES_128; |
+ case 192: |
+ return CALG_AES_192; |
+ case 256: |
+ return CALG_AES_256; |
+ default: |
+ return 0; |
+ } |
+}; |
+ |
+// Imports a raw/plaintext key of |key_size| stored in |*key_data| into a new |
+// key created for the specified |provider|. |alg| contains the algorithm of |
+// the key being imported. |
+// If |key_data| is intended to be used as an HMAC key, then |alg| should be |
+// CALG_HMAC. |
+// If successful, returns true and stores the imported key in |*key|. |
+bool ImportRawKey(HCRYPTPROV provider, ALG_ID alg, BYTE* key_data, |
+ DWORD key_size, ScopedHCRYPTKEY* key) { |
+ DCHECK_GT(key_size, 0); |
+ |
+ BYTE* actual_key = key_data; |
+ DWORD actual_size = key_size; |
+ |
+ scoped_array<BYTE> tmp_data; |
+ |
+ actual_size = sizeof(PlaintextBlobHeader) + key_size; |
+ |
+ tmp_data.reset(new BYTE[actual_size]); |
+ actual_key = tmp_data.get(); |
+ memcpy(actual_key + sizeof(PlaintextBlobHeader), key_data, key_size); |
+ PlaintextBlobHeader* key_header |
+ = reinterpret_cast<PlaintextBlobHeader*>(actual_key); |
+ memset(key_header, 0, sizeof(PlaintextBlobHeader)); |
+ |
+ key_header->cbKeySize = key_size; |
+ |
+ key_header->hdr.bType = PLAINTEXTKEYBLOB; |
+ key_header->hdr.bVersion = CUR_BLOB_VERSION; |
+ key_header->hdr.aiKeyAlg = alg; |
+ |
+ HCRYPTKEY unsafe_key = NULL; |
+ DWORD dwFlags = CRYPT_EXPORTABLE; |
+ if (alg == CALG_HMAC) { |
+ // Though it may appear odd that IPSEC and RC2 are being used, this is |
+ // done in accordance with Microsoft's FIPS 140-2 Security Policy for the |
+ // RSA Enhanced Provider, as the approved means of using arbitrary HMAC |
+ // key material. |
+ key_header->hdr.aiKeyAlg = CALG_RC2; |
+ dwFlags |= CRYPT_IPSEC_HMAC_KEY; |
+ } |
+ |
+ BOOL ok = CryptImportKey(provider, actual_key, actual_size, NULL, |
+ dwFlags, &unsafe_key); |
+ |
+ // Clean-up the temporary copy of key, regardless of whether it was imported |
+ // sucessfully or not |
+ SecureZeroMemory(tmp_data.get(), actual_size); |
+ |
+ if (!ok) |
+ return false; |
+ |
+ key->reset(unsafe_key); |
+ return true; |
+} |
+ |
+// Attempts to generate a random AES key of |key_size_in_bits. Returns true if |
+// generation is successful, storing the generated key in |*key| and the key |
+// provider (CSP) in |*provider|. |
+bool GenerateAESKey(size_t key_size_in_bits, ScopedHCRYPTPROV* provider, |
+ ScopedHCRYPTKEY* key) { |
+ DCHECK(provider); |
+ DCHECK(key); |
+ |
+ ALG_ID alg = GetAESAlgIDForKeySize(key_size_in_bits); |
+ if (alg == 0) |
+ return false; |
+ |
+ ScopedHCRYPTPROV safe_provider; |
+ // Note: The only time NULL is safe to be passed as pszContainerName is when |
+ // dwFlags contains CRYPT_VERIFYCONTEXT, as all keys generated and/or used |
+ // will be treated as ephemeral keys and not persisted. |
+ BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL, |
+ PROV_RSA_AES, CRYPT_VERIFYCONTEXT); |
+ if (!ok) |
+ return false; |
+ |
+ ScopedHCRYPTKEY safe_key; |
+ // In the FIPS 140-2 Security Policy for CAPI on XP/Vista+, Microsoft notes |
+ // that CryptGenKey makes use of the same functionality exposed via |
+ // CryptGenRandom. The reason this is being used, as opposed to |
+ // CryptGenRandom and CryptImportKey is for compliance with the security |
+ // policy |
+ ok = CryptGenKey(safe_provider.get(), alg, CRYPT_EXPORTABLE, |
+ safe_key.receive()); |
+ if (!ok) |
+ return false; |
+ |
+ key->swap(safe_key); |
+ provider->swap(safe_provider); |
+ |
+ return true; |
+} |
+ |
+// Attempts to generate a random, |key_size_in_bits|-long HMAC key, for use |
+// with the hash function |alg|. |
+// |key_size_in_bits| must be >= 1/2 the hash size of |alg|, for security. |
+// Returns true if generation is successful, storing the generated key in |
+// |*key| and the key provider (CSP) in |*provider|. |
+bool GenerateHMACKey(size_t key_size_in_bits, ALG_ID alg, |
+ ScopedHCRYPTPROV* provider, ScopedHCRYPTKEY* key, |
+ scoped_array<BYTE>* raw_key) { |
+ DCHECK(provider); |
+ DCHECK(key); |
+ |
+ ScopedHCRYPTPROV safe_provider; |
+ // See comment in GenerateAESKey as to why NULL is acceptable for the |
+ // container name. |
+ BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL, |
+ PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); |
+ if (!ok) |
+ return false; |
+ |
+ ScopedHCRYPTHASH safe_hash; |
+ ok = CryptCreateHash(safe_provider.get(), alg, NULL, 0, safe_hash.receive()); |
+ if (!ok) |
+ return false; |
+ |
+ DWORD hash_size = 0; |
+ DWORD param_size = sizeof(hash_size); |
+ ok = CryptGetHashParam(safe_hash, HP_HASHSIZE, |
+ reinterpret_cast<BYTE*>(&hash_size), ¶m_size, 0); |
+ if (!ok || hash_size == 0) |
+ return false; |
+ |
+ // An HMAC key must be >= L/2, where L is the output size of the hash |
+ // function being used. |
+ if (key_size_in_bits < (hash_size / 2 * 8) || (key_size_in_bits % 8) != 0) |
+ return false; |
+ |
+ |
+ DWORD key_size_in_bytes = key_size_in_bits / 8; |
+ scoped_array<BYTE> random(new BYTE[key_size_in_bytes]); |
+ ok = CryptGenRandom(safe_provider, key_size_in_bytes, |
+ random.get()); |
+ if (!ok) |
+ return false; |
+ |
+ ScopedHCRYPTKEY safe_key; |
+ if (!ImportRawKey(safe_provider, CALG_HMAC, random.get(), |
+ key_size_in_bytes, &safe_key)) |
+ return false; |
+ |
+ key->swap(safe_key); |
+ provider->swap(safe_provider); |
+ raw_key->swap(random); |
+ |
+ return true; |
+} |
+ |
+// Attempts to create an HMAC hash instance using the specified |provider| |
+// and |key|. The inner hash function will be |hash_alg|. If successful, |
+// returns true and stores the hash in |*hash|. |
+bool CreateHMACHash(HCRYPTPROV provider, HCRYPTKEY key, ALG_ID hash_alg, |
+ ScopedHCRYPTHASH* hash) { |
+ ScopedHCRYPTHASH safe_hash; |
+ BOOL ok = FALSE; |
+ |
+ ok = CryptCreateHash(provider, CALG_HMAC, key, 0, safe_hash.receive()); |
+ if (!ok) |
+ return false; |
+ |
+ HMAC_INFO hmac_info; |
+ memset(&hmac_info, 0, sizeof(hmac_info)); |
+ hmac_info.HashAlgid = hash_alg; |
+ |
+ ok = CryptSetHashParam(safe_hash, HP_HMAC_INFO, |
+ reinterpret_cast<const BYTE*>(&hmac_info), 0); |
+ if (!ok) |
+ return false; |
+ |
+ hash->swap(safe_hash); |
+ return true; |
+} |
+ |
+// Performs a single iteration of the PBKDF2 function F for the specified |
+// |block_number| using the PRF |hash|, writing the output to |*output_buf|. |
+// |output_buf| must have enough space to accomodate the output of the PRF |
+// specified by |hash|. |
+// Returns true if the block was successfully computed. |
+bool ComputePBKDF2Block(HCRYPTPROV provider, HCRYPTKEY key, HCRYPTHASH hash, |
+ const std::string& salt, size_t iterations, |
+ size_t block_number, BYTE* output_buf) { |
+ // From RFC2898: |
+ // 3. <snip> The function F is defined as the exclusive-or sum of the first |
+ // c iterates of the underlying pseudorandom function PRF applied to the |
+ // password P and the concatenation of the salt S and the block index i: |
+ // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c |
+ // where |
+ // U_1 = PRF(P, S || INT (i)) |
+ // U_2 = PRF(P, U_1) |
+ // ... |
+ // U_c = PRF(P, U_{c-1}) |
+ ScopedHCRYPTHASH safe_hash; |
+ BOOL ok = CryptDuplicateHash(hash, 0, 0, safe_hash.receive()); |
+ if (!ok) |
+ return false; |
+ |
+ DWORD hash_size = 0; |
+ DWORD param_size = sizeof(hash_size); |
+ |
+ ok = CryptGetHashParam(safe_hash, HP_HASHSIZE, |
+ reinterpret_cast<BYTE*>(&hash_size), ¶m_size, 0); |
+ if (!ok || hash_size == 0) |
+ return false; |
+ |
+ // Iteration U_1: Compute PRF for S. |
+ ok = CryptHashData(safe_hash, |
+ reinterpret_cast<const BYTE*>(salt.data()), salt.size(), |
+ 0); |
+ if (!ok) |
+ return false; |
+ |
+ // Iteration U_1: and append (big-endian) INT (i). |
+ // TODO(rsleevi): Platform endian checks? |
+ uint32 big_endian_block_number = ((block_number << 24) & 0xFF000000) | |
+ ((block_number << 8) & 0x00FF0000) | |
+ ((block_number >> 8) & 0x0000FF00) | |
+ ((block_number >> 24) & 0x000000FF); |
+ ok = CryptHashData(safe_hash, |
+ reinterpret_cast<const BYTE*>(&big_endian_block_number), |
+ 4, 0); |
+ |
+ scoped_array<BYTE> output(new BYTE[hash_size]); |
+ scoped_array<BYTE> hash_val(new BYTE[hash_size]); |
+ |
+ DWORD size = hash_size; |
+ ok = CryptGetHashParam(safe_hash, HP_HASHVAL, hash_val.get(), |
+ &size, 0); |
+ if (!ok) |
+ return false; |
+ |
+ memcpy(output.get(), hash_val.get(), hash_size); |
+ |
+ // Iteration 2 - c: Compute U_{iteration} by applying the HMAC-SHA1 PRF to |
+ // U_{iteration - 1} with key |key|, then xor the resultant hash with |
+ // |output|, which contains U_1 ^ U_2 ^ ... ^ U_{iteration - 1} |
+ for (size_t iteration = 2; iteration <= iterations; ++iteration) { |
+ safe_hash.reset(); |
+ ok = CryptDuplicateHash(hash, 0, 0, safe_hash.receive()); |
+ if (!ok) |
+ return false; |
+ |
+ ok = CryptHashData(safe_hash, hash_val.get(), hash_size, 0); |
+ if (!ok) |
+ return false; |
+ |
+ size = hash_size; |
+ ok = CryptGetHashParam(safe_hash, HP_HASHVAL, hash_val.get(), &size, |
+ 0); |
+ if (!ok || size != hash_size) |
+ return false; |
+ |
+ for (int i = 0; i < hash_size; ++i) |
+ output[i] ^= hash_val[i]; |
+ } |
+ |
+ memcpy(output_buf, output.get(), hash_size); |
+ return true; |
+} |
+ |
+} // namespace |
+ |
// static |
SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, |
size_t key_size_in_bits) { |
+ DCHECK_GE(key_size_in_bits, 8); |
+ |
+ ScopedHCRYPTPROV provider_handle; |
+ ScopedHCRYPTKEY key_handle; |
+ |
+ bool ok = false; |
+ |
+ scoped_array<BYTE> raw_key; |
+ if (algorithm == AES) { |
+ ok = GenerateAESKey(key_size_in_bits, &provider_handle, &key_handle); |
+ } else if (algorithm == HMAC_SHA1) { |
+ ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider_handle, |
+ &key_handle, &raw_key); |
+ } |
+ |
+ if (ok) { |
+ size_t key_size_in_bytes = key_size_in_bits / 8; |
+ if (raw_key == NULL) |
+ key_size_in_bytes = 0; |
+ |
+ SymmetricKey* result = new SymmetricKey(provider_handle.release(), |
+ key_handle.release(), |
+ raw_key.get(), |
+ key_size_in_bytes); |
+ if (raw_key != NULL) |
+ SecureZeroMemory(raw_key.get(), key_size_in_bytes); |
+ |
+ return result; |
+ } |
+ |
+ NOTREACHED(); |
return NULL; |
} |
@@ -20,11 +347,146 @@ |
const std::string& salt, |
size_t iterations, |
size_t key_size_in_bits) { |
- return NULL; |
+ // CryptoAPI lacks native routines to perform PBKDF2 derivation as specified |
+ // in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is |
+ // supported as the PRF. |
+ if (algorithm != HMAC_SHA1 && algorithm != AES) |
+ return NULL; |
+ |
+ // While not used until the end, sanity-check the input before proceding with |
+ // the expensive computation. |
+ DWORD provider_type = 0; |
+ ALG_ID alg = 0; |
+ switch (algorithm) { |
+ case AES: |
+ provider_type = PROV_RSA_AES; |
+ alg = GetAESAlgIDForKeySize(key_size_in_bits); |
+ break; |
+ case HMAC_SHA1: |
+ provider_type = PROV_RSA_FULL; |
+ alg = CALG_HMAC; |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+ if (alg == 0 || provider_type == 0) |
+ return NULL; |
+ |
+ ScopedHCRYPTPROV provider; |
+ BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type, |
+ CRYPT_VERIFYCONTEXT); |
+ if (!ok) |
+ return false; |
+ |
+ // Convert the user password into a key suitable to be fed into the PRF |
+ // function. |
+ ScopedHCRYPTKEY password_as_key; |
+ BYTE* password_as_bytes = |
+ const_cast<BYTE*>(reinterpret_cast<const BYTE*>(password.data())); |
+ if (!ImportRawKey(provider, CALG_HMAC, password_as_bytes, |
+ password.size(), &password_as_key)) |
+ return NULL; |
+ |
+ // Configure the PRF function. Only HMAC variants are supported, with the |
+ // only hash function supported being SHA1. |
+ // TODO(rsleevi): Support SHA-256 on XP SP3+. |
+ ScopedHCRYPTHASH prf; |
+ if (!CreateHMACHash(provider, password_as_key, CALG_SHA1, &prf)) |
+ return false; |
+ |
+ DWORD hLen = 0; |
+ DWORD param_size = sizeof(hLen); |
+ ok = CryptGetHashParam(prf, HP_HASHSIZE, |
+ reinterpret_cast<BYTE*>(&hLen), ¶m_size, 0); |
+ if (!ok || hLen == 0) |
+ return false; |
+ |
+ // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop. |
+ size_t dkLen = key_size_in_bits / 8; |
+ DCHECK_GT(dkLen, 0); |
+ |
+ if ((dkLen / hLen) > 0xFFFFFFFF) { |
+ DLOG(ERROR) << "Derived key too long."; |
+ return NULL; |
+ } |
+ |
+ // 2. Let l be the number of hLen-octet blocks in the derived key, |
+ // rounding up, and let r be the number of octets in the last |
+ // block: |
+ size_t l = dkLen / hLen; |
+ size_t r = hLen - (dkLen % hLen); |
+ if (r != hLen) ++l; |
+ |
+ DCHECK_GT(l, 0); |
+ |
+ size_t total_generated_size = l * hLen; |
+ scoped_array<BYTE> generated_key(new BYTE[total_generated_size]); |
+ BYTE* block_offset = generated_key.get(); |
+ |
+ // 3. For each block of the derived key apply the function F defined below |
+ // to the password P, the salt S, the iteration count c, and the block |
+ // index to compute the block: |
+ // T_1 = F (P, S, c, 1) |
+ // T_2 = F (P, S, c, 2) |
+ // ... |
+ // T_l = F (P, S, c, l) |
+ // <snip> |
+ // 4. Concatenate the blocks and extract the first dkLen octets to produce |
+ // a derived key DK: |
+ // DK = T_1 || T_2 || ... || T_l<0..r-1> |
+ bool error = false; |
+ for (size_t cur_block = 1; cur_block <= l && !error; ++cur_block) { |
+ if (!ComputePBKDF2Block(provider, password_as_key, prf, |
+ salt, iterations, cur_block, block_offset)) { |
+ error = true; |
+ } |
+ block_offset += hLen; |
+ } |
+ |
+ if (error) |
+ return NULL; |
+ |
+ // Convert the derived key bytes into a key handle for the desired algorithm. |
+ ScopedHCRYPTKEY key; |
+ if (!ImportRawKey(provider, alg, generated_key.get(), |
+ dkLen, &key)) |
+ return NULL; |
+ |
+ SymmetricKey* result = new SymmetricKey(provider.release(), key.release(), |
+ generated_key.get(), dkLen); |
+ |
+ SecureZeroMemory(generated_key.get(), total_generated_size); |
+ |
+ return result; |
} |
bool SymmetricKey::GetRawKey(std::string* raw_key) { |
- return false; |
+ // Short circuit for when the key was supplied during initialization |
+ if (!raw_key_.empty()) { |
+ *raw_key = raw_key_; |
+ return true; |
+ } |
+ |
+ DWORD size = 0; |
+ BOOL ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0, NULL, &size); |
+ if (!ok && GetLastError() != ERROR_MORE_DATA) |
+ return false; |
+ |
+ scoped_array<BYTE> result(new BYTE[size]); |
+ |
+ ok = CryptExportKey(key_, NULL, PLAINTEXTKEYBLOB, 0, |
+ result.get(), &size); |
+ if (!ok) |
+ return false; |
+ |
+ PlaintextBlobHeader* header = |
+ reinterpret_cast<PlaintextBlobHeader*>(result.get()); |
+ raw_key->assign(reinterpret_cast<char*>(result.get() + sizeof(*header)), |
+ header->cbKeySize); |
+ |
+ SecureZeroMemory(result.get(), size); |
+ |
+ return true; |
} |
} // namespace base |