Refactor and optimize cbc encryption/decryption

- Optimize and clean up encryption and decryption code.
- Consolidate various CBC encryption/decryption schemes into a
  common class.
- Make it a constructor argument whether cipher block chain is
  continuous across Encrypt/Decrypt calls.
- Also align protected region size as required in CENC spec.

Issue #77

Change-Id: I533d92ada3cd80933b532b9c3a1cca105ba66f8e
This commit is contained in:
KongQun Yang 2016-03-25 11:02:43 -07:00
parent a2438554f6
commit a9e5a2ff4f
13 changed files with 667 additions and 709 deletions

View File

@ -112,7 +112,7 @@ edash_packager::media::EncryptionMode GetEncryptionMode(
} else if (protection_scheme == "cbc1") { } else if (protection_scheme == "cbc1") {
return edash_packager::media::kEncryptionModeAesCbc; return edash_packager::media::kEncryptionModeAesCbc;
} else { } else {
LOG(ERROR) << "Protection scheme is unknown."; LOG(ERROR) << "Unknown protection scheme: " << protection_scheme;
return edash_packager::media::kEncryptionModeUnknown; return edash_packager::media::kEncryptionModeUnknown;
} }
} }
@ -377,6 +377,12 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
EncryptionMode encryption_mode = GetEncryptionMode(FLAGS_protection_scheme); EncryptionMode encryption_mode = GetEncryptionMode(FLAGS_protection_scheme);
if (encryption_mode == kEncryptionModeUnknown) if (encryption_mode == kEncryptionModeUnknown)
return false; return false;
if (encryption_mode == kEncryptionModeAesCbc && !FLAGS_iv.empty()) {
if (FLAGS_iv.size() != 16) {
LOG(ERROR) << "Iv size should be 16 bytes for CBC encryption mode.";
return false;
}
}
if (!AssignFlagsFromProfile()) if (!AssignFlagsFromProfile())
return false; return false;

View File

@ -27,6 +27,32 @@ namespace media {
AesDecryptor::AesDecryptor() {} AesDecryptor::AesDecryptor() {}
AesDecryptor::~AesDecryptor() {} AesDecryptor::~AesDecryptor() {}
bool AesDecryptor::Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) {
DCHECK(plaintext);
plaintext->resize(ciphertext.size());
size_t plaintext_size;
if (!DecryptInternal(ciphertext.data(), ciphertext.size(), plaintext->data(),
&plaintext_size))
return false;
plaintext->resize(plaintext_size);
return true;
}
bool AesDecryptor::Decrypt(const std::string& ciphertext,
std::string* plaintext) {
DCHECK(plaintext);
plaintext->resize(ciphertext.size());
size_t plaintext_size;
if (!DecryptInternal(reinterpret_cast<const uint8_t*>(ciphertext.data()),
ciphertext.size(),
reinterpret_cast<uint8_t*>(string_as_array(plaintext)),
&plaintext_size))
return false;
plaintext->resize(plaintext_size);
return true;
}
AesCtrDecryptor::AesCtrDecryptor() {} AesCtrDecryptor::AesCtrDecryptor() {}
AesCtrDecryptor::~AesCtrDecryptor() {} AesCtrDecryptor::~AesCtrDecryptor() {}
@ -37,163 +63,119 @@ bool AesCtrDecryptor::InitializeWithIv(const std::vector<uint8_t>& key,
return encryptor_->InitializeWithIv(key, iv); return encryptor_->InitializeWithIv(key, iv);
} }
// For AES CTR, encryption and decryption are identical.
bool AesCtrDecryptor::Decrypt(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext) {
DCHECK(encryptor_);
return encryptor_->EncryptData(ciphertext, ciphertext_size, plaintext);
}
bool AesCtrDecryptor::Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) {
DCHECK(encryptor_);
return encryptor_->Encrypt(ciphertext, plaintext);
}
bool AesCtrDecryptor::Decrypt(const std::string& ciphertext,
std::string* plaintext) {
DCHECK(encryptor_);
return encryptor_->Encrypt(ciphertext, plaintext);
}
bool AesCtrDecryptor::SetIv(const std::vector<uint8_t>& iv) { bool AesCtrDecryptor::SetIv(const std::vector<uint8_t>& iv) {
DCHECK(encryptor_); DCHECK(encryptor_);
return encryptor_->SetIv(iv); return encryptor_->SetIv(iv);
} }
AesCbcPkcs5Decryptor::AesCbcPkcs5Decryptor() {} bool AesCtrDecryptor::DecryptInternal(const uint8_t* ciphertext,
AesCbcPkcs5Decryptor::~AesCbcPkcs5Decryptor() {} size_t ciphertext_size,
uint8_t* plaintext,
size_t* plaintext_size) {
DCHECK(encryptor_);
*plaintext_size = ciphertext_size;
// For AES CTR, encryption and decryption are identical.
return encryptor_->Encrypt(ciphertext, ciphertext_size, plaintext);
}
bool AesCbcPkcs5Decryptor::InitializeWithIv(const std::vector<uint8_t>& key, AesCbcDecryptor::AesCbcDecryptor(CbcPaddingScheme padding_scheme,
bool chain_across_calls)
: padding_scheme_(padding_scheme),
chain_across_calls_(chain_across_calls) {
if (padding_scheme_ != kNoPadding) {
CHECK(!chain_across_calls) << "cipher block chain across calls only makes "
"sense if the padding_scheme is kNoPadding.";
}
}
AesCbcDecryptor::~AesCbcDecryptor() {}
bool AesCbcDecryptor::InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) { const std::vector<uint8_t>& iv) {
if (!IsKeySizeValidForAes(key.size())) { if (!IsKeySizeValidForAes(key.size())) {
LOG(ERROR) << "Invalid AES key size: " << key.size(); LOG(ERROR) << "Invalid AES key size: " << key.size();
return false; return false;
} }
aes_key_.reset(new AES_KEY());
CHECK_EQ(AES_set_decrypt_key(key.data(), key.size() * 8, aes_key_.get()), 0);
return SetIv(iv);
}
bool AesCbcDecryptor::SetIv(const std::vector<uint8_t>& iv) {
if (iv.size() != AES_BLOCK_SIZE) { if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size(); LOG(ERROR) << "Invalid IV size: " << iv.size();
return false; return false;
} }
aes_key_.reset(new AES_KEY());
CHECK_EQ(AES_set_decrypt_key(&key[0], key.size() * 8, aes_key_.get()), 0);
iv_ = iv; iv_ = iv;
return true; return true;
} }
bool AesCbcPkcs5Decryptor::Decrypt(const uint8_t* ciphertext, bool AesCbcDecryptor::DecryptInternal(const uint8_t* ciphertext,
size_t ciphertext_size, size_t ciphertext_size,
uint8_t* plaintext) { uint8_t* plaintext,
NOTIMPLEMENTED(); size_t* plaintext_size) {
return false; DCHECK(plaintext_size);
}
bool AesCbcPkcs5Decryptor::Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) {
NOTIMPLEMENTED();
return false;
}
bool AesCbcPkcs5Decryptor::Decrypt(const std::string& ciphertext,
std::string* plaintext) {
if ((ciphertext.size() % AES_BLOCK_SIZE) != 0) {
LOG(ERROR) << "Expecting cipher text size to be multiple of "
<< AES_BLOCK_SIZE << ", got " << ciphertext.size();
return false;
}
DCHECK(plaintext);
DCHECK(aes_key_); DCHECK(aes_key_);
// Plaintext size is the same as ciphertext size except for pkcs5 padding.
// Will update later if using pkcs5 padding.
*plaintext_size = ciphertext_size;
if (ciphertext_size == 0) {
if (padding_scheme_ == kPkcs5Padding) {
LOG(ERROR) << "Expected ciphertext to be at least " << AES_BLOCK_SIZE
<< " bytes with Pkcs5 padding";
return false;
}
return true;
}
DCHECK(plaintext);
plaintext->resize(ciphertext.size()); std::vector<uint8_t> local_iv(iv_);
AES_cbc_encrypt(reinterpret_cast<const uint8_t*>(ciphertext.data()), const size_t residual_block_size = ciphertext_size % AES_BLOCK_SIZE;
reinterpret_cast<uint8_t*>(string_as_array(plaintext)), if (residual_block_size == 0) {
ciphertext.size(), AES_cbc_encrypt(ciphertext, plaintext, ciphertext_size, aes_key_.get(),
aes_key_.get(), local_iv.data(), AES_DECRYPT);
&iv_[0], if (chain_across_calls_)
AES_DECRYPT); iv_ = local_iv;
if (padding_scheme_ != kPkcs5Padding)
return true;
// Strip off PKCS5 padding bytes. // Strip off PKCS5 padding bytes.
const uint8_t num_padding_bytes = (*plaintext)[plaintext->size() - 1]; const uint8_t num_padding_bytes = plaintext[ciphertext_size - 1];
if (num_padding_bytes > AES_BLOCK_SIZE) { if (num_padding_bytes > AES_BLOCK_SIZE) {
LOG(ERROR) << "Padding length is too large : " LOG(ERROR) << "Padding length is too large : "
<< static_cast<int>(num_padding_bytes); << static_cast<int>(num_padding_bytes);
return false; return false;
} }
plaintext->resize(plaintext->size() - num_padding_bytes); *plaintext_size -= num_padding_bytes;
return true; return true;
} } else if (padding_scheme_ != kCtsPadding) {
LOG(ERROR) << "Expecting cipher text size to be multiple of "
bool AesCbcPkcs5Decryptor::SetIv(const std::vector<uint8_t>& iv) { << AES_BLOCK_SIZE << ", got " << ciphertext_size;
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false; return false;
} }
iv_ = iv; DCHECK(!chain_across_calls_);
return true; DCHECK_EQ(padding_scheme_, kCtsPadding);
}
AesCbcCtsDecryptor::AesCbcCtsDecryptor() {}
AesCbcCtsDecryptor::~AesCbcCtsDecryptor() {}
bool AesCbcCtsDecryptor::InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) {
if (!IsKeySizeValidForAes(key.size())) {
LOG(ERROR) << "Invalid AES key size: " << key.size();
return false;
}
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
aes_key_.reset(new AES_KEY());
CHECK_EQ(AES_set_decrypt_key(&key[0], key.size() * 8, aes_key_.get()), 0);
iv_ = iv;
return true;
}
bool AesCbcCtsDecryptor::Decrypt(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext) {
DCHECK(ciphertext);
DCHECK(plaintext);
if (ciphertext_size < AES_BLOCK_SIZE) { if (ciphertext_size < AES_BLOCK_SIZE) {
// Don't have a full block, leave unencrypted. // Don't have a full block, leave unencrypted.
memcpy(plaintext, ciphertext, ciphertext_size); memcpy(plaintext, ciphertext, ciphertext_size);
return true; return true;
} }
std::vector<uint8_t> iv(iv_);
size_t residual_block_size = ciphertext_size % AES_BLOCK_SIZE;
if (residual_block_size == 0) {
// No residual block. No need to do ciphertext stealing.
AES_cbc_encrypt(ciphertext,
plaintext,
ciphertext_size,
aes_key_.get(),
&iv[0],
AES_DECRYPT);
return true;
}
// AES-CBC decrypt everything up to the next-to-last full block. // AES-CBC decrypt everything up to the next-to-last full block.
size_t cbc_size = ciphertext_size - residual_block_size; const size_t cbc_size = ciphertext_size - residual_block_size;
if (cbc_size > AES_BLOCK_SIZE) { if (cbc_size > AES_BLOCK_SIZE) {
AES_cbc_encrypt(ciphertext, AES_cbc_encrypt(ciphertext, plaintext, cbc_size - AES_BLOCK_SIZE,
plaintext, aes_key_.get(), local_iv.data(), AES_DECRYPT);
cbc_size - AES_BLOCK_SIZE,
aes_key_.get(),
&iv[0],
AES_DECRYPT);
} }
const uint8_t* next_to_last_ciphertext_block =
ciphertext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE;
uint8_t* next_to_last_plaintext_block =
plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE;
// Determine what the last IV should be so that we can "skip ahead" in the // Determine what the last IV should be so that we can "skip ahead" in the
// CBC decryption. // CBC decryption.
std::vector<uint8_t> last_iv( std::vector<uint8_t> last_iv(
@ -203,64 +185,24 @@ bool AesCbcCtsDecryptor::Decrypt(const uint8_t* ciphertext,
// Decrypt the next-to-last block using the IV determined above. This decrypts // Decrypt the next-to-last block using the IV determined above. This decrypts
// the residual block bits. // the residual block bits.
AES_cbc_encrypt( AES_cbc_encrypt(next_to_last_ciphertext_block, next_to_last_plaintext_block,
ciphertext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE, AES_BLOCK_SIZE, aes_key_.get(), last_iv.data(), AES_DECRYPT);
plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE,
AES_BLOCK_SIZE, aes_key_.get(), &last_iv[0], AES_DECRYPT);
// Swap back the residual block bits and the next-to-last full block. // Swap back the residual block bits and the next-to-last block.
if (plaintext == ciphertext) { if (plaintext == ciphertext) {
uint8_t* ptr1 = plaintext + ciphertext_size - residual_block_size; std::swap_ranges(next_to_last_plaintext_block,
uint8_t* ptr2 = plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE; next_to_last_plaintext_block + residual_block_size,
for (size_t i = 0; i < residual_block_size; ++i) { next_to_last_plaintext_block + AES_BLOCK_SIZE);
uint8_t temp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = temp;
++ptr1;
++ptr2;
}
} else { } else {
uint8_t* residual_plaintext_block = memcpy(next_to_last_plaintext_block + AES_BLOCK_SIZE,
plaintext + ciphertext_size - residual_block_size; next_to_last_plaintext_block, residual_block_size);
memcpy(residual_plaintext_block, residual_plaintext_block - AES_BLOCK_SIZE, memcpy(next_to_last_plaintext_block,
residual_block_size); next_to_last_ciphertext_block + AES_BLOCK_SIZE, residual_block_size);
memcpy(residual_plaintext_block - AES_BLOCK_SIZE,
ciphertext + ciphertext_size - residual_block_size,
residual_block_size);
} }
// Decrypt the last full block. // Decrypt the next-to-last full block.
AES_cbc_encrypt( AES_cbc_encrypt(next_to_last_plaintext_block, next_to_last_plaintext_block,
plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE, AES_BLOCK_SIZE, aes_key_.get(), local_iv.data(), AES_DECRYPT);
plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE,
AES_BLOCK_SIZE, aes_key_.get(), &iv[0], AES_DECRYPT);
return true;
}
bool AesCbcCtsDecryptor::Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) {
DCHECK(plaintext);
plaintext->resize(ciphertext.size(), 0);
if (ciphertext.empty())
return true;
return Decrypt(ciphertext.data(), ciphertext.size(), &(*plaintext)[0]);
}
bool AesCbcCtsDecryptor::Decrypt(const std::string& ciphertext,
std::string* plaintext) {
NOTIMPLEMENTED();
return false;
}
bool AesCbcCtsDecryptor::SetIv(const std::vector<uint8_t>& iv) {
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
iv_ = iv;
return true; return true;
} }

View File

@ -27,34 +27,42 @@ class AesDecryptor {
AesDecryptor(); AesDecryptor();
virtual ~AesDecryptor(); virtual ~AesDecryptor();
/// Initialize the decryptor with specified key and IV.
/// @return true on successful initialization, false otherwise.
virtual bool InitializeWithIv(const std::vector<uint8_t>& key, virtual bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) = 0; const std::vector<uint8_t>& iv) = 0;
/// @name Various forms of decrypt calls. /// @name Various forms of decrypt calls.
/// The plaintext and ciphertext pointers can be the same address. /// The plaintext and ciphertext pointers can be the same address.
/// @{ /// @{
virtual bool Decrypt(const uint8_t* ciphertext, bool Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext);
bool Decrypt(const std::string& ciphertext, std::string* plaintext);
bool Decrypt(const uint8_t* ciphertext,
size_t ciphertext_size, size_t ciphertext_size,
uint8_t* plaintext) = 0; uint8_t* plaintext) {
size_t plaintext_size;
virtual bool Decrypt(const std::vector<uint8_t>& ciphertext, return DecryptInternal(ciphertext, ciphertext_size, plaintext,
std::vector<uint8_t>* plaintext) = 0; &plaintext_size);
}
virtual bool Decrypt(const std::string& ciphertext,
std::string* plaintext) = 0;
/// @} /// @}
/// Set IV. @a block_offset_ is reset to 0 on success. /// Set IV.
/// @return true if successful, false if the input is invalid. /// @return true if successful, false if the input is invalid.
virtual bool SetIv(const std::vector<uint8_t>& iv) = 0; virtual bool SetIv(const std::vector<uint8_t>& iv) = 0;
const std::vector<uint8_t>& iv() const { return iv_; }
protected: protected:
// Initialization vector, with size 8 or 16. /// Internal implementation of decrypt function.
std::vector<uint8_t> iv_; /// @param ciphertext points to the input ciphertext.
// Openssl AES_KEY. /// @param ciphertext_size is the input ciphertext size.
scoped_ptr<AES_KEY> aes_key_; /// @param[out] plaintext points to the output plaintext. @a plaintext and
/// @a ciphertext can point to the same address.
/// @param[out] plaintext_size contains the size of plaintext on success.
/// It should never be larger than @a ciphertext_size.
virtual bool DecryptInternal(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext,
size_t* plaintext_size) = 0;
private: private:
DISALLOW_COPY_AND_ASSIGN(AesDecryptor); DISALLOW_COPY_AND_ASSIGN(AesDecryptor);
@ -71,80 +79,57 @@ class AesCtrDecryptor : public AesDecryptor {
bool InitializeWithIv(const std::vector<uint8_t>& key, bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override; const std::vector<uint8_t>& iv) override;
bool Decrypt(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext) override;
bool Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) override;
bool Decrypt(const std::string& ciphertext, std::string* plaintext) override;
bool SetIv(const std::vector<uint8_t>& iv) override; bool SetIv(const std::vector<uint8_t>& iv) override;
/// @} /// @}
uint32_t block_offset() const { return encryptor_->block_offset(); } uint32_t block_offset() const { return encryptor_->block_offset(); }
protected:
bool DecryptInternal(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext,
size_t* plaintext_size) override;
private: private:
scoped_ptr<AesCtrEncryptor> encryptor_; scoped_ptr<AesCtrEncryptor> encryptor_;
DISALLOW_COPY_AND_ASSIGN(AesCtrDecryptor); DISALLOW_COPY_AND_ASSIGN(AesCtrDecryptor);
}; };
// Class which implements AES-CBC (Cipher block chaining) decryption with // Class which implements AES-CBC (Cipher block chaining) decryption.
// PKCS#5 padding. class AesCbcDecryptor : public AesDecryptor {
class AesCbcPkcs5Decryptor : public AesDecryptor {
public: public:
AesCbcPkcs5Decryptor(); /// @param padding_scheme indicates the padding scheme used. Currently
~AesCbcPkcs5Decryptor() override; /// supported schemes: kNoPadding, kPkcs5Padding, kCtsPadding.
/// @param chain_across_calls indicates whether there is a continuous cipher
/// block chain across calls for Decrypt function. If it is false, iv
/// is not updated across Decrypt function calls.
AesCbcDecryptor(CbcPaddingScheme padding_scheme, bool chain_across_calls);
~AesCbcDecryptor() override;
/// @name AesDecryptor implementation overrides. /// @name AesDecryptor implementation overrides.
/// @{ /// @{
bool InitializeWithIv(const std::vector<uint8_t>& key, bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override; const std::vector<uint8_t>& iv) override;
bool Decrypt(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext) override;
bool Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) override;
bool Decrypt(const std::string& ciphertext, std::string* plaintext) override;
bool SetIv(const std::vector<uint8_t>& iv) override; bool SetIv(const std::vector<uint8_t>& iv) override;
/// @} /// @}
private: protected:
DISALLOW_COPY_AND_ASSIGN(AesCbcPkcs5Decryptor); bool DecryptInternal(const uint8_t* ciphertext,
};
// Class which implements AES-CBC (Cipher block chaining) decryption with
// Ciphertext stealing.
class AesCbcCtsDecryptor : public AesDecryptor {
public:
AesCbcCtsDecryptor();
~AesCbcCtsDecryptor() override;
/// @name AesDecryptor implementation overrides.
/// @{
bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override;
bool Decrypt(const uint8_t* ciphertext,
size_t ciphertext_size, size_t ciphertext_size,
uint8_t* plaintext) override; uint8_t* plaintext,
size_t* plaintext_size) override;
bool Decrypt(const std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>* plaintext) override;
bool Decrypt(const std::string& ciphertext, std::string* plaintext) override;
bool SetIv(const std::vector<uint8_t>& iv) override;
/// @}
private: private:
DISALLOW_COPY_AND_ASSIGN(AesCbcCtsDecryptor); // Openssl AES_KEY.
scoped_ptr<AES_KEY> aes_key_;
// Initialization vector, must be 16 for CBC.
std::vector<uint8_t> iv_;
const CbcPaddingScheme padding_scheme_;
const bool chain_across_calls_;
DISALLOW_COPY_AND_ASSIGN(AesCbcDecryptor);
}; };
} // namespace media } // namespace media

View File

@ -32,9 +32,6 @@ bool IsKeySizeValidForAes(size_t key_size) {
return key_size == 16 || key_size == 24 || key_size == 32; return key_size == 16 || key_size == 24 || key_size == 32;
} }
// CENC protection scheme uses 128-bit keys in counter mode.
const uint32_t kCencKeySize = 16;
} // namespace } // namespace
namespace edash_packager { namespace edash_packager {
@ -47,7 +44,7 @@ bool AesEncryptor::InitializeWithRandomIv(
const std::vector<uint8_t>& key, const std::vector<uint8_t>& key,
uint8_t iv_size) { uint8_t iv_size) {
std::vector<uint8_t> iv(iv_size, 0); std::vector<uint8_t> iv(iv_size, 0);
if (RAND_bytes(&iv[0], iv_size) != 1) { if (RAND_bytes(iv.data(), iv_size) != 1) {
LOG(ERROR) << "RAND_bytes failed with error: " LOG(ERROR) << "RAND_bytes failed with error: "
<< ERR_error_string(ERR_get_error(), NULL); << ERR_error_string(ERR_get_error(), NULL);
return false; return false;
@ -55,62 +52,98 @@ bool AesEncryptor::InitializeWithRandomIv(
return InitializeWithIv(key, iv); return InitializeWithIv(key, iv);
} }
bool AesEncryptor::InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) {
if (!IsKeySizeValidForAes(key.size())) {
LOG(ERROR) << "Invalid AES key size: " << key.size();
return false;
}
aes_key_.reset(new AES_KEY());
CHECK_EQ(AES_set_encrypt_key(key.data(), key.size() * 8, aes_key_.get()), 0);
return SetIv(iv);
}
bool AesEncryptor::Encrypt(const std::vector<uint8_t>& plaintext, bool AesEncryptor::Encrypt(const std::vector<uint8_t>& plaintext,
std::vector<uint8_t>* ciphertext) { std::vector<uint8_t>* ciphertext) {
if (plaintext.empty()) // Save plaintext size to make it work for in-place conversion, since the
return true; // next statement will update the plaintext size.
ciphertext->resize(plaintext.size() + NumPaddingBytes(plaintext.size())); const size_t plaintext_size = plaintext.size();
return EncryptData(plaintext.data(), plaintext.size(), ciphertext->data()); ciphertext->resize(plaintext_size + NumPaddingBytes(plaintext.size()));
return EncryptInternal(plaintext.data(), plaintext_size, ciphertext->data());
} }
bool AesEncryptor::Encrypt(const std::string& plaintext, bool AesEncryptor::Encrypt(const std::string& plaintext,
std::string* ciphertext) { std::string* ciphertext) {
ciphertext->resize(plaintext.size() + NumPaddingBytes(plaintext.size())); // Save plaintext size to make it work for in-place conversion, since the
return EncryptData(reinterpret_cast<const uint8_t*>(plaintext.data()), // next statement will update the plaintext size.
plaintext.size(), const size_t plaintext_size = plaintext.size();
ciphertext->resize(plaintext_size + NumPaddingBytes(plaintext.size()));
return EncryptInternal(
reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext_size,
reinterpret_cast<uint8_t*>(string_as_array(ciphertext))); reinterpret_cast<uint8_t*>(string_as_array(ciphertext)));
} }
AesCtrEncryptor::AesCtrEncryptor() AesCtrEncryptor::AesCtrEncryptor()
: block_offset_(0), : block_offset_(0),
encrypted_counter_(AES_BLOCK_SIZE, 0), encrypted_counter_(AES_BLOCK_SIZE, 0),
counter_overflow_(false) { counter_overflow_(false) {}
COMPILE_ASSERT(AES_BLOCK_SIZE == kCencKeySize,
cenc_key_size_should_be_the_same_as_aes_block_size);
}
AesCtrEncryptor::~AesCtrEncryptor() {} AesCtrEncryptor::~AesCtrEncryptor() {}
bool AesCtrEncryptor::InitializeWithIv(const std::vector<uint8_t>& key, void AesCtrEncryptor::UpdateIv() {
const std::vector<uint8_t>& iv) { block_offset_ = 0;
if (key.size() != kCencKeySize) {
LOG(ERROR) << "Invalid key size of " << key.size() << " for CENC."; // As recommended in ISO/IEC FDIS 23001-7: CENC spec, for 64-bit (8-byte)
return false; // IV_Sizes, initialization vectors for subsequent samples can be created by
// incrementing the initialization vector of the previous sample.
// For 128-bit (16-byte) IV_Sizes, initialization vectors for subsequent
// samples should be created by adding the block count of the previous sample
// to the initialization vector of the previous sample.
if (iv().size() == 8) {
counter_ = iv();
Increment64(&counter_[0]);
set_iv(counter_);
counter_.resize(AES_BLOCK_SIZE, 0);
} else {
DCHECK_EQ(16u, iv().size());
// Even though the block counter portion of the counter (bytes 8 to 15) is
// treated as a 64-bit number, it is recommended that the initialization
// vector is treated as a 128-bit number when calculating the next
// initialization vector from the previous one. The block counter portion
// is already incremented by number of blocks, the other 64 bits of the
// counter (bytes 0 to 7) is incremented here if the block counter portion
// has overflowed.
if (counter_overflow_)
Increment64(&counter_[0]);
set_iv(counter_);
} }
counter_overflow_ = false;
}
bool AesCtrEncryptor::SetIv(const std::vector<uint8_t>& iv) {
if (!IsIvSizeValid(iv.size())) { if (!IsIvSizeValid(iv.size())) {
LOG(ERROR) << "Invalid IV size: " << iv.size(); LOG(ERROR) << "Invalid IV size: " << iv.size();
return false; return false;
} }
aes_key_.reset(new AES_KEY()); block_offset_ = 0;
CHECK_EQ(AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, aes_key_.get()), 0); set_iv(iv);
return SetIv(iv); counter_ = iv;
counter_.resize(AES_BLOCK_SIZE, 0);
return true;
} }
size_t AesCtrEncryptor::NumPaddingBytes(size_t size) { bool AesCtrEncryptor::EncryptInternal(const uint8_t* plaintext,
return 0;
}
bool AesCtrEncryptor::EncryptData(const uint8_t* plaintext,
size_t plaintext_size, size_t plaintext_size,
uint8_t* ciphertext) { uint8_t* ciphertext) {
DCHECK(plaintext); DCHECK(plaintext);
DCHECK(ciphertext); DCHECK(ciphertext);
DCHECK(aes_key_); DCHECK(aes_key());
for (size_t i = 0; i < plaintext_size; ++i) { for (size_t i = 0; i < plaintext_size; ++i) {
if (block_offset_ == 0) { if (block_offset_ == 0) {
AES_encrypt(&counter_[0], &encrypted_counter_[0], aes_key_.get()); AES_encrypt(&counter_[0], &encrypted_counter_[0], aes_key());
// As mentioned in ISO/IEC FDIS 23001-7: CENC spec, of the 16 byte counter // As mentioned in ISO/IEC FDIS 23001-7: CENC spec, of the 16 byte counter
// block, bytes 8 to 15 (i.e. the least significant bytes) are used as a // block, bytes 8 to 15 (i.e. the least significant bytes) are used as a
// simple 64 bit unsigned integer that is incremented by one for each // simple 64 bit unsigned integer that is incremented by one for each
@ -125,195 +158,111 @@ bool AesCtrEncryptor::EncryptData(const uint8_t* plaintext,
return true; return true;
} }
void AesCtrEncryptor::UpdateIv() { size_t AesCtrEncryptor::NumPaddingBytes(size_t size) const {
block_offset_ = 0; // No padding needed for CTR.
// As recommended in ISO/IEC FDIS 23001-7: CENC spec, for 64-bit (8-byte)
// IV_Sizes, initialization vectors for subsequent samples can be created by
// incrementing the initialization vector of the previous sample.
// For 128-bit (16-byte) IV_Sizes, initialization vectors for subsequent
// samples should be created by adding the block count of the previous sample
// to the initialization vector of the previous sample.
if (iv_.size() == 8) {
Increment64(&iv_[0]);
counter_ = iv_;
counter_.resize(AES_BLOCK_SIZE, 0);
} else {
DCHECK_EQ(16u, iv_.size());
// Even though the block counter portion of the counter (bytes 8 to 15) is
// treated as a 64-bit number, it is recommended that the initialization
// vector is treated as a 128-bit number when calculating the next
// initialization vector from the previous one. The block counter portion
// is already incremented by number of blocks, the other 64 bits of the
// counter (bytes 0 to 7) is incremented here if the block counter portion
// has overflowed.
if (counter_overflow_)
Increment64(&counter_[0]);
iv_ = counter_;
}
counter_overflow_ = false;
}
bool AesCtrEncryptor::SetIv(const std::vector<uint8_t>& iv) {
if (!IsIvSizeValid(iv.size())) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
block_offset_ = 0;
counter_ = iv_ = iv;
counter_.resize(AES_BLOCK_SIZE, 0);
return true;
}
AesCbcPkcs5Encryptor::AesCbcPkcs5Encryptor() {}
AesCbcPkcs5Encryptor::~AesCbcPkcs5Encryptor() {}
bool AesCbcPkcs5Encryptor::InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) {
if (!IsKeySizeValidForAes(key.size())) {
LOG(ERROR) << "Invalid AES key size: " << key.size();
return false;
}
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
aes_key_.reset(new AES_KEY());
CHECK_EQ(AES_set_encrypt_key(&key[0], key.size() * 8, aes_key_.get()), 0);
iv_ = iv;
return true;
}
size_t AesCbcPkcs5Encryptor::NumPaddingBytes(size_t size) {
return AES_BLOCK_SIZE - (size % AES_BLOCK_SIZE);
}
bool AesCbcPkcs5Encryptor::EncryptData(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) {
DCHECK(ciphertext);
DCHECK(aes_key_);
// Pad the input with PKCS5 padding.
// TODO(kqyang): Consider more efficient implementation.
memcpy(ciphertext, plaintext, plaintext_size);
for (size_t i = plaintext_size;
i < plaintext_size + NumPaddingBytes(plaintext_size); ++i) {
ciphertext[i] = NumPaddingBytes(plaintext_size);
}
std::vector<uint8_t> iv(iv_);
AES_cbc_encrypt(ciphertext, ciphertext,
plaintext_size + NumPaddingBytes(plaintext_size),
aes_key_.get(), &iv[0], AES_ENCRYPT);
return true;
}
void AesCbcPkcs5Encryptor::UpdateIv() {}
bool AesCbcPkcs5Encryptor::SetIv(const std::vector<uint8_t>& iv) {
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
iv_ = iv;
return true;
}
AesCbcCtsEncryptor::AesCbcCtsEncryptor() {}
AesCbcCtsEncryptor::~AesCbcCtsEncryptor() {}
bool AesCbcCtsEncryptor::InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) {
if (!IsKeySizeValidForAes(key.size())) {
LOG(ERROR) << "Invalid AES key size: " << key.size();
return false;
}
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
aes_key_.reset(new AES_KEY());
CHECK_EQ(AES_set_encrypt_key(&key[0], key.size() * 8, aes_key_.get()), 0);
iv_ = iv;
return true;
}
size_t AesCbcCtsEncryptor::NumPaddingBytes(size_t size) {
return 0; return 0;
} }
bool AesCbcCtsEncryptor::EncryptData(const uint8_t* plaintext, AesCbcEncryptor::AesCbcEncryptor(CbcPaddingScheme padding_scheme,
size_t size, bool chain_across_calls)
uint8_t* ciphertext) { : padding_scheme_(padding_scheme),
DCHECK(plaintext); chain_across_calls_(chain_across_calls) {
DCHECK(ciphertext); if (padding_scheme_ != kNoPadding) {
CHECK(!chain_across_calls) << "cipher block chain across calls only makes "
"sense if the padding_scheme is kNoPadding.";
}
}
AesCbcEncryptor::~AesCbcEncryptor() {}
if (size < AES_BLOCK_SIZE) { void AesCbcEncryptor::UpdateIv() {
// Don't have a full block, leave unencrypted. // From CENC spec: CBC mode Initialization Vectors need not be unique per
memcpy(ciphertext, plaintext, size); // sample or Subsample and may be generated randomly or sequentially, e.g.
return true; // a per sample IV may be (1) equal to the cipher text of the last encrypted
// cipher block (a continous cipher block chain across samples), or (2)
// generated by incrementing the previuos IV by the number of cipher blocks in the last
// sample or (3) by a fixed amount. We use method (1) here. No separate IV
// update is needed.
} }
std::vector<uint8_t> iv(iv_); bool AesCbcEncryptor::SetIv(const std::vector<uint8_t>& iv) {
size_t residual_block_size = size % AES_BLOCK_SIZE;
size_t cbc_size = size - residual_block_size;
// Encrypt everything but the residual block using CBC.
AES_cbc_encrypt(plaintext,
ciphertext,
cbc_size,
aes_key_.get(),
&iv[0],
AES_ENCRYPT);
if (residual_block_size == 0) {
// No residual block. No need to do ciphertext stealing.
return true;
}
// Zero-pad the residual block and encrypt using CBC.
std::vector<uint8_t> residual_block(plaintext + size - residual_block_size,
plaintext + size);
residual_block.resize(AES_BLOCK_SIZE, 0);
AES_cbc_encrypt(&residual_block[0],
&residual_block[0],
AES_BLOCK_SIZE,
aes_key_.get(),
&iv[0],
AES_ENCRYPT);
// Replace the last full block with the zero-padded, encrypted residual block,
// and replace the residual block with the equivalent portion of the last full
// encrypted block. It may appear that some encrypted bits of the last full
// block are lost, but they are not, as they were used as the IV when
// encrypting the zero-padded residual block.
uint8_t* residual_ciphertext_block = ciphertext + size - residual_block_size;
memcpy(residual_ciphertext_block,
residual_ciphertext_block - AES_BLOCK_SIZE,
residual_block_size);
memcpy(residual_ciphertext_block - AES_BLOCK_SIZE,
residual_block.data(),
AES_BLOCK_SIZE);
return true;
}
void AesCbcCtsEncryptor::UpdateIv() {}
bool AesCbcCtsEncryptor::SetIv(const std::vector<uint8_t>& iv) {
if (iv.size() != AES_BLOCK_SIZE) { if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size(); LOG(ERROR) << "Invalid IV size: " << iv.size();
return false; return false;
} }
iv_ = iv; set_iv(iv);
return true; return true;
} }
bool AesCbcEncryptor::EncryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) {
DCHECK(aes_key());
const size_t residual_block_size = plaintext_size % AES_BLOCK_SIZE;
if (padding_scheme_ == kNoPadding && residual_block_size != 0) {
LOG(ERROR) << "Expecting input size to be multiple of " << AES_BLOCK_SIZE
<< ", got " << plaintext_size;
return false;
}
// Encrypt everything but the residual block using CBC.
const size_t cbc_size = plaintext_size - residual_block_size;
std::vector<uint8_t> local_iv(iv());
if (cbc_size != 0) {
AES_cbc_encrypt(plaintext, ciphertext, cbc_size, aes_key(), local_iv.data(),
AES_ENCRYPT);
} else if (padding_scheme_ == kCtsPadding) {
// Don't have a full block, leave unencrypted.
memcpy(ciphertext, plaintext, plaintext_size);
return true;
}
if (residual_block_size == 0 && padding_scheme_ != kPkcs5Padding) {
if (chain_across_calls_)
set_iv(local_iv);
// No residual block. No need to do padding.
return true;
}
DCHECK(!chain_across_calls_);
std::vector<uint8_t> residual_block(plaintext + cbc_size,
plaintext + plaintext_size);
DCHECK_EQ(residual_block.size(), residual_block_size);
uint8_t* residual_ciphertext_block = ciphertext + cbc_size;
if (padding_scheme_ == kPkcs5Padding) {
const size_t num_padding_bytes = AES_BLOCK_SIZE - residual_block_size;
DCHECK_EQ(num_padding_bytes, NumPaddingBytes(plaintext_size));
// Pad residue block with PKCS5 padding.
residual_block.resize(AES_BLOCK_SIZE, static_cast<char>(num_padding_bytes));
AES_cbc_encrypt(residual_block.data(), residual_ciphertext_block,
AES_BLOCK_SIZE, aes_key(), local_iv.data(), AES_ENCRYPT);
} else {
DCHECK_EQ(padding_scheme_, kCtsPadding);
// Zero-pad the residual block and encrypt using CBC.
residual_block.resize(AES_BLOCK_SIZE, 0);
AES_cbc_encrypt(residual_block.data(), residual_block.data(),
AES_BLOCK_SIZE, aes_key(), local_iv.data(), AES_ENCRYPT);
// Replace the last full block with the zero-padded, encrypted residual
// block, and replace the residual block with the equivalent portion of the
// last full encrypted block. It may appear that some encrypted bits of the
// last full block are lost, but they are not, as they were used as the IV
// when encrypting the zero-padded residual block.
memcpy(residual_ciphertext_block,
residual_ciphertext_block - AES_BLOCK_SIZE, residual_block_size);
memcpy(residual_ciphertext_block - AES_BLOCK_SIZE, residual_block.data(),
AES_BLOCK_SIZE);
}
return true;
}
size_t AesCbcEncryptor::NumPaddingBytes(size_t size) const {
return (padding_scheme_ == kPkcs5Padding)
? (AES_BLOCK_SIZE - (size % AES_BLOCK_SIZE))
: 0;
}
} // namespace media } // namespace media
} // namespace edash_packager } // namespace edash_packager

View File

@ -29,52 +29,64 @@ class AesEncryptor {
/// Initialize the encryptor with specified key and a random generated IV /// Initialize the encryptor with specified key and a random generated IV
/// of the specified size. /// of the specified size.
/// @return true on successful initialization, false otherwise. /// @return true on successful initialization, false otherwise.
virtual bool InitializeWithRandomIv(const std::vector<uint8_t>& key, bool InitializeWithRandomIv(const std::vector<uint8_t>& key,
uint8_t iv_size); uint8_t iv_size);
/// Initialize the encryptor with specified key and IV. /// Initialize the encryptor with specified key and IV.
/// @return true on successful initialization, false otherwise. /// @return true on successful initialization, false otherwise.
virtual bool InitializeWithIv(const std::vector<uint8_t>& key, bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) = 0; const std::vector<uint8_t>& iv);
virtual size_t NumPaddingBytes(size_t size) = 0; /// @name Various forms of encrypt calls.
/// @name Various forms of encrypt and decrypt calls.
/// The plaintext and ciphertext pointers can be the same address. /// The plaintext and ciphertext pointers can be the same address.
/// @{
virtual bool EncryptData(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) = 0;
bool Encrypt(const std::vector<uint8_t>& plaintext, bool Encrypt(const std::vector<uint8_t>& plaintext,
std::vector<uint8_t>* ciphertext); std::vector<uint8_t>* ciphertext);
bool Encrypt(const std::string& plaintext, std::string* ciphertext); bool Encrypt(const std::string& plaintext, std::string* ciphertext);
bool Encrypt(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) {
return EncryptInternal(plaintext, plaintext_size, ciphertext);
}
/// @} /// @}
/// Update IV for next sample. /// Update IV for next sample.
/// As recommended in ISO/IEC FDIS 23001-7: /// As recommended in ISO/IEC FDIS 23001-7:
/// IV need to be updated per sample for CENC. /// IV need to be updated per sample for CENC.
/// IV need not be unique per sample for CBC mode.
virtual void UpdateIv() = 0; virtual void UpdateIv() = 0;
/// Set IV. /// Set IV.
/// @return true if successful, false if the input is invalid. /// @return true if successful, false if the input is invalid.
virtual bool SetIv(const std::vector<uint8_t>& iv) = 0; virtual bool SetIv(const std::vector<uint8_t>& iv) = 0;
/// @return The current iv.
const std::vector<uint8_t>& iv() const { return iv_; } const std::vector<uint8_t>& iv() const { return iv_; }
protected: protected:
/// Internal implementation of encrypt function.
/// @param plaintext points to the input plaintext.
/// @param plaintext_size is the size of input plaintext.
/// @param[out] ciphertext points to the output ciphertext. @a plaintext and
/// @a ciphertext can point to the same address.
virtual bool EncryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) = 0;
/// @param size specifies the input plaintext size.
/// @returns The number of padding bytes needed for output ciphertext.
virtual size_t NumPaddingBytes(size_t size) const = 0;
void set_iv(const std::vector<uint8_t>& iv) { iv_ = iv; }
AES_KEY* aes_key() const { return aes_key_.get(); }
private:
// Initialization vector, with size 8 or 16. // Initialization vector, with size 8 or 16.
std::vector<uint8_t> iv_; std::vector<uint8_t> iv_;
// Openssl AES_KEY. // Openssl AES_KEY.
scoped_ptr<AES_KEY> aes_key_; scoped_ptr<AES_KEY> aes_key_;
private:
DISALLOW_COPY_AND_ASSIGN(AesEncryptor); DISALLOW_COPY_AND_ASSIGN(AesEncryptor);
}; };
// Class which implements AES-CTR counter-mode encryption/decryption. // Class which implements AES-CTR counter-mode encryption.
class AesCtrEncryptor : public AesEncryptor { class AesCtrEncryptor : public AesEncryptor {
public: public:
AesCtrEncryptor(); AesCtrEncryptor();
@ -82,18 +94,6 @@ class AesCtrEncryptor : public AesEncryptor {
/// @name AesEncryptor implementation overrides. /// @name AesEncryptor implementation overrides.
/// @{ /// @{
/// @param key should be 16 bytes in size as specified in CENC spec.
/// @param iv_size should be either 8 or 16 as specified in CENC spec.
/// @return true on successful initialization, false otherwise.
bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override;
size_t NumPaddingBytes(size_t size) override;
bool EncryptData(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) override;
/// Update IV for next sample. @a block_offset_ is reset to 0. /// Update IV for next sample. @a block_offset_ is reset to 0.
/// As recommended in ISO/IEC FDIS 23001-7: CENC spec, /// As recommended in ISO/IEC FDIS 23001-7: CENC spec,
/// For 64-bit IV size, new_iv = old_iv + 1; /// For 64-bit IV size, new_iv = old_iv + 1;
@ -105,6 +105,12 @@ class AesCtrEncryptor : public AesEncryptor {
uint32_t block_offset() const { return block_offset_; } uint32_t block_offset() const { return block_offset_; }
protected:
bool EncryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) override;
size_t NumPaddingBytes(size_t size) const override;
private: private:
// Current block offset. // Current block offset.
uint32_t block_offset_; uint32_t block_offset_;
@ -118,58 +124,43 @@ class AesCtrEncryptor : public AesEncryptor {
DISALLOW_COPY_AND_ASSIGN(AesCtrEncryptor); DISALLOW_COPY_AND_ASSIGN(AesCtrEncryptor);
}; };
// Class which implements AES-CBC (Cipher block chaining) encryption with enum CbcPaddingScheme {
// PKCS#5 padding. kNoPadding,
class AesCbcPkcs5Encryptor : public AesEncryptor { kPkcs5Padding,
public: kCtsPadding,
AesCbcPkcs5Encryptor();
~AesCbcPkcs5Encryptor() override;
/// @name AesEncryptor implementation overrides.
/// @{
bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override;
size_t NumPaddingBytes(size_t size) override;
bool EncryptData(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) override;
void UpdateIv() override;
bool SetIv(const std::vector<uint8_t>& iv) override;
/// @}
private:
DISALLOW_COPY_AND_ASSIGN(AesCbcPkcs5Encryptor);
}; };
// Class which implements AES-CBC (Cipher block chaining) encryption with const bool kChainAcrossCalls = true;
// Ciphertext stealing.
class AesCbcCtsEncryptor : public AesEncryptor { // Class which implements AES-CBC (Cipher block chaining) encryption.
class AesCbcEncryptor : public AesEncryptor {
public: public:
AesCbcCtsEncryptor(); /// @param padding_scheme indicates the padding scheme used. Currently
~AesCbcCtsEncryptor() override; /// supported schemes: kNoPadding, kPkcs5Padding, kCtsPadding.
/// @param chain_across_calls indicates whether there is a continuous cipher
/// block chain across calls for Encrypt function. If it is false, iv
/// is not updated across Encrypt function calls.
AesCbcEncryptor(CbcPaddingScheme padding_scheme, bool chain_across_calls);
~AesCbcEncryptor() override;
/// @name AesEncryptor implementation overrides. /// @name AesEncryptor implementation overrides.
/// @{ /// @{
bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override;
size_t NumPaddingBytes(size_t size) override;
bool EncryptData(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) override;
void UpdateIv() override; void UpdateIv() override;
bool SetIv(const std::vector<uint8_t>& iv) override; bool SetIv(const std::vector<uint8_t>& iv) override;
/// @} /// @}
protected:
bool EncryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext) override;
size_t NumPaddingBytes(size_t size) const override;
private: private:
DISALLOW_COPY_AND_ASSIGN(AesCbcCtsEncryptor); const CbcPaddingScheme padding_scheme_;
const bool chain_across_calls_;
DISALLOW_COPY_AND_ASSIGN(AesCbcEncryptor);
}; };
} // namespace media } // namespace media

View File

@ -170,7 +170,7 @@ TEST_F(AesCtrEncryptorTest, NistTestCase) {
TEST_F(AesCtrEncryptorTest, NistTestCaseInplaceEncryptionDecryption) { TEST_F(AesCtrEncryptorTest, NistTestCaseInplaceEncryptionDecryption) {
std::vector<uint8_t> buffer = plaintext_; std::vector<uint8_t> buffer = plaintext_;
EXPECT_TRUE(encryptor_.EncryptData(&buffer[0], buffer.size(), &buffer[0])); EXPECT_TRUE(encryptor_.Encrypt(&buffer[0], buffer.size(), &buffer[0]));
EXPECT_EQ(ciphertext_, buffer); EXPECT_EQ(ciphertext_, buffer);
EXPECT_TRUE(decryptor_.SetIv(iv_)); EXPECT_TRUE(decryptor_.SetIv(iv_));
@ -211,12 +211,11 @@ TEST_F(AesCtrEncryptorTest, 128BitIVBoundaryCaseEncryption) {
ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_max64)); ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_max64));
std::vector<uint8_t> encrypted_verify(plaintext_.size(), 0); std::vector<uint8_t> encrypted_verify(plaintext_.size(), 0);
EXPECT_TRUE(encryptor_.EncryptData(&plaintext_[0], kAesBlockSize, EXPECT_TRUE(
&encrypted_verify[0])); encryptor_.Encrypt(&plaintext_[0], kAesBlockSize, &encrypted_verify[0]));
std::vector<uint8_t> iv_zero(kIv128Zero, kIv128Zero + arraysize(kIv128Zero)); std::vector<uint8_t> iv_zero(kIv128Zero, kIv128Zero + arraysize(kIv128Zero));
ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_zero)); ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_zero));
EXPECT_TRUE(encryptor_.EncryptData(&plaintext_[kAesBlockSize], EXPECT_TRUE(encryptor_.Encrypt(&plaintext_[kAesBlockSize], kAesBlockSize * 3,
kAesBlockSize * 3,
&encrypted_verify[kAesBlockSize])); &encrypted_verify[kAesBlockSize]));
EXPECT_EQ(encrypted, encrypted_verify); EXPECT_EQ(encrypted, encrypted_verify);
} }
@ -254,7 +253,7 @@ TEST_P(AesCtrEncryptorSubsampleTest, NistTestCaseSubsamples) {
for (uint32_t i = 0, offset = 0; i < test_case->subsample_count; ++i) { for (uint32_t i = 0, offset = 0; i < test_case->subsample_count; ++i) {
uint32_t len = test_case->subsample_sizes[i]; uint32_t len = test_case->subsample_sizes[i];
EXPECT_TRUE( EXPECT_TRUE(
encryptor_.EncryptData(&plaintext_[offset], len, &encrypted[offset])); encryptor_.Encrypt(&plaintext_[offset], len, &encrypted[offset]));
offset += len; offset += len;
EXPECT_EQ(offset % kAesBlockSize, encryptor_.block_offset()); EXPECT_EQ(offset % kAesBlockSize, encryptor_.block_offset());
} }
@ -301,30 +300,66 @@ INSTANTIATE_TEST_CASE_P(IvTestCases,
AesCtrEncryptorIvTest, AesCtrEncryptorIvTest,
::testing::ValuesIn(kIvTestCases)); ::testing::ValuesIn(kIvTestCases));
class AesCbcPkcs5EncryptorTestEncryptionDecryption : public testing::Test { class AesCbcEncryptDecryptTest {
public: public:
void TestEncryptionDecryption(const std::vector<uint8_t>& key, AesCbcEncryptDecryptTest()
const std::vector<uint8_t>& iv, : encryptor_(new AesCbcEncryptor(kPkcs5Padding, !kChainAcrossCalls)),
const std::string& plaintext, decryptor_(new AesCbcDecryptor(kPkcs5Padding, !kChainAcrossCalls)),
const std::string& expected_ciphertext_hex) { key_(kAesKey, kAesKey + arraysize(kAesKey)),
AesCbcPkcs5Encryptor encryptor; iv_(kAesIv, kAesIv + arraysize(kAesIv)) {}
EXPECT_TRUE(encryptor.InitializeWithIv(key, iv));
std::string ciphertext; void TestEncryptDecrypt(const std::vector<uint8_t>& plaintext,
encryptor.Encrypt(plaintext, &ciphertext); const std::vector<uint8_t>& expected_ciphertext) {
EXPECT_EQ(expected_ciphertext_hex, // Test Vector form.
base::HexEncode(ciphertext.data(), ciphertext.size())); TestEncryptDecryptSeparateBuffers(plaintext, expected_ciphertext);
TestEncryptDecryptInPlace(plaintext, expected_ciphertext);
AesCbcPkcs5Decryptor decryptor; // Test string form.
ASSERT_TRUE(decryptor.InitializeWithIv(key, iv)); std::string plaintext_str(plaintext.begin(), plaintext.end());
std::string expected_ciphertext_str(expected_ciphertext.begin(),
expected_ciphertext.end());
TestEncryptDecryptSeparateBuffers(plaintext_str, expected_ciphertext_str);
TestEncryptDecryptInPlace(plaintext_str, expected_ciphertext_str);
}
std::string decrypted; protected:
EXPECT_TRUE(decryptor.Decrypt(ciphertext, &decrypted)); template <class T>
void TestEncryptDecryptSeparateBuffers(const T& plaintext,
const T& expected_ciphertext) {
ASSERT_TRUE(encryptor_->InitializeWithIv(key_, iv_));
ASSERT_TRUE(decryptor_->InitializeWithIv(key_, iv_));
T encrypted;
EXPECT_TRUE(encryptor_->Encrypt(plaintext, &encrypted));
EXPECT_EQ(expected_ciphertext, encrypted);
T decrypted;
EXPECT_TRUE(decryptor_->Decrypt(encrypted, &decrypted));
EXPECT_EQ(plaintext, decrypted); EXPECT_EQ(plaintext, decrypted);
} }
template <class T>
void TestEncryptDecryptInPlace(const T& plaintext,
const T& expected_ciphertext) {
ASSERT_TRUE(encryptor_->InitializeWithIv(key_, iv_));
ASSERT_TRUE(decryptor_->InitializeWithIv(key_, iv_));
T buffer(plaintext);
EXPECT_TRUE(encryptor_->Encrypt(buffer, &buffer));
EXPECT_EQ(expected_ciphertext, buffer);
EXPECT_TRUE(decryptor_->Decrypt(buffer, &buffer));
EXPECT_EQ(plaintext, buffer);
}
scoped_ptr<AesCbcEncryptor> encryptor_;
scoped_ptr<AesCbcDecryptor> decryptor_;
std::vector<uint8_t> key_;
std::vector<uint8_t> iv_;
}; };
TEST_F(AesCbcPkcs5EncryptorTestEncryptionDecryption, EncryptAES256CBC) { class AesCbcTest : public AesCbcEncryptDecryptTest, public testing::Test {};
TEST_F(AesCbcTest, Aes256CbcPkcs5) {
// NIST SP 800-38A test vector F.2.5 CBC-AES256.Encrypt. // NIST SP 800-38A test vector F.2.5 CBC-AES256.Encrypt.
static const uint8_t kAesCbcKey[] = { static const uint8_t kAesCbcKey[] = {
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae,
@ -363,18 +398,17 @@ TEST_F(AesCbcPkcs5EncryptorTestEncryptionDecryption, EncryptAES256CBC) {
0x3f, 0x46, 0x17, 0x96, 0xd6, 0xb0, 0xd6, 0xb2, 0x3f, 0x46, 0x17, 0x96, 0xd6, 0xb0, 0xd6, 0xb2,
0xe0, 0xc2, 0xa7, 0x2b, 0x4d, 0x80, 0xe6, 0x44}; 0xe0, 0xc2, 0xa7, 0x2b, 0x4d, 0x80, 0xe6, 0x44};
const std::vector<uint8_t> key(kAesCbcKey, key_.assign(kAesCbcKey, kAesCbcKey + arraysize(kAesCbcKey));
kAesCbcKey + arraysize(kAesCbcKey)); iv_.assign(kAesCbcIv, kAesCbcIv + arraysize(kAesCbcIv));
const std::vector<uint8_t> iv(kAesCbcIv, kAesCbcIv + arraysize(kAesCbcIv)); const std::vector<uint8_t> plaintext(
const std::string plaintext(reinterpret_cast<const char*>(kAesCbcPlaintext), kAesCbcPlaintext, kAesCbcPlaintext + arraysize(kAesCbcPlaintext));
sizeof(kAesCbcPlaintext)); const std::vector<uint8_t> expected_ciphertext(
const std::string expected_ciphertext_hex = kAesCbcCiphertext, kAesCbcCiphertext + arraysize(kAesCbcCiphertext));
base::HexEncode(kAesCbcCiphertext, sizeof(kAesCbcCiphertext));
TestEncryptionDecryption(key, iv, plaintext, expected_ciphertext_hex); TestEncryptDecrypt(plaintext, expected_ciphertext);
} }
TEST_F(AesCbcPkcs5EncryptorTestEncryptionDecryption, EncryptAES128CBCRegression) { TEST_F(AesCbcTest, Aes128CbcPkcs5) {
const std::string kKey = "128=SixteenBytes"; const std::string kKey = "128=SixteenBytes";
const std::string kIv = "Sweet Sixteen IV"; const std::string kIv = "Sweet Sixteen IV";
const std::string kPlaintext = const std::string kPlaintext =
@ -383,172 +417,201 @@ TEST_F(AesCbcPkcs5EncryptorTestEncryptionDecryption, EncryptAES128CBCRegression)
"D4A67A0BA33C30F207344D81D1E944BBE65587C3D7D9939A" "D4A67A0BA33C30F207344D81D1E944BBE65587C3D7D9939A"
"C070C62B9C15A3EA312EA4AD1BC7929F4D3C16B03AD5ADA8"; "C070C62B9C15A3EA312EA4AD1BC7929F4D3C16B03AD5ADA8";
const std::vector<uint8_t> key(kKey.begin(), kKey.end()); key_.assign(kKey.begin(), kKey.end());
const std::vector<uint8_t> iv(kIv.begin(), kIv.end()); iv_.assign(kIv.begin(), kIv.end());
TestEncryptionDecryption(key, iv, kPlaintext, kExpectedCiphertextHex); const std::vector<uint8_t> plaintext(kPlaintext.begin(), kPlaintext.end());
std::vector<uint8_t> expected_ciphertext;
ASSERT_TRUE(
base::HexStringToBytes(kExpectedCiphertextHex, &expected_ciphertext));
TestEncryptDecrypt(plaintext, expected_ciphertext);
} }
TEST_F(AesCbcPkcs5EncryptorTestEncryptionDecryption, EncryptAES192CBCRegression) { TEST_F(AesCbcTest, Aes192CbcPkcs5) {
const std::string kKey = "192bitsIsTwentyFourByte!"; const std::string kKey = "192bitsIsTwentyFourByte!";
const std::string kIv = "Sweet Sixteen IV"; const std::string kIv = "Sweet Sixteen IV";
const std::string kPlaintext = "Small text"; const std::string kPlaintext = "Small text";
const std::string kExpectedCiphertextHex = "78DE5D7C2714FC5C61346C5416F6C89A"; const std::string kExpectedCiphertextHex = "78DE5D7C2714FC5C61346C5416F6C89A";
const std::vector<uint8_t> key(kKey.begin(), kKey.end());
const std::vector<uint8_t> iv(kIv.begin(), kIv.end());
TestEncryptionDecryption(key, iv, kPlaintext, kExpectedCiphertextHex);
}
class AesCbcPkcs5EncryptorTest : public testing::Test {
public:
void SetUp() override {
const std::string kKey = "128=SixteenBytes";
const std::string kIv = "Sweet Sixteen IV";
key_.assign(kKey.begin(), kKey.end()); key_.assign(kKey.begin(), kKey.end());
iv_.assign(kIv.begin(), kIv.end()); iv_.assign(kIv.begin(), kIv.end());
const std::vector<uint8_t> plaintext(kPlaintext.begin(), kPlaintext.end());
std::vector<uint8_t> expected_ciphertext;
ASSERT_TRUE(
base::HexStringToBytes(kExpectedCiphertextHex, &expected_ciphertext));
TestEncryptDecrypt(plaintext, expected_ciphertext);
} }
protected: TEST_F(AesCbcTest, NoPaddingNoChainAcrossCalls) {
std::vector<uint8_t> key_; const uint8_t kPlaintext[] = {
std::vector<uint8_t> iv_; 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
};
const uint8_t kCiphertext[] = {
0x77, 0xcd, 0xe9, 0x1f, 0xe6, 0xdf, 0x9c, 0xbc,
0x5d, 0x0c, 0x98, 0xf9, 0x6e, 0xfd, 0x59, 0x0b,
}; };
TEST_F(AesCbcPkcs5EncryptorTest, UnsupportedKeySize) { std::vector<uint8_t> plaintext(kPlaintext,
AesCbcPkcs5Encryptor encryptor; kPlaintext + arraysize(kPlaintext));
EXPECT_FALSE(encryptor.InitializeWithIv(std::vector<uint8_t>(15, 0), iv_)); std::vector<uint8_t> ciphertext(kCiphertext,
} kCiphertext + arraysize(kCiphertext));
TEST_F(AesCbcPkcs5EncryptorTest, UnsupportedIvSize) { AesCbcEncryptor encryptor(kNoPadding, !kChainAcrossCalls);
AesCbcPkcs5Encryptor encryptor;
EXPECT_FALSE(encryptor.InitializeWithIv(key_, std::vector<uint8_t>(14, 0)));
}
TEST_F(AesCbcPkcs5EncryptorTest, EmptyEncrypt) {
AesCbcPkcs5Encryptor encryptor;
ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_)); ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_));
std::string ciphertext; std::vector<uint8_t> encrypted;
std::string expected_ciphertext_hex = "8518B8878D34E7185E300D0FCC426396"; ASSERT_TRUE(encryptor.Encrypt(plaintext, &encrypted));
encryptor.Encrypt("", &ciphertext); EXPECT_EQ(ciphertext, encrypted);
EXPECT_EQ(expected_ciphertext_hex, // Iv should not have been updated.
base::HexEncode(ciphertext.data(), ciphertext.size())); EXPECT_EQ(iv_, encryptor.iv());
} ASSERT_TRUE(encryptor.Encrypt(plaintext, &encrypted));
EXPECT_EQ(ciphertext, encrypted);
TEST_F(AesCbcPkcs5EncryptorTest, CipherTextNotMultipleOfBlockSize) { AesCbcDecryptor decryptor(kNoPadding, !kChainAcrossCalls);
AesCbcPkcs5Decryptor decryptor;
ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_)); ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_));
std::string plaintext;
EXPECT_FALSE(decryptor.Decrypt("1", &plaintext));
}
class AesCbcCtsEncryptorDecryptorTest : public testing::Test {
public:
void SetUp() override {
key_.assign(kAesKey, kAesKey + arraysize(kAesKey));
iv_.assign(kAesIv, kAesIv + arraysize(kAesIv));
}
void TestEncryptDecryptSeparateBuffers(
const std::vector<uint8_t>& plaintext,
const std::vector<uint8_t>& expected_ciphertext) {
ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_));
ASSERT_TRUE(decryptor_.InitializeWithIv(key_, iv_));
std::vector<uint8_t> encrypted;
encryptor_.Encrypt(plaintext, &encrypted);
EXPECT_EQ(expected_ciphertext, encrypted);
std::vector<uint8_t> decrypted; std::vector<uint8_t> decrypted;
decryptor_.Decrypt(encrypted, &decrypted); ASSERT_TRUE(decryptor.Decrypt(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
// Iv should not have been updated.
EXPECT_EQ(iv_, encryptor.iv());
ASSERT_TRUE(decryptor.Decrypt(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted); EXPECT_EQ(plaintext, decrypted);
} }
void TestEncryptDecryptInPlace( TEST_F(AesCbcTest, NoPaddingChainAcrossCalls) {
const std::vector<uint8_t>& plaintext, const uint8_t kPlaintext[] = {
const std::vector<uint8_t>& expected_ciphertext) { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_)); 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
ASSERT_TRUE(decryptor_.InitializeWithIv(key_, iv_)); };
const uint8_t kCiphertext[] = {
std::vector<uint8_t> buffer(plaintext); 0x77, 0xcd, 0xe9, 0x1f, 0xe6, 0xdf, 0x9c, 0xbc,
encryptor_.Encrypt(buffer, &buffer); 0x5d, 0x0c, 0x98, 0xf9, 0x6e, 0xfd, 0x59, 0x0b,
EXPECT_EQ(expected_ciphertext, buffer); };
decryptor_.Decrypt(buffer, &buffer); const uint8_t kCiphertext2[] = {
EXPECT_EQ(plaintext, buffer); 0xbd, 0xdd, 0xe4, 0x39, 0x52, 0x6f, 0x10, 0x0c,
} 0x95, 0x45, 0xc2, 0x74, 0xd4, 0xf7, 0xfd, 0x3f,
protected:
std::vector<uint8_t> key_;
std::vector<uint8_t> iv_;
AesCbcCtsEncryptor encryptor_;
AesCbcCtsDecryptor decryptor_;
}; };
TEST_F(AesCbcCtsEncryptorDecryptorTest, TestWithResidualBytes) { std::vector<uint8_t> plaintext(kPlaintext,
std::vector<uint8_t> plaintext; kPlaintext + arraysize(kPlaintext));
ASSERT_TRUE(base::HexStringToBytes( std::vector<uint8_t> ciphertext(kCiphertext,
kCiphertext + arraysize(kCiphertext));
std::vector<uint8_t> ciphertext2(kCiphertext2,
kCiphertext2 + arraysize(kCiphertext2));
AesCbcEncryptor encryptor(kNoPadding, kChainAcrossCalls);
ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_));
std::vector<uint8_t> encrypted;
ASSERT_TRUE(encryptor.Encrypt(plaintext, &encrypted));
EXPECT_EQ(ciphertext, encrypted);
// Iv should have been updated.
EXPECT_NE(iv_, encryptor.iv());
// If run encrypt again, the result will be different.
ASSERT_TRUE(encryptor.Encrypt(plaintext, &encrypted));
EXPECT_EQ(ciphertext2, encrypted);
AesCbcDecryptor decryptor(kNoPadding, kChainAcrossCalls);
ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_));
std::vector<uint8_t> decrypted;
ASSERT_TRUE(decryptor.Decrypt(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
// Iv should have been updated.
EXPECT_NE(iv_, encryptor.iv());
// If run decrypt on ciphertext2 now, it will return the original plaintext.
ASSERT_TRUE(decryptor.Decrypt(ciphertext2, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
TEST_F(AesCbcTest, UnsupportedKeySize) {
EXPECT_FALSE(encryptor_->InitializeWithIv(std::vector<uint8_t>(15, 0), iv_));
EXPECT_FALSE(decryptor_->InitializeWithIv(std::vector<uint8_t>(15, 0), iv_));
}
TEST_F(AesCbcTest, UnsupportedIvSize) {
EXPECT_FALSE(encryptor_->InitializeWithIv(key_, std::vector<uint8_t>(14, 0)));
EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector<uint8_t>(8, 0)));
}
TEST_F(AesCbcTest, Pkcs5CipherTextNotMultipleOfBlockSize) {
std::string plaintext;
ASSERT_TRUE(decryptor_->InitializeWithIv(key_, iv_));
EXPECT_FALSE(decryptor_->Decrypt("1", &plaintext));
}
TEST_F(AesCbcTest, Pkcs5CipherTextEmpty) {
std::string plaintext;
ASSERT_TRUE(decryptor_->InitializeWithIv(key_, iv_));
EXPECT_FALSE(decryptor_->Decrypt("", &plaintext));
}
struct CbcTestCase {
CbcPaddingScheme padding_scheme;
const char* plaintext_hex;
const char* expected_ciphertext_hex;
};
const CbcTestCase kCbcTestCases[] = {
// No padding with zero bytes.
{kNoPadding, "", ""},
{kNoPadding,
"6bc1bee22e409f96e93d7e117393172a6bc1bee22e409f96e93d7e117393172a",
"77cde91fe6df9cbc5d0c98f96efd590bbddde439526f100c9545c274d4f7fd3f"},
// Pkcs5 padding with zero bytes.
{kPkcs5Padding, "", "f6a3569dea3cda208eb3d5792942612b"},
// Cts Padding with zero bytes.
{kCtsPadding, "", ""},
// Cts Padding with no encrypted blocks.
{kCtsPadding, "3f593e7a204a5e70f2", "3f593e7a204a5e70f2"},
// Cts padding with residual bytes.
{kCtsPadding,
"e0818f2dc7caaa9edf09285a0c1fca98d39e9b08a47ab6911c4bbdf27d94" "e0818f2dc7caaa9edf09285a0c1fca98d39e9b08a47ab6911c4bbdf27d94"
"f917cdffc9ebb307141f23b0d3921e0ed7f86eb09381286f8e7a4f", "f917cdffc9ebb307141f23b0d3921e0ed7f86eb09381286f8e7a4f",
&plaintext));
std::vector<uint8_t> ciphertext;
ASSERT_TRUE(base::HexStringToBytes(
"b40a0b8704c74e22e8030cad6f272b34ace54cc7c9c64b2018bbcf23df018" "b40a0b8704c74e22e8030cad6f272b34ace54cc7c9c64b2018bbcf23df018"
"39b14899441cf74a9fb2f2b229a609146f31be8e8a826eb6e857e", "39b14899441cf74a9fb2f2b229a609146f31be8e8a826eb6e857e"},
&ciphertext)); // Cts padding with even blocks.
{kCtsPadding,
TestEncryptDecryptSeparateBuffers(plaintext, ciphertext);
TestEncryptDecryptInPlace(plaintext, ciphertext);
}
TEST_F(AesCbcCtsEncryptorDecryptorTest, TestEvenBlocks) {
std::vector<uint8_t> plaintext;
ASSERT_TRUE(base::HexStringToBytes(
"3f593e7a204a5e70f2814dca05aa49d36f2daddc9a24e0515802c539efc3" "3f593e7a204a5e70f2814dca05aa49d36f2daddc9a24e0515802c539efc3"
"1094b3ad6c26d6f5c0e387545ce6a4c2c14d", "1094b3ad6c26d6f5c0e387545ce6a4c2c14d",
&plaintext));
std::vector<uint8_t> ciphertext;
ASSERT_TRUE(base::HexStringToBytes(
"5f32cd0504b27b25ee04090d88d37d340c9c0a9fa50b05358b98fad4302ea" "5f32cd0504b27b25ee04090d88d37d340c9c0a9fa50b05358b98fad4302ea"
"480148d8aa091f4e7d186a7223df153f6f7", "480148d8aa091f4e7d186a7223df153f6f7"},
&ciphertext)); // Cts padding with one block and a half.
{kCtsPadding, "3f593e7a204a5e70f2814dca05aa49d36f2daddc9a4302ea",
"623fc113fe02ce85628deb58d652c6995f32cd0504b27b25"},
};
TestEncryptDecryptSeparateBuffers(plaintext, ciphertext); class AesCbcCryptorVerificationTest
TestEncryptDecryptInPlace(plaintext, ciphertext); : public AesCbcEncryptDecryptTest,
} public ::testing::TestWithParam<CbcTestCase> {};
TEST_P(AesCbcCryptorVerificationTest, EncryptDecryptTest) {
encryptor_.reset(
new AesCbcEncryptor(GetParam().padding_scheme, !kChainAcrossCalls));
decryptor_.reset(
new AesCbcDecryptor(GetParam().padding_scheme, !kChainAcrossCalls));
TEST_F(AesCbcCtsEncryptorDecryptorTest, TestOneBlockAndAHalf) {
std::vector<uint8_t> plaintext; std::vector<uint8_t> plaintext;
ASSERT_TRUE(base::HexStringToBytes( std::string plaintext_hex(GetParam().plaintext_hex);
"3f593e7a204a5e70f2814dca05aa49d36f2daddc9a4302ea", if (!plaintext_hex.empty())
&plaintext)); ASSERT_TRUE(base::HexStringToBytes(plaintext_hex, &plaintext));
std::vector<uint8_t> ciphertext; std::vector<uint8_t> expected_ciphertext;
ASSERT_TRUE(base::HexStringToBytes( std::string expected_ciphertext_hex(GetParam().expected_ciphertext_hex);
"623fc113fe02ce85628deb58d652c6995f32cd0504b27b25", if (!expected_ciphertext_hex.empty()) {
&ciphertext)); ASSERT_TRUE(base::HexStringToBytes(GetParam().expected_ciphertext_hex,
&expected_ciphertext));
TestEncryptDecryptSeparateBuffers(plaintext, ciphertext);
TestEncryptDecryptInPlace(plaintext, ciphertext);
} }
TEST_F(AesCbcCtsEncryptorDecryptorTest, TestZeroEncryptedBlocks) { TestEncryptDecrypt(plaintext, expected_ciphertext);
std::vector<uint8_t> plaintext;
ASSERT_TRUE(base::HexStringToBytes("3f593e7a204a5e70f2", &plaintext));
TestEncryptDecryptSeparateBuffers(plaintext, plaintext);
TestEncryptDecryptInPlace(plaintext, plaintext);
} }
TEST_F(AesCbcCtsEncryptorDecryptorTest, TestZeroBytes) { INSTANTIATE_TEST_CASE_P(CbcTestCases,
std::vector<uint8_t> plaintext; AesCbcCryptorVerificationTest,
::testing::ValuesIn(kCbcTestCases));
TestEncryptDecryptSeparateBuffers(plaintext, plaintext);
TestEncryptDecryptInPlace(plaintext, plaintext);
}
} // namespace media } // namespace media
} // namespace edash_packager } // namespace edash_packager

View File

@ -44,7 +44,7 @@ bool DecryptorSource::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
aes_decryptor.reset(new AesCtrDecryptor); aes_decryptor.reset(new AesCtrDecryptor);
break; break;
case kEncryptionModeAesCbc: case kEncryptionModeAesCbc:
aes_decryptor.reset(new AesCbcPkcs5Decryptor); aes_decryptor.reset(new AesCbcDecryptor(kNoPadding, kChainAcrossCalls));
break; break;
default: default:
LOG(ERROR) << "Unsupported Decryption Mode: " LOG(ERROR) << "Unsupported Decryption Mode: "

View File

@ -19,7 +19,7 @@ RequestSigner::RequestSigner(const std::string& signer_name)
RequestSigner::~RequestSigner() {} RequestSigner::~RequestSigner() {}
AesRequestSigner::AesRequestSigner(const std::string& signer_name, AesRequestSigner::AesRequestSigner(const std::string& signer_name,
scoped_ptr<AesCbcPkcs5Encryptor> encryptor) scoped_ptr<AesCbcEncryptor> encryptor)
: RequestSigner(signer_name), aes_cbc_encryptor_(encryptor.Pass()) { : RequestSigner(signer_name), aes_cbc_encryptor_(encryptor.Pass()) {
DCHECK(aes_cbc_encryptor_); DCHECK(aes_cbc_encryptor_);
} }
@ -39,7 +39,8 @@ AesRequestSigner* AesRequestSigner::CreateSigner(const std::string& signer_name,
return NULL; return NULL;
} }
scoped_ptr<AesCbcPkcs5Encryptor> encryptor(new AesCbcPkcs5Encryptor()); scoped_ptr<AesCbcEncryptor> encryptor(
new AesCbcEncryptor(kPkcs5Padding, !kChainAcrossCalls));
if (!encryptor->InitializeWithIv(aes_key, iv)) if (!encryptor->InitializeWithIv(aes_key, iv))
return NULL; return NULL;
return new AesRequestSigner(signer_name, encryptor.Pass()); return new AesRequestSigner(signer_name, encryptor.Pass());

View File

@ -14,7 +14,7 @@
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
class AesCbcPkcs5Encryptor; class AesCbcEncryptor;
class RsaPrivateKey; class RsaPrivateKey;
/// Abstract class used for signature generation. /// Abstract class used for signature generation.
@ -56,9 +56,9 @@ class AesRequestSigner : public RequestSigner {
private: private:
AesRequestSigner(const std::string& signer_name, AesRequestSigner(const std::string& signer_name,
scoped_ptr<AesCbcPkcs5Encryptor> encryptor); scoped_ptr<AesCbcEncryptor> encryptor);
scoped_ptr<AesCbcPkcs5Encryptor> aes_cbc_encryptor_; scoped_ptr<AesCbcEncryptor> aes_cbc_encryptor_;
DISALLOW_COPY_AND_ASSIGN(AesRequestSigner); DISALLOW_COPY_AND_ASSIGN(AesRequestSigner);
}; };

View File

@ -23,7 +23,8 @@ namespace mp4 {
namespace { namespace {
// Generate 64bit IV by default. // Generate 64bit IV by default.
const size_t kDefaultIvSize = 8u; const size_t kDefaultIvSizeForCtr = 8u;
const size_t kDefaultIvSizeForCbc = 16u;
const size_t kCencBlockSize = 16u; const size_t kCencBlockSize = 16u;
// Adds one or more subsamples to |*subsamples|. This may add more than one // Adds one or more subsamples to |*subsamples|. This may add more than one
@ -175,16 +176,19 @@ void EncryptingFragmenter::FinalizeFragmentForEncryption() {
Status EncryptingFragmenter::CreateEncryptor() { Status EncryptingFragmenter::CreateEncryptor() {
DCHECK(encryption_key_); DCHECK(encryption_key_);
scoped_ptr<AesEncryptor> encryptor; scoped_ptr<AesEncryptor> encryptor;
size_t default_iv_size = 0;
if (encryption_mode_ == kEncryptionModeAesCtr) { if (encryption_mode_ == kEncryptionModeAesCtr) {
encryptor.reset(new AesCtrEncryptor); encryptor.reset(new AesCtrEncryptor);
default_iv_size = kDefaultIvSizeForCtr;
} else if (encryption_mode_ == kEncryptionModeAesCbc) { } else if (encryption_mode_ == kEncryptionModeAesCbc) {
encryptor.reset(new AesCbcPkcs5Encryptor); encryptor.reset(new AesCbcEncryptor(kNoPadding, kChainAcrossCalls));
default_iv_size = kDefaultIvSizeForCbc;
} else { } else {
return Status(error::MUXER_FAILURE, "Unsupported encryption mode."); return Status(error::MUXER_FAILURE, "Unsupported encryption mode.");
} }
const bool initialized = encryption_key_->iv.empty() const bool initialized = encryption_key_->iv.empty()
? encryptor->InitializeWithRandomIv( ? encryptor->InitializeWithRandomIv(
encryption_key_->key, kDefaultIvSize) encryption_key_->key, default_iv_size)
: encryptor->InitializeWithIv( : encryptor->InitializeWithIv(
encryption_key_->key, encryption_key_->iv); encryption_key_->key, encryption_key_->iv);
if (!initialized) if (!initialized)
@ -195,7 +199,7 @@ Status EncryptingFragmenter::CreateEncryptor() {
void EncryptingFragmenter::EncryptBytes(uint8_t* data, uint32_t size) { void EncryptingFragmenter::EncryptBytes(uint8_t* data, uint32_t size) {
DCHECK(encryptor_); DCHECK(encryptor_);
CHECK(encryptor_->EncryptData(data, size, data)); CHECK(encryptor_->Encrypt(data, size, data));
} }
Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) { Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
@ -223,8 +227,11 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
// encrypted bytes of each frame within the superframe must be block // encrypted bytes of each frame within the superframe must be block
// aligned so that the counter state can be computed for each frame // aligned so that the counter state can be computed for each frame
// within the superframe. // within the superframe.
if (is_superframe) { // For AES-CBC mode 'cbc1' scheme, clear data is sized appropriately so
uint16_t misalign_bytes = subsample.cipher_bytes % kCencBlockSize; // that the cipher data is block aligned.
if (is_superframe || encryption_mode_ == kEncryptionModeAesCbc) {
const uint16_t misalign_bytes =
subsample.cipher_bytes % kCencBlockSize;
subsample.clear_bytes += misalign_bytes; subsample.clear_bytes += misalign_bytes;
subsample.cipher_bytes -= misalign_bytes; subsample.cipher_bytes -= misalign_bytes;
} }
@ -258,10 +265,18 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
if (video_slice_header_size < 0) if (video_slice_header_size < 0)
return Status(error::MUXER_FAILURE, "Failed to read slice header."); return Status(error::MUXER_FAILURE, "Failed to read slice header.");
const uint64_t current_clear_bytes = nalu.header_size() + uint64_t current_clear_bytes =
video_slice_header_size; nalu.header_size() + video_slice_header_size;
const uint64_t cipher_bytes = uint64_t cipher_bytes = nalu.payload_size() - video_slice_header_size;
nalu.payload_size() - video_slice_header_size;
// For AES-CBC mode 'cbc1' scheme, clear data is sized appropriately
// so that the cipher data is block aligned.
if (encryption_mode_ == kEncryptionModeAesCbc) {
const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
current_clear_bytes += misalign_bytes;
cipher_bytes -= misalign_bytes;
}
const uint8_t* nalu_data = nalu.data() + current_clear_bytes; const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes); EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);
@ -285,7 +300,12 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
traf()->auxiliary_size.sample_info_sizes.push_back( traf()->auxiliary_size.sample_info_sizes.push_back(
sample_encryption_entry.ComputeSize()); sample_encryption_entry.ComputeSize());
} else { } else {
EncryptBytes(data, sample->data_size()); uint64_t encryption_data_size = sample->data_size();
// AES-CBC mode requires all encrypted cipher blocks to be 16 bytes. The
// partial blocks are left unencrypted.
if (encryption_mode_ == kEncryptionModeAesCbc)
encryption_data_size -= encryption_data_size % kCencBlockSize;
EncryptBytes(data, encryption_data_size);
} }
traf()->sample_encryption.sample_encryption_entries.push_back( traf()->sample_encryption.sample_encryption_entries.push_back(

View File

@ -78,7 +78,7 @@ Status Encryptor::EncryptFrame(scoped_refptr<MediaSample> sample,
uint8_t* sample_data = sample->writable_data(); uint8_t* sample_data = sample->writable_data();
// Encrypt the data in-place. // Encrypt the data in-place.
if (!encryptor_->EncryptData(sample_data, sample_size, sample_data)) { if (!encryptor_->Encrypt(sample_data, sample_size, sample_data)) {
return Status(error::MUXER_FAILURE, "Failed to encrypt the frame."); return Status(error::MUXER_FAILURE, "Failed to encrypt the frame.");
} }

View File

@ -1114,7 +1114,7 @@ bool WvmMediaParser::ProcessEcm() {
encryption_key.key.begin(), encryption_key.key.begin(),
encryption_key.key.begin() + kAssetKeySizeBytes); encryption_key.key.begin() + kAssetKeySizeBytes);
std::vector<uint8_t> iv(kInitializationVectorSizeBytes); std::vector<uint8_t> iv(kInitializationVectorSizeBytes);
AesCbcCtsDecryptor asset_decryptor; AesCbcDecryptor asset_decryptor(kCtsPadding, !kChainAcrossCalls);
if (!asset_decryptor.InitializeWithIv(asset_key, iv)) { if (!asset_decryptor.InitializeWithIv(asset_key, iv)) {
LOG(ERROR) << "Failed to initialize asset_decryptor."; LOG(ERROR) << "Failed to initialize asset_decryptor.";
return false; return false;
@ -1124,13 +1124,14 @@ bool WvmMediaParser::ProcessEcm() {
kEcmFlagsSizeBytes + kEcmContentKeySizeBytes + kEcmFlagsSizeBytes + kEcmContentKeySizeBytes +
kEcmPaddingSizeBytes; // flags + contentKey + padding. kEcmPaddingSizeBytes; // flags + contentKey + padding.
std::vector<uint8_t> content_key_buffer(content_key_buffer_size); std::vector<uint8_t> content_key_buffer(content_key_buffer_size);
asset_decryptor.Decrypt(ecm_data, content_key_buffer_size, CHECK(asset_decryptor.Decrypt(ecm_data, content_key_buffer_size,
content_key_buffer.data()); content_key_buffer.data()));
std::vector<uint8_t> decrypted_content_key_vec( std::vector<uint8_t> decrypted_content_key_vec(
content_key_buffer.begin() + 4, content_key_buffer.begin() + 4,
content_key_buffer.begin() + 20); content_key_buffer.begin() + 20);
scoped_ptr<AesCbcCtsDecryptor> content_decryptor(new AesCbcCtsDecryptor); scoped_ptr<AesCbcDecryptor> content_decryptor(
new AesCbcDecryptor(kCtsPadding, !kChainAcrossCalls));
if (!content_decryptor->InitializeWithIv(decrypted_content_key_vec, iv)) { if (!content_decryptor->InitializeWithIv(decrypted_content_key_vec, iv)) {
LOG(ERROR) << "Failed to initialize content decryptor."; LOG(ERROR) << "Failed to initialize content decryptor.";
return false; return false;

View File

@ -20,7 +20,7 @@
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
class AesCbcCtsDecryptor; class AesCbcDecryptor;
class KeySource; class KeySource;
struct EncryptionKey; struct EncryptionKey;
@ -252,7 +252,7 @@ class WvmMediaParser : public MediaParser {
std::deque<DemuxStreamIdMediaSample> media_sample_queue_; std::deque<DemuxStreamIdMediaSample> media_sample_queue_;
std::vector<uint8_t> sample_data_; std::vector<uint8_t> sample_data_;
KeySource* decryption_key_source_; KeySource* decryption_key_source_;
scoped_ptr<AesCbcCtsDecryptor> content_decryptor_; scoped_ptr<AesCbcDecryptor> content_decryptor_;
DISALLOW_COPY_AND_ASSIGN(WvmMediaParser); DISALLOW_COPY_AND_ASSIGN(WvmMediaParser);
}; };