blob: eee629f0765e0b49540f1726479a87bf8168b8d5 [file] [log] [blame]
// 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 "components/sync/base/nigori.h"
#include <stdint.h>
#include <sstream>
#include <vector>
#include "base/base64.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_byteorder.h"
#include "base/time/default_tick_clock.h"
#include "components/sync/base/sync_base_switches.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/random.h"
#include "crypto/symmetric_key.h"
using base::Base64Encode;
using base::Base64Decode;
using crypto::HMAC;
using crypto::SymmetricKey;
const size_t kDerivedKeySizeInBits = 128;
const size_t kDerivedKeySizeInBytes = kDerivedKeySizeInBits / 8;
const size_t kHashSize = 32;
namespace syncer {
const char kNigoriKeyName[] = "nigori-key";
namespace {
// NigoriStream simplifies the concatenation operation of the Nigori protocol.
class NigoriStream {
public:
// Append the big-endian representation of the length of |value| with 32 bits,
// followed by |value| itself to the stream.
NigoriStream& operator<<(const std::string& value) {
uint32_t size = base::HostToNet32(value.size());
stream_.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
stream_ << value;
return *this;
}
// Append the big-endian representation of the length of |type| with 32 bits,
// followed by the big-endian representation of the value of |type|, with 32
// bits, to the stream.
NigoriStream& operator<<(const Nigori::Type type) {
uint32_t size = base::HostToNet32(sizeof(uint32_t));
stream_.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
uint32_t value = base::HostToNet32(type);
stream_.write(reinterpret_cast<char*>(&value), sizeof(uint32_t));
return *this;
}
std::string str() { return stream_.str(); }
private:
std::ostringstream stream_;
};
const char* GetHistogramSuffixForKeyDerivationMethod(
KeyDerivationMethod method) {
switch (method) {
case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
return "Pbkdf2";
case KeyDerivationMethod::SCRYPT_8192_8_11:
return "Scrypt8192";
case KeyDerivationMethod::UNSUPPORTED:
break;
}
NOTREACHED();
return "Unsupported";
}
} // namespace
KeyDerivationParams::KeyDerivationParams(KeyDerivationMethod method,
const std::string& scrypt_salt)
: method_(method),
scrypt_salt_(scrypt_salt) {}
KeyDerivationParams::KeyDerivationParams(const KeyDerivationParams& other) =
default;
KeyDerivationParams::KeyDerivationParams(KeyDerivationParams&& other) = default;
KeyDerivationParams& KeyDerivationParams::operator=(
const KeyDerivationParams& other) = default;
bool KeyDerivationParams::operator==(const KeyDerivationParams& other) const {
return method_ == other.method_ &&
scrypt_salt_ == other.scrypt_salt_;
}
bool KeyDerivationParams::operator!=(const KeyDerivationParams& other) const {
return !(*this == other);
}
const std::string& KeyDerivationParams::scrypt_salt() const {
DCHECK_EQ(method_, KeyDerivationMethod::SCRYPT_8192_8_11);
return scrypt_salt_;
}
KeyDerivationParams KeyDerivationParams::CreateForPbkdf2() {
return {KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003, /*scrypt_salt_=*/""};
}
KeyDerivationParams KeyDerivationParams::CreateForScrypt(
const std::string& salt) {
return {KeyDerivationMethod::SCRYPT_8192_8_11, salt};
}
KeyDerivationParams KeyDerivationParams::CreateWithUnsupportedMethod() {
return {KeyDerivationMethod::UNSUPPORTED, /*scrypt_salt_=*/""};
}
Nigori::Keys::Keys() = default;
Nigori::Keys::~Keys() = default;
bool Nigori::Keys::InitByDerivationUsingPbkdf2(const std::string& password) {
// Previously (<=M70) this value has been recalculated every time based on a
// constant hostname (hardcoded to "localhost") and username (hardcoded to
// "dummy") as PBKDF2_HMAC_SHA1(Ns("dummy") + Ns("localhost"), "saltsalt",
// 1001, 128), where Ns(S) is the NigoriStream representation of S (32-bit
// big-endian length of S followed by S itself).
const char kRawConstantSalt[] = {0xc7, 0xca, 0xfb, 0x23, 0xec, 0x2a,
0x9d, 0x4c, 0x03, 0x5a, 0x90, 0xae,
0xed, 0x8b, 0xa4, 0x98};
const size_t kUserIterations = 1002;
const size_t kEncryptionIterations = 1003;
const size_t kSigningIterations = 1004;
std::string salt(kRawConstantSalt, sizeof(kRawConstantSalt));
// Kuser = PBKDF2(P, Suser, Nuser, 16)
user_key = SymmetricKey::DeriveKeyFromPasswordUsingPbkdf2(
SymmetricKey::AES, password, salt, kUserIterations,
kDerivedKeySizeInBits);
DCHECK(user_key);
// Kenc = PBKDF2(P, Suser, Nenc, 16)
encryption_key = SymmetricKey::DeriveKeyFromPasswordUsingPbkdf2(
SymmetricKey::AES, password, salt, kEncryptionIterations,
kDerivedKeySizeInBits);
DCHECK(encryption_key);
// Kmac = PBKDF2(P, Suser, Nmac, 16)
mac_key = SymmetricKey::DeriveKeyFromPasswordUsingPbkdf2(
SymmetricKey::HMAC_SHA1, password, salt, kSigningIterations,
kDerivedKeySizeInBits);
DCHECK(mac_key);
return user_key && encryption_key && mac_key;
}
bool Nigori::Keys::InitByDerivationUsingScrypt(const std::string& salt,
const std::string& password) {
const size_t kCostParameter = 8192; // 2^13.
const size_t kBlockSize = 8;
const size_t kParallelizationParameter = 11;
const size_t kMaxMemoryBytes = 32 * 1024 * 1024; // 32 MiB.
// |user_key| is not used anymore. However, old clients may fail to import a
// Nigori node without one. We initialize it to all zeroes to prevent a
// failure on those clients.
user_key = SymmetricKey::Import(SymmetricKey::AES,
std::string(kDerivedKeySizeInBytes, '\0'));
// Derive a master key twice as long as the required key size, and split it
// into two to get the encryption and MAC keys.
std::unique_ptr<SymmetricKey> master_key =
SymmetricKey::DeriveKeyFromPasswordUsingScrypt(
SymmetricKey::AES, password, salt, kCostParameter, kBlockSize,
kParallelizationParameter, kMaxMemoryBytes,
2 * kDerivedKeySizeInBits);
std::string master_key_str = master_key->key();
std::string encryption_key_str =
master_key_str.substr(0, kDerivedKeySizeInBytes);
DCHECK_EQ(encryption_key_str.length(), kDerivedKeySizeInBytes);
encryption_key = SymmetricKey::Import(SymmetricKey::AES, encryption_key_str);
std::string mac_key_str = master_key_str.substr(kDerivedKeySizeInBytes);
DCHECK_EQ(mac_key_str.length(), kDerivedKeySizeInBytes);
mac_key = SymmetricKey::Import(SymmetricKey::HMAC_SHA1, mac_key_str);
return user_key && encryption_key && mac_key;
}
bool Nigori::Keys::InitByImport(const std::string& user_key_str,
const std::string& encryption_key_str,
const std::string& mac_key_str) {
user_key = SymmetricKey::Import(SymmetricKey::AES, user_key_str);
encryption_key = SymmetricKey::Import(SymmetricKey::AES, encryption_key_str);
DCHECK(encryption_key);
mac_key = SymmetricKey::Import(SymmetricKey::HMAC_SHA1, mac_key_str);
DCHECK(mac_key);
return encryption_key && mac_key;
}
Nigori::Nigori() : tick_clock_(base::DefaultTickClock::GetInstance()) {}
Nigori::~Nigori() {}
bool Nigori::InitByDerivation(const KeyDerivationParams& key_derivation_params,
const std::string& password) {
base::TimeTicks begin_time = tick_clock_->NowTicks();
bool result = false;
switch (key_derivation_params.method()) {
case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
result = keys_.InitByDerivationUsingPbkdf2(password);
break;
case KeyDerivationMethod::SCRYPT_8192_8_11:
DCHECK(!base::FeatureList::IsEnabled(
switches::kSyncForceDisableScryptForCustomPassphrase));
result = keys_.InitByDerivationUsingScrypt(
key_derivation_params.scrypt_salt(), password);
break;
case KeyDerivationMethod::UNSUPPORTED:
return false;
}
UmaHistogramTimes(
base::StringPrintf("Sync.Crypto.NigoriKeyDerivationDuration.%s",
GetHistogramSuffixForKeyDerivationMethod(
key_derivation_params.method())),
tick_clock_->NowTicks() - begin_time);
return result;
}
bool Nigori::InitByImport(const std::string& user_key,
const std::string& encryption_key,
const std::string& mac_key) {
return keys_.InitByImport(user_key, encryption_key, mac_key);
}
// Permute[Kenc,Kmac](type || name)
bool Nigori::Permute(Type type,
const std::string& name,
std::string* permuted) const {
DCHECK_LT(0U, name.size());
NigoriStream plaintext;
plaintext << type << name;
crypto::Encryptor encryptor;
if (!encryptor.Init(keys_.encryption_key.get(), crypto::Encryptor::CBC,
std::string(kIvSize, 0)))
return false;
std::string ciphertext;
if (!encryptor.Encrypt(plaintext.str(), &ciphertext))
return false;
HMAC hmac(HMAC::SHA256);
if (!hmac.Init(keys_.mac_key->key()))
return false;
std::vector<unsigned char> hash(kHashSize);
if (!hmac.Sign(ciphertext, &hash[0], hash.size()))
return false;
std::string output;
output.assign(ciphertext);
output.append(hash.begin(), hash.end());
Base64Encode(output, permuted);
return true;
}
// Enc[Kenc,Kmac](value)
bool Nigori::Encrypt(const std::string& value, std::string* encrypted) const {
if (0U >= value.size())
return false;
std::string iv;
crypto::RandBytes(base::WriteInto(&iv, kIvSize + 1), kIvSize);
crypto::Encryptor encryptor;
if (!encryptor.Init(keys_.encryption_key.get(), crypto::Encryptor::CBC, iv))
return false;
std::string ciphertext;
if (!encryptor.Encrypt(value, &ciphertext))
return false;
HMAC hmac(HMAC::SHA256);
if (!hmac.Init(keys_.mac_key->key()))
return false;
std::vector<unsigned char> hash(kHashSize);
if (!hmac.Sign(ciphertext, &hash[0], hash.size()))
return false;
std::string output;
output.assign(iv);
output.append(ciphertext);
output.append(hash.begin(), hash.end());
Base64Encode(output, encrypted);
return true;
}
bool Nigori::Decrypt(const std::string& encrypted, std::string* value) const {
std::string input;
if (!Base64Decode(encrypted, &input))
return false;
if (input.size() < kIvSize * 2 + kHashSize)
return false;
// The input is:
// * iv (16 bytes)
// * ciphertext (multiple of 16 bytes)
// * hash (32 bytes)
std::string iv(input.substr(0, kIvSize));
std::string ciphertext(
input.substr(kIvSize, input.size() - (kIvSize + kHashSize)));
std::string hash(input.substr(input.size() - kHashSize, kHashSize));
HMAC hmac(HMAC::SHA256);
if (!hmac.Init(keys_.mac_key->key()))
return false;
if (!hmac.Verify(ciphertext, hash))
return false;
crypto::Encryptor encryptor;
if (!encryptor.Init(keys_.encryption_key.get(), crypto::Encryptor::CBC, iv))
return false;
if (!encryptor.Decrypt(ciphertext, value))
return false;
return true;
}
void Nigori::ExportKeys(std::string* user_key,
std::string* encryption_key,
std::string* mac_key) const {
DCHECK(encryption_key);
DCHECK(mac_key);
DCHECK(user_key);
if (keys_.user_key)
*user_key = keys_.user_key->key();
else
user_key->clear();
*encryption_key = keys_.encryption_key->key();
*mac_key = keys_.mac_key->key();
}
// static
std::string Nigori::GenerateScryptSalt() {
static const size_t kSaltSizeInBytes = 32;
std::string salt;
salt.resize(kSaltSizeInBytes);
crypto::RandBytes(base::data(salt), salt.size());
return salt;
}
} // namespace syncer