539 lines
17 KiB
C++
539 lines
17 KiB
C++
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "crypto/symmetric_key.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <vector>
|
|
|
|
// TODO(wtc): replace scoped_array by std::vector.
|
|
#include "base/memory/scoped_ptr.h"
|
|
#include "base/sys_byteorder.h"
|
|
|
|
namespace crypto {
|
|
|
|
namespace {
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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, whereas 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:
|
|
NOTREACHED();
|
|
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|.
|
|
// TODO(wtc): use this function in hmac_win.cc.
|
|
bool ImportRawKey(HCRYPTPROV provider,
|
|
ALG_ID alg,
|
|
const void* key_data, size_t key_size,
|
|
ScopedHCRYPTKEY* key) {
|
|
DCHECK_GT(key_size, 0u);
|
|
|
|
DWORD actual_size =
|
|
static_cast<DWORD>(sizeof(PlaintextBlobHeader) + key_size);
|
|
std::vector<BYTE> tmp_data(actual_size);
|
|
BYTE* actual_key = &tmp_data[0];
|
|
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->hdr.bType = PLAINTEXTKEYBLOB;
|
|
key_header->hdr.bVersion = CUR_BLOB_VERSION;
|
|
key_header->hdr.aiKeyAlg = alg;
|
|
|
|
key_header->cbKeySize = static_cast<DWORD>(key_size);
|
|
|
|
HCRYPTKEY unsafe_key = NULL;
|
|
DWORD flags = 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;
|
|
flags |= CRYPT_IPSEC_HMAC_KEY;
|
|
}
|
|
|
|
BOOL ok =
|
|
CryptImportKey(provider, actual_key, actual_size, 0, flags, &unsafe_key);
|
|
|
|
// Clean up the temporary copy of key, regardless of whether it was imported
|
|
// successfully or not.
|
|
SecureZeroMemory(actual_key, 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 pszContainer 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;
|
|
}
|
|
|
|
// Returns true if the HMAC key size meets the requirement of FIPS 198
|
|
// Section 3. |alg| is the hash function used in the HMAC.
|
|
bool CheckHMACKeySize(size_t key_size_in_bits, ALG_ID alg) {
|
|
DWORD hash_size = 0;
|
|
switch (alg) {
|
|
case CALG_SHA1:
|
|
hash_size = 20;
|
|
break;
|
|
case CALG_SHA_256:
|
|
hash_size = 32;
|
|
break;
|
|
case CALG_SHA_384:
|
|
hash_size = 48;
|
|
break;
|
|
case CALG_SHA_512:
|
|
hash_size = 64;
|
|
break;
|
|
}
|
|
if (hash_size == 0)
|
|
return false;
|
|
|
|
// An HMAC key must be >= L/2, where L is the output size of the hash
|
|
// function being used.
|
|
return (key_size_in_bits >= (hash_size / 2 * 8) &&
|
|
(key_size_in_bits % 8) == 0);
|
|
}
|
|
|
|
// 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_ptr<BYTE[]>* raw_key) {
|
|
DCHECK(provider);
|
|
DCHECK(key);
|
|
DCHECK(raw_key);
|
|
|
|
if (!CheckHMACKeySize(key_size_in_bits, alg))
|
|
return false;
|
|
|
|
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;
|
|
|
|
DWORD key_size_in_bytes = static_cast<DWORD>(key_size_in_bits / 8);
|
|
scoped_ptr<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;
|
|
bool rv = ImportRawKey(safe_provider, CALG_HMAC, random.get(),
|
|
key_size_in_bytes, &safe_key);
|
|
if (rv) {
|
|
key->swap(safe_key);
|
|
provider->swap(safe_provider);
|
|
raw_key->swap(random);
|
|
}
|
|
|
|
SecureZeroMemory(random.get(), key_size_in_bytes);
|
|
return rv;
|
|
}
|
|
|
|
// 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|.
|
|
// TODO(wtc): use this function in hmac_win.cc.
|
|
bool CreateHMACHash(HCRYPTPROV provider,
|
|
HCRYPTKEY key,
|
|
ALG_ID hash_alg,
|
|
ScopedHCRYPTHASH* hash) {
|
|
ScopedHCRYPTHASH safe_hash;
|
|
BOOL 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;
|
|
}
|
|
|
|
// Computes a block of the derived key using the PBKDF2 function F for the
|
|
// specified |block_index| 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(HCRYPTHASH hash,
|
|
DWORD hash_size,
|
|
const std::string& salt,
|
|
size_t iterations,
|
|
uint32_t block_index,
|
|
BYTE* output_buf) {
|
|
// From RFC 2898:
|
|
// 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, NULL, 0, safe_hash.receive());
|
|
if (!ok)
|
|
return false;
|
|
|
|
// Iteration U_1: Compute PRF for S.
|
|
ok = CryptHashData(safe_hash, reinterpret_cast<const BYTE*>(salt.data()),
|
|
static_cast<DWORD>(salt.size()), 0);
|
|
if (!ok)
|
|
return false;
|
|
|
|
// Iteration U_1: and append (big-endian) INT (i).
|
|
uint32_t big_endian_block_index = base::HostToNet32(block_index);
|
|
ok = CryptHashData(safe_hash,
|
|
reinterpret_cast<BYTE*>(&big_endian_block_index),
|
|
sizeof(big_endian_block_index), 0);
|
|
|
|
std::vector<BYTE> hash_value(hash_size);
|
|
|
|
DWORD size = hash_size;
|
|
ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0);
|
|
if (!ok || size != hash_size)
|
|
return false;
|
|
|
|
memcpy(output_buf, &hash_value[0], hash_size);
|
|
|
|
// Iteration 2 - c: Compute U_{iteration} by applying the PRF to
|
|
// U_{iteration - 1}, 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, NULL, 0, safe_hash.receive());
|
|
if (!ok)
|
|
return false;
|
|
|
|
ok = CryptHashData(safe_hash, &hash_value[0], hash_size, 0);
|
|
if (!ok)
|
|
return false;
|
|
|
|
size = hash_size;
|
|
ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0);
|
|
if (!ok || size != hash_size)
|
|
return false;
|
|
|
|
for (DWORD i = 0; i < hash_size; ++i)
|
|
output_buf[i] ^= hash_value[i];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SymmetricKey::~SymmetricKey() {
|
|
// TODO(wtc): create a "secure" string type that zeroes itself in the
|
|
// destructor.
|
|
if (!raw_key_.empty())
|
|
SecureZeroMemory(const_cast<char *>(raw_key_.data()), raw_key_.size());
|
|
}
|
|
|
|
// static
|
|
SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm,
|
|
size_t key_size_in_bits) {
|
|
DCHECK_GE(key_size_in_bits, 8u);
|
|
|
|
ScopedHCRYPTPROV provider;
|
|
ScopedHCRYPTKEY key;
|
|
|
|
bool ok = false;
|
|
scoped_ptr<BYTE[]> raw_key;
|
|
|
|
switch (algorithm) {
|
|
case AES:
|
|
ok = GenerateAESKey(key_size_in_bits, &provider, &key);
|
|
break;
|
|
case HMAC_SHA1:
|
|
ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider,
|
|
&key, &raw_key);
|
|
break;
|
|
}
|
|
|
|
if (!ok) {
|
|
NOTREACHED();
|
|
return NULL;
|
|
}
|
|
|
|
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.release(),
|
|
key.release(),
|
|
raw_key.get(),
|
|
key_size_in_bytes);
|
|
if (raw_key != NULL)
|
|
SecureZeroMemory(raw_key.get(), key_size_in_bytes);
|
|
|
|
return result;
|
|
}
|
|
|
|
// static
|
|
SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm,
|
|
const std::string& password,
|
|
const std::string& salt,
|
|
size_t iterations,
|
|
size_t key_size_in_bits) {
|
|
// CryptoAPI lacks routines to perform PBKDF2 derivation as specified
|
|
// in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is
|
|
// supported as the PRF.
|
|
|
|
// While not used until the end, sanity-check the input before proceeding
|
|
// 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();
|
|
break;
|
|
}
|
|
if (provider_type == 0 || alg == 0)
|
|
return NULL;
|
|
|
|
ScopedHCRYPTPROV provider;
|
|
BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type,
|
|
CRYPT_VERIFYCONTEXT);
|
|
if (!ok)
|
|
return NULL;
|
|
|
|
// 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 NULL;
|
|
|
|
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 NULL;
|
|
|
|
// 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, 0u);
|
|
|
|
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 - 1) / hLen;
|
|
DCHECK_GT(L, 0u);
|
|
|
|
size_t total_generated_size = L * hLen;
|
|
std::vector<BYTE> generated_key(total_generated_size);
|
|
BYTE* block_offset = &generated_key[0];
|
|
|
|
// 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>
|
|
for (uint32_t block_index = 1; block_index <= L; ++block_index) {
|
|
if (!ComputePBKDF2Block(prf, hLen, salt, iterations, block_index,
|
|
block_offset))
|
|
return NULL;
|
|
block_offset += hLen;
|
|
}
|
|
|
|
// Convert the derived key bytes into a key handle for the desired algorithm.
|
|
ScopedHCRYPTKEY key;
|
|
if (!ImportRawKey(provider, alg, &generated_key[0], dkLen, &key))
|
|
return NULL;
|
|
|
|
SymmetricKey* result = new SymmetricKey(provider.release(), key.release(),
|
|
&generated_key[0], dkLen);
|
|
|
|
SecureZeroMemory(&generated_key[0], total_generated_size);
|
|
|
|
return result;
|
|
}
|
|
|
|
// static
|
|
SymmetricKey* SymmetricKey::Import(Algorithm algorithm,
|
|
const std::string& raw_key) {
|
|
DWORD provider_type = 0;
|
|
ALG_ID alg = 0;
|
|
switch (algorithm) {
|
|
case AES:
|
|
provider_type = PROV_RSA_AES;
|
|
alg = GetAESAlgIDForKeySize(raw_key.size() * 8);
|
|
break;
|
|
case HMAC_SHA1:
|
|
provider_type = PROV_RSA_FULL;
|
|
alg = CALG_HMAC;
|
|
break;
|
|
default:
|
|
NOTREACHED();
|
|
break;
|
|
}
|
|
if (provider_type == 0 || alg == 0)
|
|
return NULL;
|
|
|
|
ScopedHCRYPTPROV provider;
|
|
BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type,
|
|
CRYPT_VERIFYCONTEXT);
|
|
if (!ok)
|
|
return NULL;
|
|
|
|
ScopedHCRYPTKEY key;
|
|
if (!ImportRawKey(provider, alg, raw_key.data(), raw_key.size(), &key))
|
|
return NULL;
|
|
|
|
return new SymmetricKey(provider.release(), key.release(),
|
|
raw_key.data(), raw_key.size());
|
|
}
|
|
|
|
bool SymmetricKey::GetRawKey(std::string* raw_key) {
|
|
// Short circuit for when the key was supplied to the constructor.
|
|
if (!raw_key_.empty()) {
|
|
*raw_key = raw_key_;
|
|
return true;
|
|
}
|
|
|
|
DWORD size = 0;
|
|
BOOL ok = CryptExportKey(key_, 0, PLAINTEXTKEYBLOB, 0, NULL, &size);
|
|
if (!ok)
|
|
return false;
|
|
|
|
std::vector<BYTE> result(size);
|
|
|
|
ok = CryptExportKey(key_, 0, PLAINTEXTKEYBLOB, 0, &result[0], &size);
|
|
if (!ok)
|
|
return false;
|
|
|
|
PlaintextBlobHeader* header =
|
|
reinterpret_cast<PlaintextBlobHeader*>(&result[0]);
|
|
raw_key->assign(reinterpret_cast<char*>(&result[sizeof(*header)]),
|
|
header->cbKeySize);
|
|
|
|
SecureZeroMemory(&result[0], size);
|
|
|
|
return true;
|
|
}
|
|
|
|
SymmetricKey::SymmetricKey(HCRYPTPROV provider,
|
|
HCRYPTKEY key,
|
|
const void* key_data, size_t key_size_in_bytes)
|
|
: provider_(provider), key_(key) {
|
|
if (key_data) {
|
|
raw_key_.assign(reinterpret_cast<const char*>(key_data),
|
|
key_size_in_bytes);
|
|
}
|
|
}
|
|
|
|
} // namespace crypto
|