diff options
-rw-r--r-- | build/build_config.h | 3 | ||||
-rw-r--r-- | crypto/crypto.gyp | 1 | ||||
-rw-r--r-- | crypto/encryptor.cc | 120 | ||||
-rw-r--r-- | crypto/encryptor.h | 68 | ||||
-rw-r--r-- | crypto/encryptor_nss.cc | 167 | ||||
-rw-r--r-- | crypto/encryptor_unittest.cc | 68 |
6 files changed, 378 insertions, 49 deletions
diff --git a/build/build_config.h b/build/build_config.h index 0bfccd7..3742a080 100644 --- a/build/build_config.h +++ b/build/build_config.h @@ -95,14 +95,17 @@ #define ARCH_CPU_X86_FAMILY 1 #define ARCH_CPU_X86_64 1 #define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 #elif defined(_M_IX86) || defined(__i386__) #define ARCH_CPU_X86_FAMILY 1 #define ARCH_CPU_X86 1 #define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 #elif defined(__ARMEL__) #define ARCH_CPU_ARM_FAMILY 1 #define ARCH_CPU_ARMEL 1 #define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 #define WCHAR_T_IS_UNSIGNED 1 #else #error Please add support for your architecture in build/build_config.h diff --git a/crypto/crypto.gyp b/crypto/crypto.gyp index e99ea75..7311ace 100644 --- a/crypto/crypto.gyp +++ b/crypto/crypto.gyp @@ -109,6 +109,7 @@ 'crypto_module_blocking_password_delegate.h', 'cssm_init.cc', 'cssm_init.h', + 'encryptor.cc', 'encryptor.h', 'encryptor_mac.cc', 'encryptor_nss.cc', diff --git a/crypto/encryptor.cc b/crypto/encryptor.cc new file mode 100644 index 0000000..59988f8 --- /dev/null +++ b/crypto/encryptor.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2011 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/encryptor.h" + +#include "base/logging.h" +#include "build/build_config.h" + +// Include headers to provide bswap for all platforms. +#if defined(COMPILER_MSVC) +#include <stdlib.h> +#define bswap_16(x) _byteswap_ushort(x) +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) +#elif defined(OS_MACOSX) +#include <libkern/OSByteOrder.h> +#define bswap_16(x) OSSwapInt16(x) +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) +#else +#include <byteswap.h> +#endif + +#if defined(ARCH_CPU_LITTLE_ENDIAN) +#define ntoh_64(x) bswap_64(x) +#define hton_64(x) bswap_64(x) +#else +#define ntoh_64(x) (x) +#define hton_64(x) (x) +#endif + +namespace crypto { + +///////////////////////////////////////////////////////////////////////////// +// Encyptor::Counter Implementation. +Encryptor::Counter::Counter(const std::string& counter) { + CHECK(sizeof(counter_) == counter.length()); + + memcpy(&counter_, counter.data(), sizeof(counter_)); +} + +Encryptor::Counter::~Counter() { +} + +bool Encryptor::Counter::Increment() { + uint64 low_num = ntoh_64(counter_.components64[1]); + uint64 new_low_num = low_num + 1; + counter_.components64[1] = hton_64(new_low_num); + + // If overflow occured then increment the most significant component. + if (new_low_num < low_num) { + counter_.components64[0] = + hton_64(ntoh_64(counter_.components64[0]) + 1); + } + + // TODO(hclam): Return false if counter value overflows. + return true; +} + +void Encryptor::Counter::Write(void* buf) { + uint8* buf_ptr = reinterpret_cast<uint8*>(buf); + memcpy(buf_ptr, &counter_, sizeof(counter_)); +} + +size_t Encryptor::Counter::GetLengthInBytes() const { + return sizeof(counter_); +} + +///////////////////////////////////////////////////////////////////////////// +// Partial Encryptor Implementation. + +bool Encryptor::SetCounter(const std::string& counter) { + if (mode_ != CTR) + return false; + if (counter.length() != 16u) + return false; + + counter_.reset(new Counter(counter)); + return true; +} + +bool Encryptor::GenerateCounterMask(size_t plaintext_len, + uint8* mask, + size_t* mask_len) { + DCHECK_EQ(CTR, mode_); + CHECK(mask); + CHECK(mask_len); + + const size_t kBlockLength = counter_->GetLengthInBytes(); + size_t blocks = (plaintext_len + kBlockLength - 1) / kBlockLength; + CHECK(blocks); + + *mask_len = blocks * kBlockLength; + + for (size_t i = 0; i < blocks; ++i) { + counter_->Write(mask); + mask += kBlockLength; + + bool ret = counter_->Increment(); + if (!ret) + return false; + } + return true; +} + +void Encryptor::MaskMessage(const void* plaintext, + size_t plaintext_len, + const void* mask, + void* ciphertext) const { + DCHECK_EQ(CTR, mode_); + const uint8* plaintext_ptr = reinterpret_cast<const uint8*>(plaintext); + const uint8* mask_ptr = reinterpret_cast<const uint8*>(mask); + uint8* ciphertext_ptr = reinterpret_cast<uint8*>(ciphertext); + + for (size_t i = 0; i < plaintext_len; ++i) + ciphertext_ptr[i] = plaintext_ptr[i] ^ mask_ptr[i]; +} + +} // namespace crypto diff --git a/crypto/encryptor.h b/crypto/encryptor.h index 0fdf758..712a19c 100644 --- a/crypto/encryptor.h +++ b/crypto/encryptor.h @@ -8,6 +8,8 @@ #include <string> +#include "base/basictypes.h" +#include "base/scoped_ptr.h" #include "build/build_config.h" #include "crypto/crypto_api.h" @@ -24,13 +26,41 @@ class SymmetricKey; class CRYPTO_API Encryptor { public: enum Mode { - CBC + CBC, + CTR, }; + + // This class implements a 128-bits counter to be used in AES-CTR encryption. + // Only 128-bits counter is supported in this class. + class Counter { + public: + Counter(const std::string& counter); + ~Counter(); + + // Increment the counter value. + bool Increment(); + + // Write the content of the counter to |buf|. |buf| should have enough + // space for |GetLengthInBytes()|. + void Write(void* buf); + + // Return the length of this counter. + size_t GetLengthInBytes() const; + + private: + union { + uint32 components32[4]; + uint64 components64[2]; + } counter_; + }; + Encryptor(); virtual ~Encryptor(); // Initializes the encryptor using |key| and |iv|. Returns false if either the // key or the initialization vector cannot be used. + // + // When |mode| is CTR then |iv| should be empty. bool Init(SymmetricKey* key, Mode mode, const std::string& iv); // Encrypts |plaintext| into |ciphertext|. @@ -39,11 +69,41 @@ class CRYPTO_API Encryptor { // Decrypts |ciphertext| into |plaintext|. bool Decrypt(const std::string& ciphertext, std::string* plaintext); + // Sets the counter value when in CTR mode. Currently only 128-bits + // counter value is supported. + // + // Returns true only if update was successful. + bool SetCounter(const std::string& counter); + // TODO(albertb): Support streaming encryption. private: + // Generates a mask using |counter_| to be used for encryption in CTR mode. + // Resulting mask will be written to |mask| with |mask_len| bytes. + // + // Make sure there's enough space in mask when calling this method. + // Reserve at least |plaintext_len| + 16 bytes for |mask|. + // + // The generated mask will always have at least |plaintext_len| bytes and + // will be a multiple of the counter length. + // + // This method is used only in CTR mode. + // + // Returns false if this call failed. + bool GenerateCounterMask(size_t plaintext_len, + uint8* mask, + size_t* mask_len); + + // Mask the |plaintext| message using |mask|. The output will be written to + // |ciphertext|. |ciphertext| must have at least |plaintext_len| bytes. + void MaskMessage(const void* plaintext, + size_t plaintext_len, + const void* mask, + void* ciphertext) const; + SymmetricKey* key_; Mode mode_; + scoped_ptr<Counter> counter_; #if defined(USE_OPENSSL) bool Crypt(bool encrypt, // Pass true to encrypt, false to decrypt. @@ -51,6 +111,12 @@ class CRYPTO_API Encryptor { std::string* output); std::string iv_; #elif defined(USE_NSS) + bool Crypt(PK11Context* context, + const std::string& input, + std::string* output); + bool CryptCTR(PK11Context* context, + const std::string& input, + std::string* output); ScopedPK11Slot slot_; ScopedSECItem param_; #elif defined(OS_MACOSX) diff --git a/crypto/encryptor_nss.cc b/crypto/encryptor_nss.cc index aaa6626..c53fc10 100644 --- a/crypto/encryptor_nss.cc +++ b/crypto/encryptor_nss.cc @@ -13,6 +13,25 @@ namespace crypto { +namespace { + +inline CK_MECHANISM_TYPE GetMechanism(Encryptor::Mode mode) { + switch (mode) { + case Encryptor::CBC: + return CKM_AES_CBC_PAD; + case Encryptor::CTR: + // AES-CTR encryption uses ECB encryptor as a building block since + // NSS doesn't support CTR encryption mode. + return CKM_AES_ECB; + default: + NOTREACHED() << "Unsupported mode of operation"; + break; + } + return static_cast<CK_MECHANISM_TYPE>(-1); +} + +} // namespace + Encryptor::Encryptor() : key_(NULL), mode_(CBC) { @@ -24,101 +43,153 @@ Encryptor::~Encryptor() { bool Encryptor::Init(SymmetricKey* key, Mode mode, const std::string& iv) { DCHECK(key); - DCHECK_EQ(CBC, mode); + DCHECK(CBC == mode || CTR == mode) << "Unsupported mode of operation"; key_ = key; mode_ = mode; - if (iv.size() != AES_BLOCK_SIZE) + if (mode == CBC && iv.size() != AES_BLOCK_SIZE) return false; - slot_.reset(PK11_GetBestSlot(CKM_AES_CBC_PAD, NULL)); + slot_.reset(PK11_GetBestSlot(GetMechanism(mode), NULL)); if (!slot_.get()) return false; - SECItem iv_item; - iv_item.type = siBuffer; - iv_item.data = reinterpret_cast<unsigned char*>( - const_cast<char *>(iv.data())); - iv_item.len = iv.size(); + switch (mode) { + case CBC: + SECItem iv_item; + iv_item.type = siBuffer; + iv_item.data = reinterpret_cast<unsigned char*>( + const_cast<char *>(iv.data())); + iv_item.len = iv.size(); + + param_.reset(PK11_ParamFromIV(GetMechanism(mode), &iv_item)); + break; + case CTR: + param_.reset(PK11_ParamFromIV(GetMechanism(mode), NULL)); + break; + } - param_.reset(PK11_ParamFromIV(CKM_AES_CBC_PAD, &iv_item)); if (!param_.get()) return false; - return true; } bool Encryptor::Encrypt(const std::string& plaintext, std::string* ciphertext) { - ScopedPK11Context context(PK11_CreateContextBySymKey(CKM_AES_CBC_PAD, + ScopedPK11Context context(PK11_CreateContextBySymKey(GetMechanism(mode_), CKA_ENCRYPT, key_->key(), param_.get())); if (!context.get()) return false; - size_t ciphertext_len = plaintext.size() + AES_BLOCK_SIZE; - std::vector<unsigned char> buffer(ciphertext_len); + if (mode_ == CTR) + return CryptCTR(context.get(), plaintext, ciphertext); + else + return Crypt(context.get(), plaintext, ciphertext); +} + +bool Encryptor::Decrypt(const std::string& ciphertext, std::string* plaintext) { + if (ciphertext.empty()) + return false; + + ScopedPK11Context context(PK11_CreateContextBySymKey( + GetMechanism(mode_), (mode_ == CTR ? CKA_ENCRYPT : CKA_DECRYPT), + key_->key(), param_.get())); + if (!context.get()) + return false; + + if (mode_ == CTR) + return CryptCTR(context.get(), ciphertext, plaintext); + else + return Crypt(context.get(), ciphertext, plaintext); +} + +bool Encryptor::Crypt(PK11Context* context, const std::string& input, + std::string* output) { + size_t output_len = input.size() + AES_BLOCK_SIZE; + CHECK(output_len > input.size()) << "Output size overflow"; + + output->resize(output_len); + uint8* output_data = + reinterpret_cast<uint8*>(const_cast<char*>(output->data())); + + int input_len = input.size(); + uint8* input_data = + reinterpret_cast<uint8*>(const_cast<char*>(input.data())); int op_len; - SECStatus rv = PK11_CipherOp(context.get(), - &buffer[0], + SECStatus rv = PK11_CipherOp(context, + output_data, &op_len, - ciphertext_len, - reinterpret_cast<unsigned char*>( - const_cast<char*>(plaintext.data())), - plaintext.size()); - if (SECSuccess != rv) + output_len, + input_data, + input_len); + + if (SECSuccess != rv) { + output->clear(); return false; + } unsigned int digest_len; - rv = PK11_DigestFinal(context.get(), - &buffer[op_len], + rv = PK11_DigestFinal(context, + output_data + op_len, &digest_len, - ciphertext_len - op_len); - if (SECSuccess != rv) + output_len - op_len); + if (SECSuccess != rv) { + output->clear(); return false; + } - ciphertext->assign(reinterpret_cast<char *>(&buffer[0]), - op_len + digest_len); + output->resize(op_len + digest_len); return true; } -bool Encryptor::Decrypt(const std::string& ciphertext, std::string* plaintext) { - if (ciphertext.empty()) +bool Encryptor::CryptCTR(PK11Context* context, const std::string& input, + std::string* output) { + if (!counter_.get()) { + LOG(ERROR) << "Counter value not set in CTR mode."; return false; - - ScopedPK11Context context(PK11_CreateContextBySymKey(CKM_AES_CBC_PAD, - CKA_DECRYPT, - key_->key(), - param_.get())); - if (!context.get()) + } + + size_t output_len = ((input.size() + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE) * + AES_BLOCK_SIZE; + CHECK(output_len >= input.size()) << "Output size overflow"; + output->resize(output_len); + uint8* output_data = + reinterpret_cast<uint8*>(const_cast<char*>(output->data())); + + size_t mask_len; + bool ret = GenerateCounterMask(input.size(), output_data, &mask_len); + if (!ret) return false; - size_t plaintext_len = ciphertext.size(); - std::vector<unsigned char> buffer(plaintext_len); - + CHECK_EQ(mask_len, output_len); int op_len; - SECStatus rv = PK11_CipherOp(context.get(), - &buffer[0], + SECStatus rv = PK11_CipherOp(context, + output_data, &op_len, - plaintext_len, - reinterpret_cast<unsigned char*>( - const_cast<char*>(ciphertext.data())), - ciphertext.size()); + output_len, + output_data, + mask_len); if (SECSuccess != rv) return false; + CHECK(op_len == static_cast<int>(mask_len)); unsigned int digest_len; - rv = PK11_DigestFinal(context.get(), - &buffer[op_len], + rv = PK11_DigestFinal(context, + NULL, &digest_len, - plaintext_len - op_len); + 0); if (SECSuccess != rv) return false; + CHECK(!digest_len); - plaintext->assign(reinterpret_cast<char *>(&buffer[0]), - op_len + digest_len); + // Use |output_data| to mask |input|. + MaskMessage( + reinterpret_cast<uint8*>(const_cast<char*>(input.data())), + input.length(), output_data, output_data); + output->resize(input.length()); return true; } diff --git a/crypto/encryptor_unittest.cc b/crypto/encryptor_unittest.cc index b916854..2d710bc 100644 --- a/crypto/encryptor_unittest.cc +++ b/crypto/encryptor_unittest.cc @@ -35,6 +35,74 @@ TEST(EncryptorTest, EncryptDecrypt) { EXPECT_EQ(plaintext, decypted); } +// CTR mode encryption is only implemented using NSS. +#if defined(USE_NSS) + +TEST(EncryptorTest, EncryptDecryptCTR) { + scoped_ptr<crypto::SymmetricKey> key( + crypto::SymmetricKey::GenerateRandomKey( + crypto::SymmetricKey::AES, 128)); + + EXPECT_TRUE(NULL != key.get()); + const std::string kInitialCounter = "0000000000000000"; + + crypto::Encryptor encryptor; + EXPECT_TRUE(encryptor.Init(key.get(), crypto::Encryptor::CTR, "")); + EXPECT_TRUE(encryptor.SetCounter(kInitialCounter)); + + std::string plaintext("normal plaintext of random length"); + std::string ciphertext; + EXPECT_TRUE(encryptor.Encrypt(plaintext, &ciphertext)); + EXPECT_LT(0U, ciphertext.size()); + + std::string decypted; + EXPECT_TRUE(encryptor.SetCounter(kInitialCounter)); + EXPECT_TRUE(encryptor.Decrypt(ciphertext, &decypted)); + EXPECT_EQ(plaintext, decypted); + + plaintext = "0123456789012345"; + EXPECT_TRUE(encryptor.SetCounter(kInitialCounter)); + EXPECT_TRUE(encryptor.Encrypt(plaintext, &ciphertext)); + EXPECT_LT(0U, ciphertext.size()); + + EXPECT_TRUE(encryptor.SetCounter(kInitialCounter)); + EXPECT_TRUE(encryptor.Decrypt(ciphertext, &decypted)); + EXPECT_EQ(plaintext, decypted); +} + +TEST(EncryptorTest, CTRCounter) { + const int kCounterSize = 16; + const char kTest1[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8 buf[16]; + + // Increment 10 times. + crypto::Encryptor::Counter counter1(std::string(kTest1, kCounterSize)); + for (int i = 0; i < 10; ++i) + counter1.Increment(); + counter1.Write(buf); + EXPECT_EQ(0, memcmp(buf, kTest1, 15)); + EXPECT_TRUE(buf[15] == 10); + + // Check corner cases. + const char kTest2[] = {0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const char kExpect2[] = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}; + crypto::Encryptor::Counter counter2(std::string(kTest2, kCounterSize)); + counter2.Increment(); + counter2.Write(buf); + EXPECT_EQ(0, memcmp(buf, kExpect2, kCounterSize)); + + const char kTest3[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const char kExpect3[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + crypto::Encryptor::Counter counter3(std::string(kTest3, kCounterSize)); + counter3.Increment(); + counter3.Write(buf); + EXPECT_EQ(0, memcmp(buf, kExpect3, kCounterSize)); +} + +#endif + // TODO(wtc): add more known-answer tests. Test vectors are available from // http://www.ietf.org/rfc/rfc3602 // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf |