diff --git a/packager/app/fixed_key_encryption_flags.cc b/packager/app/fixed_key_encryption_flags.cc index 9ba3646307..a176ce11eb 100644 --- a/packager/app/fixed_key_encryption_flags.cc +++ b/packager/app/fixed_key_encryption_flags.cc @@ -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", diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 308b913421..ec103bdee4 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -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; diff --git a/packager/app/widevine_encryption_flags.cc b/packager/app/widevine_encryption_flags.cc index 80bbcd224f..d7edde94df 100644 --- a/packager/app/widevine_encryption_flags.cc +++ b/packager/app/widevine_encryption_flags.cc @@ -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 { diff --git a/packager/media/base/aes_cryptor.cc b/packager/media/base/aes_cryptor.cc index dee1d3e909..8d7d242360 100644 --- a/packager/media/base/aes_cryptor.cc +++ b/packager/media/base/aes_cryptor.cc @@ -6,6 +6,9 @@ #include "packager/media/base/aes_cryptor.h" +#include +#include + #include #include #include @@ -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& text, @@ -26,8 +43,7 @@ bool AesCryptor::Crypt(const std::vector& 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,18 +57,53 @@ 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(text.data()), text_size, - reinterpret_cast(string_as_array(crypt_text)), - &crypt_text_size)) + if (!Crypt(reinterpret_cast(text.data()), text_size, + reinterpret_cast(string_as_array(crypt_text)), + &crypt_text_size)) return false; DCHECK_LE(crypt_text_size, crypt_text->size()); crypt_text->resize(crypt_text_size); return true; } -size_t AesCryptor::NumPaddingBytes(size_t size) const { - // No padding by default. - return 0; +bool AesCryptor::SetIv(const std::vector& 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 diff --git a/packager/media/base/aes_cryptor.h b/packager/media/base/aes_cryptor.h index c17fbd307f..bc7d9a0ee9 100644 --- a/packager/media/base/aes_cryptor.h +++ b/packager/media/base/aes_cryptor.h @@ -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& iv) = 0; + bool SetIv(const std::vector& 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& iv() const { return iv_; } @@ -67,7 +88,6 @@ class AesCryptor { std::vector* iv); protected: - void set_iv(const std::vector& 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 iv_; // Openssl AES_KEY. scoped_ptr 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 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); }; diff --git a/packager/media/base/aes_cryptor_unittest.cc b/packager/media/base/aes_cryptor_unittest.cc index de4b8d6596..1ad5b0a5b6 100644 --- a/packager/media/base/aes_cryptor_unittest.cc +++ b/packager/media/base/aes_cryptor_unittest.cc @@ -220,6 +220,20 @@ TEST_F(AesCtrEncryptorTest, 128BitIVBoundaryCaseEncryption) { EXPECT_EQ(encrypted, encrypted_verify); } +TEST_F(AesCtrEncryptorTest, 64BitIvUpdate) { + std::vector 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 encrypted; + ASSERT_TRUE(encryptor_.Crypt(plaintext_, &encrypted)); + + std::vector 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 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 ciphertext(kCiphertext, kCiphertext + arraysize(kCiphertext)); - AesCbcEncryptor encryptor(kNoPadding, !kChainAcrossCalls); + AesCbcEncryptor encryptor(kNoPadding, AesCryptor::kUseConstantIv); ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_)); std::vector 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 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 ciphertext2(kCiphertext2, kCiphertext2 + arraysize(kCiphertext2)); - AesCbcEncryptor encryptor(kNoPadding, kChainAcrossCalls); + AesCbcEncryptor encryptor(kNoPadding, AesCryptor::kDontUseConstantIv); ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_)); std::vector 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 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(15, 0), iv_)); } -TEST_F(AesCbcTest, UnsupportedIvSize) { +TEST_F(AesCbcTest, VariousIvSize) { EXPECT_FALSE(encryptor_->InitializeWithIv(key_, std::vector(14, 0))); - EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector(8, 0))); + EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector(7, 0))); + EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector(1, 0))); + EXPECT_TRUE(decryptor_->InitializeWithIv(key_, std::vector(8, 0))); } TEST_F(AesCbcTest, Pkcs5CipherTextNotMultipleOfBlockSize) { @@ -584,10 +595,10 @@ class AesCbcCryptorVerificationTest public ::testing::WithParamInterface {}; 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 plaintext; std::string plaintext_hex(GetParam().plaintext_hex); diff --git a/packager/media/base/aes_decryptor.cc b/packager/media/base/aes_decryptor.cc index 969219b907..db815aee63 100644 --- a/packager/media/base/aes_decryptor.cc +++ b/packager/media/base/aes_decryptor.cc @@ -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& key, return SetIv(iv); } -bool AesCbcDecryptor::SetIv(const std::vector& 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 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 diff --git a/packager/media/base/aes_decryptor.h b/packager/media/base/aes_decryptor.h index 3dfcb3cff3..0d717a1b55 100644 --- a/packager/media/base/aes_decryptor.h +++ b/packager/media/base/aes_decryptor.h @@ -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& key, const std::vector& iv) override; - bool SetIv(const std::vector& 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 internal_iv_; DISALLOW_COPY_AND_ASSIGN(AesCbcDecryptor); }; diff --git a/packager/media/base/aes_encryptor.cc b/packager/media/base/aes_encryptor.cc index 4cce506d13..eb45906f13 100644 --- a/packager/media/base/aes_encryptor.cc +++ b/packager/media/base/aes_encryptor.cc @@ -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& key, @@ -50,55 +48,15 @@ bool AesEncryptor::InitializeWithIv(const std::vector& 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& 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& 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 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(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)) diff --git a/packager/media/base/aes_encryptor.h b/packager/media/base/aes_encryptor.h index 0b58b2a505..6393bc1943 100644 --- a/packager/media/base/aes_encryptor.h +++ b/packager/media/base/aes_encryptor.h @@ -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& 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 counter_; // Encrypted counter. std::vector 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& 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 internal_iv_; DISALLOW_COPY_AND_ASSIGN(AesCbcEncryptor); }; diff --git a/packager/media/base/aes_pattern_cryptor.cc b/packager/media/base/aes_pattern_cryptor.cc index 3ad69a863d..dcff2bbedd 100644 --- a/packager/media/base/aes_pattern_cryptor.cc +++ b/packager/media/base/aes_pattern_cryptor.cc @@ -17,9 +17,9 @@ AesPatternCryptor::AesPatternCryptor(uint8_t crypt_byte_block, uint8_t skip_byte_block, ConstantIvFlag constant_iv_flag, scoped_ptr 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& key, const std::vector& iv) { - iv_ = iv; - return cryptor_->InitializeWithIv(key, iv); -} - -bool AesPatternCryptor::SetIv(const std::vector& 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 diff --git a/packager/media/base/aes_pattern_cryptor.h b/packager/media/base/aes_pattern_cryptor.h index 3eb6c2d8b4..7078dc4349 100644 --- a/packager/media/base/aes_pattern_cryptor.h +++ b/packager/media/base/aes_pattern_cryptor.h @@ -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& key, const std::vector& iv) override; - bool SetIv(const std::vector& 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 cryptor_; - std::vector iv_; DISALLOW_COPY_AND_ASSIGN(AesPatternCryptor); }; diff --git a/packager/media/base/aes_pattern_cryptor_unittest.cc b/packager/media/base/aes_pattern_cryptor_unittest.cc index 8ac1402019..b4e9ee726d 100644 --- a/packager/media/base/aes_pattern_cryptor_unittest.cc +++ b/packager/media/base/aes_pattern_cryptor_unittest.cc @@ -24,16 +24,17 @@ namespace media { class MockAesCryptor : public AesCryptor { public: + MockAesCryptor() : AesCryptor(kDontUseConstantIv) {} + MOCK_METHOD2(InitializeWithIv, bool(const std::vector& key, const std::vector& iv)); - MOCK_METHOD1(SetIv, bool(const std::vector& 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(mock_cryptor_)) {} protected: @@ -55,11 +56,7 @@ TEST_F(AesPatternCryptorTest, InitializeWithIv) { std::vector 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 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; diff --git a/packager/media/base/decryptor_source.cc b/packager/media/base/decryptor_source.cc index 16ed5d4460..0866e98da5 100644 --- a/packager/media/base/decryptor_source.cc +++ b/packager/media/base/decryptor_source.cc @@ -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, - scoped_ptr(new AesCtrDecryptor))); + aes_decryptor.reset(new AesPatternCryptor( + decrypt_config->crypt_byte_block(), + decrypt_config->skip_byte_block(), AesCryptor::kDontUseConstantIv, + scoped_ptr(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(new AesCbcDecryptor( - kNoPadding, kChainAcrossCalls)))); + aes_decryptor.reset(new AesPatternCryptor( + decrypt_config->crypt_byte_block(), + decrypt_config->skip_byte_block(), AesCryptor::kUseConstantIv, + scoped_ptr(new AesCbcDecryptor(kNoPadding)))); break; default: LOG(ERROR) << "Unsupported protection scheme: " diff --git a/packager/media/base/request_signer.cc b/packager/media/base/request_signer.cc index d8314185b0..7b050f72ef 100644 --- a/packager/media/base/request_signer.cc +++ b/packager/media/base/request_signer.cc @@ -41,7 +41,7 @@ AesRequestSigner* AesRequestSigner::CreateSigner(const std::string& signer_name, } scoped_ptr 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()); diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index 27fdb9be5e..080ddc8bdc 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -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, - scoped_ptr(new AesCtrEncryptor))); + encryptor.reset(new AesPatternCryptor( + crypt_byte_block(), skip_byte_block(), AesCryptor::kDontUseConstantIv, + scoped_ptr(new AesCtrEncryptor))); break; case FOURCC_cbcs: - encryptor.reset( - new AesPatternCryptor(crypt_byte_block(), skip_byte_block(), - AesPatternCryptor::kUseConstantIv, - scoped_ptr(new AesCbcEncryptor( - kNoPadding, kChainAcrossCalls)))); + encryptor.reset(new AesPatternCryptor( + crypt_byte_block(), skip_byte_block(), AesCryptor::kUseConstantIv, + scoped_ptr(new AesCbcEncryptor(kNoPadding)))); break; default: return Status(error::MUXER_FAILURE, "Unsupported protection scheme."); diff --git a/packager/media/formats/wvm/wvm_media_parser.cc b/packager/media/formats/wvm/wvm_media_parser.cc index 2aa02e9dfb..8fa47e6e37 100644 --- a/packager/media/formats/wvm/wvm_media_parser.cc +++ b/packager/media/formats/wvm/wvm_media_parser.cc @@ -1114,7 +1114,7 @@ bool WvmMediaParser::ProcessEcm() { encryption_key.key.begin(), encryption_key.key.begin() + kAssetKeySizeBytes); std::vector 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 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;