Fix a few problems in AesCryptor classes

- CBC cryptors should accept IV of size 8 bytes - it will be zero
  extended to 16 bytes.
- Fixed iv() not updated problem in AesPatternCryptor.
- Replace kChainAcrossCalls with ConstantIvFlag enum flags.

Change-Id: I3fb4de0e8abbe891e6271e779373ba53f8df660d
This commit is contained in:
KongQun Yang 2016-04-13 10:52:41 -07:00 committed by Kongqun Yang
parent 66b82f87dd
commit 13202f91b6
17 changed files with 276 additions and 256 deletions

View File

@ -50,6 +50,14 @@ bool ValidateFixedCryptoFlags() {
"--enable_fixed_key_encryption")) {
success = false;
}
if (!FLAGS_iv.empty()) {
if (FLAGS_iv.size() != 8 * 2 && FLAGS_iv.size() != 16 * 2) {
PrintError(
"--iv should be either 8 bytes (16 hex digits) or 16 bytes (32 hex "
"digits).");
success = false;
}
}
// --pssh is associated with --enable_fix_key_encryption.
if (!ValidateFlag("pssh",

View File

@ -384,12 +384,6 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
const FourCC protection_scheme = GetProtectionScheme(FLAGS_protection_scheme);
if (protection_scheme == FOURCC_NULL)
return false;
if (protection_scheme == FOURCC_cbc1 || protection_scheme == FOURCC_cbcs) {
if (!FLAGS_iv.empty() && FLAGS_iv.size() != 16) {
LOG(ERROR) << "Iv size should be 16 bytes for CBC encryption mode.";
return false;
}
}
if (!AssignFlagsFromProfile())
return false;

View File

@ -61,7 +61,7 @@ DEFINE_string(protection_scheme,
"protection schemes 'cens' or 'cbcs'. Note that if a "
"pattern-based protection scheme only applies to video stream; "
"audio stream will be encrypted using the corresponding "
"non-pattern-based encryption schemes, i.e. 'cenc' for 'cens', "
"non-pattern-based protection schemes, i.e. 'cenc' for 'cens', "
"'cbc1' for 'cbcs'.");
namespace edash_packager {

View File

@ -6,6 +6,9 @@
#include "packager/media/base/aes_cryptor.h"
#include <string>
#include <vector>
#include <openssl/aes.h>
#include <openssl/err.h>
#include <openssl/rand.h>
@ -13,10 +16,24 @@
#include "packager/base/logging.h"
#include "packager/base/stl_util.h"
namespace {
// According to ISO/IEC 23001-7:2016 CENC spec, IV should be either
// 64-bit (8-byte) or 128-bit (16-byte).
bool IsIvSizeValid(size_t iv_size) {
return iv_size == 8 || iv_size == 16;
}
} // namespace
namespace edash_packager {
namespace media {
AesCryptor::AesCryptor() : aes_key_(new AES_KEY) {}
AesCryptor::AesCryptor(ConstantIvFlag constant_iv_flag)
: aes_key_(new AES_KEY),
constant_iv_flag_(constant_iv_flag),
num_crypt_bytes_(0) {}
AesCryptor::~AesCryptor() {}
bool AesCryptor::Crypt(const std::vector<uint8_t>& text,
@ -26,8 +43,7 @@ bool AesCryptor::Crypt(const std::vector<uint8_t>& text,
const size_t text_size = text.size();
crypt_text->resize(text_size + NumPaddingBytes(text_size));
size_t crypt_text_size = crypt_text->size();
if (!CryptInternal(text.data(), text_size, crypt_text->data(),
&crypt_text_size)) {
if (!Crypt(text.data(), text_size, crypt_text->data(), &crypt_text_size)) {
return false;
}
DCHECK_LE(crypt_text_size, crypt_text->size());
@ -41,7 +57,7 @@ bool AesCryptor::Crypt(const std::string& text, std::string* crypt_text) {
const size_t text_size = text.size();
crypt_text->resize(text_size + NumPaddingBytes(text_size));
size_t crypt_text_size = crypt_text->size();
if (!CryptInternal(reinterpret_cast<const uint8_t*>(text.data()), text_size,
if (!Crypt(reinterpret_cast<const uint8_t*>(text.data()), text_size,
reinterpret_cast<uint8_t*>(string_as_array(crypt_text)),
&crypt_text_size))
return false;
@ -50,9 +66,44 @@ bool AesCryptor::Crypt(const std::string& text, std::string* crypt_text) {
return true;
}
size_t AesCryptor::NumPaddingBytes(size_t size) const {
// No padding by default.
return 0;
bool AesCryptor::SetIv(const std::vector<uint8_t>& iv) {
if (!IsIvSizeValid(iv.size())) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
iv_ = iv;
num_crypt_bytes_ = 0;
SetIvInternal();
return true;
}
void AesCryptor::UpdateIv() {
if (constant_iv_flag_ == kUseConstantIv)
return;
uint64_t increment = 0;
// As recommended in ISO/IEC 23001-7:2016 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.
// There is no official recommendation of how IV for next sample should be
// generated for CBC mode. We use the same generation algorithm as CTR here.
if (iv_.size() == 8) {
increment = 1;
} else {
DCHECK_EQ(16u, iv_.size());
increment = (num_crypt_bytes_ + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE;
}
for (int i = iv_.size() - 1; increment > 0 && i >= 0; --i) {
increment += iv_[i];
iv_[i] = increment & 0xFF;
increment >>= 8;
}
num_crypt_bytes_ = 0;
SetIvInternal();
}
bool AesCryptor::GenerateRandomIv(FourCC protection_scheme,
@ -74,6 +125,11 @@ bool AesCryptor::GenerateRandomIv(FourCC protection_scheme,
return true;
}
size_t AesCryptor::NumPaddingBytes(size_t size) const {
// No padding by default.
return 0;
}
} // namespace media
} // namespace edash_packager

View File

@ -24,7 +24,18 @@ namespace media {
// implementations.
class AesCryptor {
public:
AesCryptor();
enum ConstantIvFlag {
kUseConstantIv,
kDontUseConstantIv,
};
/// @param constant_iv_flag indicates whether a constant iv is used,
/// kUseConstantIv means that the same iv is used for all Crypt calls
/// until iv is changed via SetIv; otherwise, iv can be incremented
/// (for counter mode) or chained (for cipher block chaining mode)
/// internally inside Crypt call, i.e. iv will be updated across Crypt
/// calls.
explicit AesCryptor(ConstantIvFlag constant_iv_flag);
virtual ~AesCryptor();
/// Initialize the cryptor with specified key and IV.
@ -43,18 +54,28 @@ class AesCryptor {
/// @param crypt_text should have at least @a text_size bytes.
bool Crypt(const uint8_t* text, size_t text_size, uint8_t* crypt_text) {
size_t crypt_text_size = text_size;
return CryptInternal(text, text_size, crypt_text, &crypt_text_size);
return Crypt(text, text_size, crypt_text, &crypt_text_size);
}
bool Crypt(const uint8_t* text,
size_t text_size,
uint8_t* crypt_text,
size_t* crypt_text_size) {
if (constant_iv_flag_ == kUseConstantIv)
SetIvInternal();
else
num_crypt_bytes_ += text_size;
return CryptInternal(text, text_size, crypt_text, crypt_text_size);
}
/// @}
/// Set IV.
/// @return true if successful, false if the input is invalid.
virtual bool SetIv(const std::vector<uint8_t>& iv) = 0;
bool SetIv(const std::vector<uint8_t>& iv);
/// Update IV for next sample. As recommended in ISO/IEC 23001-7: IV need to
/// be updated per sample for CENC.
/// This is used by encryptors only.
virtual void UpdateIv() = 0;
/// This is used by encryptors only. It is a NOP if using kUseConstantIv.
void UpdateIv();
/// @return The current iv.
const std::vector<uint8_t>& iv() const { return iv_; }
@ -67,7 +88,6 @@ class AesCryptor {
std::vector<uint8_t>* iv);
protected:
void set_iv(const std::vector<uint8_t>& iv) { iv_ = iv; }
const AES_KEY* aes_key() const { return aes_key_.get(); }
AES_KEY* mutable_aes_key() { return aes_key_.get(); }
@ -87,16 +107,27 @@ class AesCryptor {
uint8_t* crypt_text,
size_t* crypt_text_size) = 0;
// Internal implementation of SetIv, which setup internal iv.
virtual void SetIvInternal() = 0;
// |size| specifies the input text size.
// Return the number of padding bytes needed.
// Note: No paddings should be needed except for pkcs5-cbc encryptor.
virtual size_t NumPaddingBytes(size_t size) const;
// Initialization vector, with size 8 or 16.
std::vector<uint8_t> iv_;
// Openssl AES_KEY.
scoped_ptr<AES_KEY> aes_key_;
// Indicates whether a constant iv is used. Internal iv will be reset to
// |iv_| before calling Crypt if that is the case.
const ConstantIvFlag constant_iv_flag_;
// Initialization vector from by SetIv or InitializeWithIv, with size 8 or 16
// bytes.
std::vector<uint8_t> iv_;
// Tracks number of crypt bytes. It is used to calculate how many blocks
// should iv advance in UpdateIv(). It will be reset to 0 after iv is updated.
size_t num_crypt_bytes_;
DISALLOW_COPY_AND_ASSIGN(AesCryptor);
};

View File

@ -220,6 +220,20 @@ TEST_F(AesCtrEncryptorTest, 128BitIVBoundaryCaseEncryption) {
EXPECT_EQ(encrypted, encrypted_verify);
}
TEST_F(AesCtrEncryptorTest, 64BitIvUpdate) {
std::vector<uint8_t> iv_zero(kIv64Zero, kIv64Zero + arraysize(kIv64Zero));
ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_zero));
// There are four blocks of text in |plaintext_|, but since iv is 8 bytes,
// iv should only be incremented by one when UpdateIv() is called.
std::vector<uint8_t> encrypted;
ASSERT_TRUE(encryptor_.Crypt(plaintext_, &encrypted));
std::vector<uint8_t> iv_one(kIv64One, kIv64One + arraysize(kIv64One));
encryptor_.UpdateIv();
EXPECT_EQ(iv_one, encryptor_.iv());
}
TEST_F(AesCtrEncryptorTest, GenerateRandomIv) {
const uint8_t kCencIvSize = 8;
std::vector<uint8_t> iv;
@ -297,8 +311,10 @@ INSTANTIATE_TEST_CASE_P(IvTestCases,
class AesCbcTest : public ::testing::Test {
public:
AesCbcTest()
: encryptor_(new AesCbcEncryptor(kPkcs5Padding, !kChainAcrossCalls)),
decryptor_(new AesCbcDecryptor(kPkcs5Padding, !kChainAcrossCalls)),
: encryptor_(
new AesCbcEncryptor(kPkcs5Padding, AesCryptor::kUseConstantIv)),
decryptor_(
new AesCbcDecryptor(kPkcs5Padding, AesCryptor::kUseConstantIv)),
key_(kAesKey, kAesKey + arraysize(kAesKey)),
iv_(kAesIv, kAesIv + arraysize(kAesIv)) {}
@ -450,25 +466,21 @@ TEST_F(AesCbcTest, NoPaddingNoChainAcrossCalls) {
std::vector<uint8_t> ciphertext(kCiphertext,
kCiphertext + arraysize(kCiphertext));
AesCbcEncryptor encryptor(kNoPadding, !kChainAcrossCalls);
AesCbcEncryptor encryptor(kNoPadding, AesCryptor::kUseConstantIv);
ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_));
std::vector<uint8_t> encrypted;
ASSERT_TRUE(encryptor.Crypt(plaintext, &encrypted));
EXPECT_EQ(ciphertext, encrypted);
// Iv should not have been updated.
EXPECT_EQ(iv_, encryptor.iv());
ASSERT_TRUE(encryptor.Crypt(plaintext, &encrypted));
EXPECT_EQ(ciphertext, encrypted);
AesCbcDecryptor decryptor(kNoPadding, !kChainAcrossCalls);
AesCbcDecryptor decryptor(kNoPadding, AesCryptor::kUseConstantIv);
ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_));
std::vector<uint8_t> decrypted;
ASSERT_TRUE(decryptor.Crypt(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
// Iv should not have been updated.
EXPECT_EQ(iv_, encryptor.iv());
ASSERT_TRUE(decryptor.Crypt(ciphertext, &decrypted));
EXPECT_EQ(plaintext, decrypted);
}
@ -494,26 +506,23 @@ TEST_F(AesCbcTest, NoPaddingChainAcrossCalls) {
std::vector<uint8_t> ciphertext2(kCiphertext2,
kCiphertext2 + arraysize(kCiphertext2));
AesCbcEncryptor encryptor(kNoPadding, kChainAcrossCalls);
AesCbcEncryptor encryptor(kNoPadding, AesCryptor::kDontUseConstantIv);
ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_));
std::vector<uint8_t> encrypted;
ASSERT_TRUE(encryptor.Crypt(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.Crypt(plaintext, &encrypted));
EXPECT_NE(ciphertext, ciphertext2);
EXPECT_EQ(ciphertext2, encrypted);
AesCbcDecryptor decryptor(kNoPadding, kChainAcrossCalls);
AesCbcDecryptor decryptor(kNoPadding, AesCryptor::kDontUseConstantIv);
ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_));
std::vector<uint8_t> decrypted;
ASSERT_TRUE(decryptor.Crypt(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.Crypt(ciphertext2, &decrypted));
EXPECT_EQ(plaintext, decrypted);
@ -524,9 +533,11 @@ TEST_F(AesCbcTest, UnsupportedKeySize) {
EXPECT_FALSE(decryptor_->InitializeWithIv(std::vector<uint8_t>(15, 0), iv_));
}
TEST_F(AesCbcTest, UnsupportedIvSize) {
TEST_F(AesCbcTest, VariousIvSize) {
EXPECT_FALSE(encryptor_->InitializeWithIv(key_, std::vector<uint8_t>(14, 0)));
EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector<uint8_t>(8, 0)));
EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector<uint8_t>(7, 0)));
EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector<uint8_t>(1, 0)));
EXPECT_TRUE(decryptor_->InitializeWithIv(key_, std::vector<uint8_t>(8, 0)));
}
TEST_F(AesCbcTest, Pkcs5CipherTextNotMultipleOfBlockSize) {
@ -584,10 +595,10 @@ class AesCbcCryptorVerificationTest
public ::testing::WithParamInterface<CbcTestCase> {};
TEST_P(AesCbcCryptorVerificationTest, EncryptDecryptTest) {
encryptor_.reset(
new AesCbcEncryptor(GetParam().padding_scheme, !kChainAcrossCalls));
decryptor_.reset(
new AesCbcDecryptor(GetParam().padding_scheme, !kChainAcrossCalls));
encryptor_.reset(new AesCbcEncryptor(GetParam().padding_scheme,
AesCryptor::kUseConstantIv));
decryptor_.reset(new AesCbcDecryptor(GetParam().padding_scheme,
AesCryptor::kUseConstantIv));
std::vector<uint8_t> plaintext;
std::string plaintext_hex(GetParam().plaintext_hex);

View File

@ -22,13 +22,16 @@ bool IsKeySizeValidForAes(size_t key_size) {
namespace edash_packager {
namespace media {
AesCbcDecryptor::AesCbcDecryptor(CbcPaddingScheme padding_scheme)
: AesCbcDecryptor(padding_scheme, kDontUseConstantIv) {}
AesCbcDecryptor::AesCbcDecryptor(CbcPaddingScheme padding_scheme,
bool chain_across_calls)
: padding_scheme_(padding_scheme),
chain_across_calls_(chain_across_calls) {
ConstantIvFlag constant_iv_flag)
: AesCryptor(constant_iv_flag), padding_scheme_(padding_scheme) {
if (padding_scheme_ != kNoPadding) {
CHECK(!chain_across_calls) << "cipher block chain across calls only makes "
"sense if the padding_scheme is kNoPadding.";
CHECK_EQ(constant_iv_flag, kUseConstantIv)
<< "non-constant iv (cipher block chain across calls) only makes sense "
"if the padding_scheme is kNoPadding.";
}
}
@ -46,16 +49,6 @@ bool AesCbcDecryptor::InitializeWithIv(const std::vector<uint8_t>& key,
return SetIv(iv);
}
bool AesCbcDecryptor::SetIv(const std::vector<uint8_t>& iv) {
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
set_iv(iv);
return true;
}
bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext,
size_t ciphertext_size,
uint8_t* plaintext,
@ -82,14 +75,11 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext,
}
DCHECK(plaintext);
std::vector<uint8_t> local_iv(iv());
const size_t residual_block_size = ciphertext_size % AES_BLOCK_SIZE;
const size_t cbc_size = ciphertext_size - residual_block_size;
if (residual_block_size == 0) {
AES_cbc_encrypt(ciphertext, plaintext, ciphertext_size, aes_key(),
local_iv.data(), AES_DECRYPT);
if (chain_across_calls_)
set_iv(local_iv);
internal_iv_.data(), AES_DECRYPT);
if (padding_scheme_ != kPkcs5Padding)
return true;
@ -103,10 +93,8 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext,
*plaintext_size -= num_padding_bytes;
return true;
} else if (padding_scheme_ == kNoPadding) {
AES_cbc_encrypt(ciphertext, plaintext, cbc_size, aes_key(), local_iv.data(),
AES_DECRYPT);
if (chain_across_calls_)
set_iv(local_iv);
AES_cbc_encrypt(ciphertext, plaintext, cbc_size, aes_key(),
internal_iv_.data(), AES_DECRYPT);
// The residual block is not encrypted.
memcpy(plaintext + cbc_size, ciphertext + cbc_size, residual_block_size);
@ -117,7 +105,6 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext,
return false;
}
DCHECK(!chain_across_calls_);
DCHECK_EQ(padding_scheme_, kCtsPadding);
if (ciphertext_size < AES_BLOCK_SIZE) {
// Don't have a full block, leave unencrypted.
@ -128,7 +115,7 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext,
// AES-CBC decrypt everything up to the next-to-last full block.
if (cbc_size > AES_BLOCK_SIZE) {
AES_cbc_encrypt(ciphertext, plaintext, cbc_size - AES_BLOCK_SIZE, aes_key(),
local_iv.data(), AES_DECRYPT);
internal_iv_.data(), AES_DECRYPT);
}
const uint8_t* next_to_last_ciphertext_block =
@ -162,9 +149,14 @@ bool AesCbcDecryptor::CryptInternal(const uint8_t* ciphertext,
// Decrypt the next-to-last full block.
AES_cbc_encrypt(next_to_last_plaintext_block, next_to_last_plaintext_block,
AES_BLOCK_SIZE, aes_key(), local_iv.data(), AES_DECRYPT);
AES_BLOCK_SIZE, aes_key(), internal_iv_.data(), AES_DECRYPT);
return true;
}
void AesCbcDecryptor::SetIvInternal() {
internal_iv_ = iv();
internal_iv_.resize(AES_BLOCK_SIZE, 0);
}
} // namespace media
} // namespace edash_packager

View File

@ -24,22 +24,28 @@ using AesCtrDecryptor = AesCtrEncryptor;
/// Class which implements AES-CBC (Cipher block chaining) decryption.
class AesCbcDecryptor : public AesCryptor {
public:
/// Creates a AesCbcDecryptor with continous cipher block chain across Crypt
/// calls.
/// @param padding_scheme indicates the padding scheme used. Currently
/// 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);
explicit AesCbcDecryptor(CbcPaddingScheme padding_scheme);
/// @param padding_scheme indicates the padding scheme used. Currently
/// supported schemes: kNoPadding, kPkcs5Padding, kCtsPadding.
/// @param constant_iv_flag indicates whether a constant iv is used,
/// kUseConstantIv means that the same iv is used for all Crypt calls
/// until iv is changed via SetIv; otherwise, iv is updated internally
/// and there is a continuous cipher block chain across Crypt calls
/// util iv is changed explicitly via SetIv or UpdateIv functions.
AesCbcDecryptor(CbcPaddingScheme padding_scheme,
ConstantIvFlag constant_iv_flag);
~AesCbcDecryptor() override;
/// @name AesCryptor implementation overrides.
/// @{
bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override;
bool SetIv(const std::vector<uint8_t>& iv) override;
void UpdateIv() override {
// Nop for decryptor.
}
/// @}
private:
@ -47,9 +53,11 @@ class AesCbcDecryptor : public AesCryptor {
size_t ciphertext_size,
uint8_t* plaintext,
size_t* plaintext_size) override;
void SetIvInternal() override;
const CbcPaddingScheme padding_scheme_;
const bool chain_across_calls_;
// 16-byte internal iv for crypto operations.
std::vector<uint8_t> internal_iv_;
DISALLOW_COPY_AND_ASSIGN(AesCbcDecryptor);
};

View File

@ -15,16 +15,13 @@ namespace {
// Increment an 8-byte counter by 1. Return true if overflowed.
bool Increment64(uint8_t* counter) {
DCHECK(counter);
for (int i = 7; i >= 0; --i)
for (int i = 7; i >= 0; --i) {
if (++counter[i] != 0)
return false;
}
return true;
}
// According to ISO/IEC FDIS 23001-7: CENC spec, IV should be either
// 64-bit (8-byte) or 128-bit (16-byte).
bool IsIvSizeValid(size_t iv_size) { return iv_size == 8 || iv_size == 16; }
// AES defines three key sizes: 128, 192 and 256 bits.
bool IsKeySizeValidForAes(size_t key_size) {
return key_size == 16 || key_size == 24 || key_size == 32;
@ -35,7 +32,8 @@ bool IsKeySizeValidForAes(size_t key_size) {
namespace edash_packager {
namespace media {
AesEncryptor::AesEncryptor() {}
AesEncryptor::AesEncryptor(ConstantIvFlag constant_iv_flag)
: AesCryptor(constant_iv_flag) {}
AesEncryptor::~AesEncryptor() {}
bool AesEncryptor::InitializeWithIv(const std::vector<uint8_t>& key,
@ -50,55 +48,15 @@ bool AesEncryptor::InitializeWithIv(const std::vector<uint8_t>& key,
return SetIv(iv);
}
// We don't support constant iv for counter mode, as we don't have a use case
// for that.
AesCtrEncryptor::AesCtrEncryptor()
: block_offset_(0),
encrypted_counter_(AES_BLOCK_SIZE, 0),
counter_overflow_(false) {}
: AesEncryptor(kDontUseConstantIv),
block_offset_(0),
encrypted_counter_(AES_BLOCK_SIZE, 0) {}
AesCtrEncryptor::~AesCtrEncryptor() {}
void AesCtrEncryptor::UpdateIv() {
block_offset_ = 0;
// 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) {
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())) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
block_offset_ = 0;
set_iv(iv);
counter_ = iv;
counter_.resize(AES_BLOCK_SIZE, 0);
return true;
}
bool AesCtrEncryptor::CryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
@ -119,13 +77,12 @@ bool AesCtrEncryptor::CryptInternal(const uint8_t* plaintext,
for (size_t i = 0; i < plaintext_size; ++i) {
if (block_offset_ == 0) {
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 23001-7:2016 CENC spec, of the 16 byte counter
// 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
// subsequent block of sample data processed and is kept in network byte
// order.
if (Increment64(&counter_[8]))
counter_overflow_ = true;
Increment64(&counter_[8]);
}
ciphertext[i] = plaintext[i] ^ encrypted_counter_[block_offset_];
block_offset_ = (block_offset_ + 1) % AES_BLOCK_SIZE;
@ -133,37 +90,27 @@ bool AesCtrEncryptor::CryptInternal(const uint8_t* plaintext,
return true;
}
void AesCtrEncryptor::SetIvInternal() {
block_offset_ = 0;
counter_ = iv();
counter_.resize(AES_BLOCK_SIZE, 0);
}
AesCbcEncryptor::AesCbcEncryptor(CbcPaddingScheme padding_scheme)
: AesCbcEncryptor(padding_scheme, kDontUseConstantIv) {}
AesCbcEncryptor::AesCbcEncryptor(CbcPaddingScheme padding_scheme,
bool chain_across_calls)
: padding_scheme_(padding_scheme),
chain_across_calls_(chain_across_calls) {
ConstantIvFlag constant_iv_flag)
: AesEncryptor(constant_iv_flag), padding_scheme_(padding_scheme) {
if (padding_scheme_ != kNoPadding) {
CHECK(!chain_across_calls) << "cipher block chain across calls only makes "
"sense if the padding_scheme is kNoPadding.";
CHECK_EQ(constant_iv_flag, kUseConstantIv)
<< "non-constant iv (cipher block chain across calls) only makes sense "
"if the padding_scheme is kNoPadding.";
}
}
AesCbcEncryptor::~AesCbcEncryptor() {}
void AesCbcEncryptor::UpdateIv() {
// From CENC spec: CBC mode Initialization Vectors need not be unique per
// sample or Subsample and may be generated randomly or sequentially, e.g.
// 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.
}
bool AesCbcEncryptor::SetIv(const std::vector<uint8_t>& iv) {
if (iv.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Invalid IV size: " << iv.size();
return false;
}
set_iv(iv);
return true;
}
bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext,
@ -182,22 +129,18 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext,
// 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);
AES_cbc_encrypt(plaintext, ciphertext, cbc_size, aes_key(),
internal_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_);
if (padding_scheme_ == kNoPadding) {
// The residual block is left unencrypted.
@ -216,7 +159,8 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext,
// 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);
AES_BLOCK_SIZE, aes_key(), internal_iv_.data(),
AES_ENCRYPT);
} else {
DCHECK_EQ(num_padding_bytes, 0u);
DCHECK_EQ(padding_scheme_, kCtsPadding);
@ -224,7 +168,8 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext,
// 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);
AES_BLOCK_SIZE, aes_key(), internal_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
@ -239,6 +184,11 @@ bool AesCbcEncryptor::CryptInternal(const uint8_t* plaintext,
return true;
}
void AesCbcEncryptor::SetIvInternal() {
internal_iv_ = iv();
internal_iv_.resize(AES_BLOCK_SIZE, 0);
}
size_t AesCbcEncryptor::NumPaddingBytes(size_t size) const {
return (padding_scheme_ == kPkcs5Padding)
? (AES_BLOCK_SIZE - (size % AES_BLOCK_SIZE))

View File

@ -21,7 +21,13 @@ namespace media {
class AesEncryptor : public AesCryptor {
public:
AesEncryptor();
/// @param constant_iv_flag indicates whether a constant iv is used,
/// kUseConstantIv means that the same iv is used for all Crypt calls
/// until iv is changed via SetIv; otherwise, iv can be incremented
/// (for counter mode) or chained (for cipher block chaining mode)
/// internally inside Crypt call, i.e. iv will be updated across Crypt
/// calls.
explicit AesEncryptor(ConstantIvFlag constant_iv_flag);
~AesEncryptor() override;
/// Initialize the encryptor with specified key and IV.
@ -39,17 +45,6 @@ class AesCtrEncryptor : public AesEncryptor {
AesCtrEncryptor();
~AesCtrEncryptor() override;
/// @name AesCryptor implementation overrides.
/// @{
/// Update IV for next sample. @a block_offset_ is reset to 0.
/// As recommended in ISO/IEC FDIS 23001-7: CENC spec,
/// For 64-bit IV size, new_iv = old_iv + 1;
/// For 128-bit IV size, new_iv = old_iv + previous_sample_block_count.
void UpdateIv() override;
bool SetIv(const std::vector<uint8_t>& iv) override;
/// @}
uint32_t block_offset() const { return block_offset_; }
private:
@ -57,6 +52,7 @@ class AesCtrEncryptor : public AesEncryptor {
size_t plaintext_size,
uint8_t* ciphertext,
size_t* ciphertext_size) override;
void SetIvInternal() override;
// Current block offset.
uint32_t block_offset_;
@ -64,8 +60,6 @@ class AesCtrEncryptor : public AesEncryptor {
std::vector<uint8_t> counter_;
// Encrypted counter.
std::vector<uint8_t> encrypted_counter_;
// Keep track of whether the counter has overflowed.
bool counter_overflow_;
DISALLOW_COPY_AND_ASSIGN(AesCtrEncryptor);
};
@ -80,35 +74,38 @@ enum CbcPaddingScheme {
kCtsPadding,
};
const bool kChainAcrossCalls = true;
// Class which implements AES-CBC (Cipher block chaining) encryption.
class AesCbcEncryptor : public AesEncryptor {
public:
/// Creates a AesCbcEncryptor with continous cipher block chain across Crypt
/// calls, i.e. AesCbcEncryptor(padding_scheme, kDontUseConstantIv).
/// @param padding_scheme indicates the padding scheme used. Currently
/// 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);
explicit AesCbcEncryptor(CbcPaddingScheme padding_scheme);
/// @param padding_scheme indicates the padding scheme used. Currently
/// supported schemes: kNoPadding, kPkcs5Padding, kCtsPadding.
/// @param constant_iv_flag indicates whether a constant iv is used,
/// kUseConstantIv means that the same iv is used for all Crypt calls
/// until iv is changed via SetIv; otherwise, iv is updated internally
/// and there is a continuous cipher block chain across Crypt calls
/// util iv is changed explicitly via SetIv or UpdateIv functions.
AesCbcEncryptor(CbcPaddingScheme padding_scheme,
ConstantIvFlag constant_iv_flag);
~AesCbcEncryptor() override;
/// @name AesCryptor implementation overrides.
/// @{
void UpdateIv() override;
bool SetIv(const std::vector<uint8_t>& iv) override;
/// @}
private:
bool CryptInternal(const uint8_t* plaintext,
size_t plaintext_size,
uint8_t* ciphertext,
size_t* ciphertext_size) override;
void SetIvInternal() override;
size_t NumPaddingBytes(size_t size) const override;
const CbcPaddingScheme padding_scheme_;
const bool chain_across_calls_;
// 16-byte internal iv for crypto operations.
std::vector<uint8_t> internal_iv_;
DISALLOW_COPY_AND_ASSIGN(AesCbcEncryptor);
};

View File

@ -17,9 +17,9 @@ AesPatternCryptor::AesPatternCryptor(uint8_t crypt_byte_block,
uint8_t skip_byte_block,
ConstantIvFlag constant_iv_flag,
scoped_ptr<AesCryptor> cryptor)
: crypt_byte_block_(crypt_byte_block),
: AesCryptor(constant_iv_flag),
crypt_byte_block_(crypt_byte_block),
skip_byte_block_(skip_byte_block),
constant_iv_flag_(constant_iv_flag),
cryptor_(cryptor.Pass()) {
DCHECK(cryptor_);
}
@ -28,26 +28,13 @@ AesPatternCryptor::~AesPatternCryptor() {}
bool AesPatternCryptor::InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) {
iv_ = iv;
return cryptor_->InitializeWithIv(key, iv);
}
bool AesPatternCryptor::SetIv(const std::vector<uint8_t>& iv) {
iv_ = iv;
return cryptor_->SetIv(iv);
}
void AesPatternCryptor::UpdateIv() {
cryptor_->UpdateIv();
return SetIv(iv) && cryptor_->InitializeWithIv(key, iv);
}
bool AesPatternCryptor::CryptInternal(const uint8_t* text,
size_t text_size,
uint8_t* crypt_text,
size_t* crypt_text_size) {
if (constant_iv_flag_ == AesPatternCryptor::kUseConstantIv)
CHECK(SetIv(iv_));
// |crypt_text_size| is always the same as |text_size| for pattern encryption.
if (*crypt_text_size < text_size) {
LOG(ERROR) << "Expecting output size of at least " << text_size
@ -80,5 +67,9 @@ bool AesPatternCryptor::CryptInternal(const uint8_t* text,
return true;
}
void AesPatternCryptor::SetIvInternal() {
CHECK(cryptor_->SetIv(iv()));
}
} // namespace media
} // namespace edash_packager

View File

@ -15,11 +15,6 @@ namespace media {
/// Implements pattern-based encryption/decryption.
class AesPatternCryptor : public AesCryptor {
public:
enum ConstantIvFlag {
kUseConstantIv,
kDontUseConstantIv,
};
/// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in
/// pattern based encryption.
/// @param skip_byte_block indicates number of unencrypted blocks (16-byte)
@ -42,22 +37,18 @@ class AesPatternCryptor : public AesCryptor {
/// @{
bool InitializeWithIv(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv) override;
bool SetIv(const std::vector<uint8_t>& iv) override;
void UpdateIv() override;
/// @}
protected:
private:
bool CryptInternal(const uint8_t* text,
size_t text_size,
uint8_t* crypt_text,
size_t* crypt_text_size) override;
void SetIvInternal() override;
private:
const uint8_t crypt_byte_block_;
const uint8_t skip_byte_block_;
const ConstantIvFlag constant_iv_flag_;
scoped_ptr<AesCryptor> cryptor_;
std::vector<uint8_t> iv_;
DISALLOW_COPY_AND_ASSIGN(AesPatternCryptor);
};

View File

@ -24,16 +24,17 @@ namespace media {
class MockAesCryptor : public AesCryptor {
public:
MockAesCryptor() : AesCryptor(kDontUseConstantIv) {}
MOCK_METHOD2(InitializeWithIv,
bool(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv));
MOCK_METHOD1(SetIv, bool(const std::vector<uint8_t>& iv));
MOCK_METHOD0(UpdateIv, void());
MOCK_METHOD4(CryptInternal,
bool(const uint8_t* text,
size_t text_size,
uint8_t* crypt_text,
size_t* crypt_text_size));
MOCK_METHOD0(SetIvInternal, void());
};
class AesPatternCryptorTest : public ::testing::Test {
@ -42,7 +43,7 @@ class AesPatternCryptorTest : public ::testing::Test {
: mock_cryptor_(new MockAesCryptor),
pattern_cryptor_(kCryptByteBlock,
kSkipByteBlock,
AesPatternCryptor::kDontUseConstantIv,
AesCryptor::kDontUseConstantIv,
scoped_ptr<MockAesCryptor>(mock_cryptor_)) {}
protected:
@ -55,11 +56,7 @@ TEST_F(AesPatternCryptorTest, InitializeWithIv) {
std::vector<uint8_t> iv(8, 'i');
EXPECT_CALL(*mock_cryptor_, InitializeWithIv(key, iv)).WillOnce(Return(true));
EXPECT_TRUE(pattern_cryptor_.InitializeWithIv(key, iv));
}
TEST_F(AesPatternCryptorTest, UpdateIv) {
EXPECT_CALL(*mock_cryptor_, UpdateIv());
pattern_cryptor_.UpdateIv();
EXPECT_EQ(iv, pattern_cryptor_.iv());
}
namespace {
@ -152,7 +149,7 @@ TEST(AesPatternCryptorConstIvTest, UseConstantIv) {
// SetIv will be called twice:
// once by AesPatternCryptor::SetIv,
// once by AesPatternCryptor::Crypt, to make sure the same iv is used.
EXPECT_CALL(*mock_cryptor, SetIv(iv)).Times(2).WillRepeatedly(Return(true));
EXPECT_CALL(*mock_cryptor, SetIvInternal()).Times(2);
EXPECT_TRUE(pattern_cryptor.SetIv(iv));
std::string crypt_text;
@ -167,7 +164,7 @@ TEST(AesPatternCryptorConstIvTest, DontUseConstantIv) {
std::vector<uint8_t> iv(8, 'i');
// SetIv will be called only once by AesPatternCryptor::SetIv.
EXPECT_CALL(*mock_cryptor, SetIv(iv)).WillOnce(Return(true));
EXPECT_CALL(*mock_cryptor, SetIvInternal());
EXPECT_TRUE(pattern_cryptor.SetIv(iv));
std::string crypt_text;

View File

@ -46,22 +46,19 @@ bool DecryptorSource::DecryptSampleBuffer(const DecryptConfig* decrypt_config,
aes_decryptor.reset(new AesCtrDecryptor);
break;
case FOURCC_cbc1:
aes_decryptor.reset(new AesCbcDecryptor(kNoPadding, kChainAcrossCalls));
aes_decryptor.reset(new AesCbcDecryptor(kNoPadding));
break;
case FOURCC_cens:
aes_decryptor.reset(
new AesPatternCryptor(decrypt_config->crypt_byte_block(),
decrypt_config->skip_byte_block(),
AesPatternCryptor::kDontUseConstantIv,
aes_decryptor.reset(new AesPatternCryptor(
decrypt_config->crypt_byte_block(),
decrypt_config->skip_byte_block(), AesCryptor::kDontUseConstantIv,
scoped_ptr<AesCryptor>(new AesCtrDecryptor)));
break;
case FOURCC_cbcs:
aes_decryptor.reset(
new AesPatternCryptor(decrypt_config->crypt_byte_block(),
decrypt_config->skip_byte_block(),
AesPatternCryptor::kUseConstantIv,
scoped_ptr<AesCryptor>(new AesCbcDecryptor(
kNoPadding, kChainAcrossCalls))));
aes_decryptor.reset(new AesPatternCryptor(
decrypt_config->crypt_byte_block(),
decrypt_config->skip_byte_block(), AesCryptor::kUseConstantIv,
scoped_ptr<AesCryptor>(new AesCbcDecryptor(kNoPadding))));
break;
default:
LOG(ERROR) << "Unsupported protection scheme: "

View File

@ -41,7 +41,7 @@ AesRequestSigner* AesRequestSigner::CreateSigner(const std::string& signer_name,
}
scoped_ptr<AesCbcEncryptor> encryptor(
new AesCbcEncryptor(kPkcs5Padding, !kChainAcrossCalls));
new AesCbcEncryptor(kPkcs5Padding, AesCryptor::kUseConstantIv));
if (!encryptor->InitializeWithIv(aes_key, iv))
return NULL;
return new AesRequestSigner(signer_name, encryptor.Pass());

View File

@ -197,20 +197,17 @@ Status EncryptingFragmenter::CreateEncryptor() {
encryptor.reset(new AesCtrEncryptor);
break;
case FOURCC_cbc1:
encryptor.reset(new AesCbcEncryptor(kNoPadding, kChainAcrossCalls));
encryptor.reset(new AesCbcEncryptor(kNoPadding));
break;
case FOURCC_cens:
encryptor.reset(
new AesPatternCryptor(crypt_byte_block(), skip_byte_block(),
AesPatternCryptor::kDontUseConstantIv,
encryptor.reset(new AesPatternCryptor(
crypt_byte_block(), skip_byte_block(), AesCryptor::kDontUseConstantIv,
scoped_ptr<AesCryptor>(new AesCtrEncryptor)));
break;
case FOURCC_cbcs:
encryptor.reset(
new AesPatternCryptor(crypt_byte_block(), skip_byte_block(),
AesPatternCryptor::kUseConstantIv,
scoped_ptr<AesCryptor>(new AesCbcEncryptor(
kNoPadding, kChainAcrossCalls))));
encryptor.reset(new AesPatternCryptor(
crypt_byte_block(), skip_byte_block(), AesCryptor::kUseConstantIv,
scoped_ptr<AesCryptor>(new AesCbcEncryptor(kNoPadding))));
break;
default:
return Status(error::MUXER_FAILURE, "Unsupported protection scheme.");

View File

@ -1114,7 +1114,7 @@ bool WvmMediaParser::ProcessEcm() {
encryption_key.key.begin(),
encryption_key.key.begin() + kAssetKeySizeBytes);
std::vector<uint8_t> iv(kInitializationVectorSizeBytes);
AesCbcDecryptor asset_decryptor(kCtsPadding, !kChainAcrossCalls);
AesCbcDecryptor asset_decryptor(kCtsPadding, AesCryptor::kUseConstantIv);
if (!asset_decryptor.InitializeWithIv(asset_key, iv)) {
LOG(ERROR) << "Failed to initialize asset_decryptor.";
return false;
@ -1131,7 +1131,7 @@ bool WvmMediaParser::ProcessEcm() {
content_key_buffer.begin() + 4,
content_key_buffer.begin() + 20);
scoped_ptr<AesCbcDecryptor> content_decryptor(
new AesCbcDecryptor(kCtsPadding, !kChainAcrossCalls));
new AesCbcDecryptor(kCtsPadding, AesCryptor::kUseConstantIv));
if (!content_decryptor->InitializeWithIv(decrypted_content_key_vec, iv)) {
LOG(ERROR) << "Failed to initialize content decryptor.";
return false;