From a9e5a2ff4f6470692e4e4a8958f1830a4306155c Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Fri, 25 Mar 2016 11:02:43 -0700 Subject: [PATCH] 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 --- packager/app/packager_main.cc | 8 +- packager/media/base/aes_decryptor.cc | 296 ++++++------- packager/media/base/aes_decryptor.h | 119 +++--- packager/media/base/aes_encryptor.cc | 365 +++++++--------- packager/media/base/aes_encryptor.h | 125 +++--- packager/media/base/aes_encryptor_unittest.cc | 393 ++++++++++-------- packager/media/base/decryptor_source.cc | 2 +- packager/media/base/request_signer.cc | 5 +- packager/media/base/request_signer.h | 6 +- .../formats/mp4/encrypting_fragmenter.cc | 42 +- packager/media/formats/webm/encryptor.cc | 2 +- .../media/formats/wvm/wvm_media_parser.cc | 9 +- packager/media/formats/wvm/wvm_media_parser.h | 4 +- 13 files changed, 667 insertions(+), 709 deletions(-) diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 0f28b06528..144efac5f6 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -112,7 +112,7 @@ edash_packager::media::EncryptionMode GetEncryptionMode( } else if (protection_scheme == "cbc1") { return edash_packager::media::kEncryptionModeAesCbc; } else { - LOG(ERROR) << "Protection scheme is unknown."; + LOG(ERROR) << "Unknown protection scheme: " << protection_scheme; return edash_packager::media::kEncryptionModeUnknown; } } @@ -377,6 +377,12 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) { EncryptionMode encryption_mode = GetEncryptionMode(FLAGS_protection_scheme); if (encryption_mode == kEncryptionModeUnknown) 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()) return false; diff --git a/packager/media/base/aes_decryptor.cc b/packager/media/base/aes_decryptor.cc index 9841e6a3c0..b62f47983d 100644 --- a/packager/media/base/aes_decryptor.cc +++ b/packager/media/base/aes_decryptor.cc @@ -27,6 +27,32 @@ namespace media { AesDecryptor::AesDecryptor() {} AesDecryptor::~AesDecryptor() {} +bool AesDecryptor::Decrypt(const std::vector& ciphertext, + std::vector* 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(ciphertext.data()), + ciphertext.size(), + reinterpret_cast(string_as_array(plaintext)), + &plaintext_size)) + return false; + plaintext->resize(plaintext_size); + return true; +} + AesCtrDecryptor::AesCtrDecryptor() {} AesCtrDecryptor::~AesCtrDecryptor() {} @@ -37,163 +63,119 @@ bool AesCtrDecryptor::InitializeWithIv(const std::vector& key, 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& ciphertext, - std::vector* 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& iv) { DCHECK(encryptor_); return encryptor_->SetIv(iv); } -AesCbcPkcs5Decryptor::AesCbcPkcs5Decryptor() {} -AesCbcPkcs5Decryptor::~AesCbcPkcs5Decryptor() {} +bool AesCtrDecryptor::DecryptInternal(const uint8_t* ciphertext, + 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& key, - const std::vector& iv) { +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& key, + const std::vector& 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_decrypt_key(key.data(), key.size() * 8, aes_key_.get()), 0); + + 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; } - 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 AesCbcPkcs5Decryptor::Decrypt(const uint8_t* ciphertext, - size_t ciphertext_size, - uint8_t* plaintext) { - NOTIMPLEMENTED(); - return false; -} - -bool AesCbcPkcs5Decryptor::Decrypt(const std::vector& ciphertext, - std::vector* 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); +bool AesCbcDecryptor::DecryptInternal(const uint8_t* ciphertext, + size_t ciphertext_size, + uint8_t* plaintext, + size_t* plaintext_size) { + DCHECK(plaintext_size); DCHECK(aes_key_); - - plaintext->resize(ciphertext.size()); - AES_cbc_encrypt(reinterpret_cast(ciphertext.data()), - reinterpret_cast(string_as_array(plaintext)), - ciphertext.size(), - aes_key_.get(), - &iv_[0], - AES_DECRYPT); - - // Strip off PKCS5 padding bytes. - const uint8_t num_padding_bytes = (*plaintext)[plaintext->size() - 1]; - if (num_padding_bytes > AES_BLOCK_SIZE) { - LOG(ERROR) << "Padding length is too large : " - << static_cast(num_padding_bytes); - return false; + // 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; } - plaintext->resize(plaintext->size() - num_padding_bytes); - return true; -} - -bool AesCbcPkcs5Decryptor::SetIv(const std::vector& iv) { - if (iv.size() != AES_BLOCK_SIZE) { - LOG(ERROR) << "Invalid IV size: " << iv.size(); - return false; - } - - iv_ = iv; - return true; -} - -AesCbcCtsDecryptor::AesCbcCtsDecryptor() {} -AesCbcCtsDecryptor::~AesCbcCtsDecryptor() {} - -bool AesCbcCtsDecryptor::InitializeWithIv(const std::vector& key, - const std::vector& 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); + std::vector local_iv(iv_); + const size_t residual_block_size = ciphertext_size % AES_BLOCK_SIZE; + if (residual_block_size == 0) { + AES_cbc_encrypt(ciphertext, plaintext, ciphertext_size, aes_key_.get(), + local_iv.data(), AES_DECRYPT); + if (chain_across_calls_) + iv_ = local_iv; + if (padding_scheme_ != kPkcs5Padding) + return true; + + // Strip off PKCS5 padding bytes. + const uint8_t num_padding_bytes = plaintext[ciphertext_size - 1]; + if (num_padding_bytes > AES_BLOCK_SIZE) { + LOG(ERROR) << "Padding length is too large : " + << static_cast(num_padding_bytes); + return false; + } + *plaintext_size -= num_padding_bytes; + return true; + } else if (padding_scheme_ != kCtsPadding) { + LOG(ERROR) << "Expecting cipher text size to be multiple of " + << AES_BLOCK_SIZE << ", got " << ciphertext_size; + 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. memcpy(plaintext, ciphertext, ciphertext_size); return true; } - std::vector 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. - 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) { - AES_cbc_encrypt(ciphertext, - plaintext, - cbc_size - AES_BLOCK_SIZE, - aes_key_.get(), - &iv[0], - AES_DECRYPT); + AES_cbc_encrypt(ciphertext, plaintext, cbc_size - AES_BLOCK_SIZE, + aes_key_.get(), local_iv.data(), 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 // CBC decryption. std::vector 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 // the residual block bits. - AES_cbc_encrypt( - ciphertext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE, - plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE, - AES_BLOCK_SIZE, aes_key_.get(), &last_iv[0], AES_DECRYPT); + AES_cbc_encrypt(next_to_last_ciphertext_block, next_to_last_plaintext_block, + AES_BLOCK_SIZE, aes_key_.get(), last_iv.data(), 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) { - uint8_t* ptr1 = plaintext + ciphertext_size - residual_block_size; - uint8_t* ptr2 = plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE; - for (size_t i = 0; i < residual_block_size; ++i) { - uint8_t temp = *ptr1; - *ptr1 = *ptr2; - *ptr2 = temp; - ++ptr1; - ++ptr2; - } + std::swap_ranges(next_to_last_plaintext_block, + next_to_last_plaintext_block + residual_block_size, + next_to_last_plaintext_block + AES_BLOCK_SIZE); } else { - uint8_t* residual_plaintext_block = - plaintext + ciphertext_size - residual_block_size; - memcpy(residual_plaintext_block, residual_plaintext_block - AES_BLOCK_SIZE, - residual_block_size); - memcpy(residual_plaintext_block - AES_BLOCK_SIZE, - ciphertext + ciphertext_size - residual_block_size, - residual_block_size); + memcpy(next_to_last_plaintext_block + AES_BLOCK_SIZE, + next_to_last_plaintext_block, residual_block_size); + memcpy(next_to_last_plaintext_block, + next_to_last_ciphertext_block + AES_BLOCK_SIZE, residual_block_size); } - // Decrypt the last full block. - AES_cbc_encrypt( - plaintext + ciphertext_size - residual_block_size - AES_BLOCK_SIZE, - 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& ciphertext, - std::vector* 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& iv) { - if (iv.size() != AES_BLOCK_SIZE) { - LOG(ERROR) << "Invalid IV size: " << iv.size(); - return false; - } - - iv_ = iv; + // 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_.get(), local_iv.data(), AES_DECRYPT); return true; } diff --git a/packager/media/base/aes_decryptor.h b/packager/media/base/aes_decryptor.h index 27baacf001..5545224b40 100644 --- a/packager/media/base/aes_decryptor.h +++ b/packager/media/base/aes_decryptor.h @@ -27,34 +27,42 @@ class AesDecryptor { AesDecryptor(); virtual ~AesDecryptor(); + /// Initialize the decryptor with specified key and IV. + /// @return true on successful initialization, false otherwise. virtual bool InitializeWithIv(const std::vector& key, const std::vector& iv) = 0; /// @name Various forms of decrypt calls. /// The plaintext and ciphertext pointers can be the same address. /// @{ - virtual bool Decrypt(const uint8_t* ciphertext, - size_t ciphertext_size, - uint8_t* plaintext) = 0; - - virtual bool Decrypt(const std::vector& ciphertext, - std::vector* plaintext) = 0; - - virtual bool Decrypt(const std::string& ciphertext, - std::string* plaintext) = 0; + bool Decrypt(const std::vector& ciphertext, + std::vector* plaintext); + bool Decrypt(const std::string& ciphertext, std::string* plaintext); + bool Decrypt(const uint8_t* ciphertext, + size_t ciphertext_size, + uint8_t* plaintext) { + size_t plaintext_size; + return DecryptInternal(ciphertext, ciphertext_size, plaintext, + &plaintext_size); + } /// @} - /// Set IV. @a block_offset_ is reset to 0 on success. + /// Set IV. /// @return true if successful, false if the input is invalid. virtual bool SetIv(const std::vector& iv) = 0; - const std::vector& iv() const { return iv_; } - protected: - // Initialization vector, with size 8 or 16. - std::vector iv_; - // Openssl AES_KEY. - scoped_ptr aes_key_; + /// Internal implementation of decrypt function. + /// @param ciphertext points to the input ciphertext. + /// @param ciphertext_size is the input ciphertext size. + /// @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: DISALLOW_COPY_AND_ASSIGN(AesDecryptor); @@ -71,80 +79,57 @@ class AesCtrDecryptor : public AesDecryptor { bool InitializeWithIv(const std::vector& key, const std::vector& iv) override; - bool Decrypt(const uint8_t* ciphertext, - size_t ciphertext_size, - uint8_t* plaintext) override; - - bool Decrypt(const std::vector& ciphertext, - std::vector* plaintext) override; - - bool Decrypt(const std::string& ciphertext, std::string* plaintext) override; - bool SetIv(const std::vector& iv) override; /// @} 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: scoped_ptr encryptor_; DISALLOW_COPY_AND_ASSIGN(AesCtrDecryptor); }; -// Class which implements AES-CBC (Cipher block chaining) decryption with -// PKCS#5 padding. -class AesCbcPkcs5Decryptor : public AesDecryptor { +// Class which implements AES-CBC (Cipher block chaining) decryption. +class AesCbcDecryptor : public AesDecryptor { public: - AesCbcPkcs5Decryptor(); - ~AesCbcPkcs5Decryptor() override; + /// @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); + ~AesCbcDecryptor() override; /// @name AesDecryptor implementation overrides. /// @{ bool InitializeWithIv(const std::vector& key, const std::vector& iv) override; - bool Decrypt(const uint8_t* ciphertext, - size_t ciphertext_size, - uint8_t* plaintext) override; - - bool Decrypt(const std::vector& ciphertext, - std::vector* plaintext) override; - - bool Decrypt(const std::string& ciphertext, std::string* plaintext) override; - bool SetIv(const std::vector& iv) override; /// @} - private: - DISALLOW_COPY_AND_ASSIGN(AesCbcPkcs5Decryptor); -}; - -// 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& key, - const std::vector& iv) override; - - bool Decrypt(const uint8_t* ciphertext, - size_t ciphertext_size, - uint8_t* plaintext) override; - - bool Decrypt(const std::vector& ciphertext, - std::vector* plaintext) override; - - bool Decrypt(const std::string& ciphertext, std::string* plaintext) override; - - bool SetIv(const std::vector& iv) override; - /// @} + protected: + bool DecryptInternal(const uint8_t* ciphertext, + size_t ciphertext_size, + uint8_t* plaintext, + size_t* plaintext_size) override; private: - DISALLOW_COPY_AND_ASSIGN(AesCbcCtsDecryptor); + // Openssl AES_KEY. + scoped_ptr aes_key_; + // Initialization vector, must be 16 for CBC. + std::vector iv_; + const CbcPaddingScheme padding_scheme_; + const bool chain_across_calls_; + + DISALLOW_COPY_AND_ASSIGN(AesCbcDecryptor); }; } // namespace media diff --git a/packager/media/base/aes_encryptor.cc b/packager/media/base/aes_encryptor.cc index 63f71bb9cc..36728d22f7 100644 --- a/packager/media/base/aes_encryptor.cc +++ b/packager/media/base/aes_encryptor.cc @@ -32,9 +32,6 @@ bool IsKeySizeValidForAes(size_t key_size) { 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 edash_packager { @@ -47,7 +44,7 @@ bool AesEncryptor::InitializeWithRandomIv( const std::vector& key, uint8_t iv_size) { std::vector 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: " << ERR_error_string(ERR_get_error(), NULL); return false; @@ -55,62 +52,98 @@ bool AesEncryptor::InitializeWithRandomIv( return InitializeWithIv(key, iv); } +bool AesEncryptor::InitializeWithIv(const std::vector& key, + const std::vector& 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& plaintext, std::vector* ciphertext) { - if (plaintext.empty()) - return true; - ciphertext->resize(plaintext.size() + NumPaddingBytes(plaintext.size())); - return EncryptData(plaintext.data(), plaintext.size(), ciphertext->data()); + // Save plaintext size to make it work for in-place conversion, since the + // next statement will update the plaintext size. + const size_t plaintext_size = plaintext.size(); + ciphertext->resize(plaintext_size + NumPaddingBytes(plaintext.size())); + return EncryptInternal(plaintext.data(), plaintext_size, ciphertext->data()); } bool AesEncryptor::Encrypt(const std::string& plaintext, std::string* ciphertext) { - ciphertext->resize(plaintext.size() + NumPaddingBytes(plaintext.size())); - return EncryptData(reinterpret_cast(plaintext.data()), - plaintext.size(), - reinterpret_cast(string_as_array(ciphertext))); + // Save plaintext size to make it work for in-place conversion, since the + // next statement will update the plaintext size. + const size_t plaintext_size = plaintext.size(); + ciphertext->resize(plaintext_size + NumPaddingBytes(plaintext.size())); + return EncryptInternal( + reinterpret_cast(plaintext.data()), plaintext_size, + reinterpret_cast(string_as_array(ciphertext))); } AesCtrEncryptor::AesCtrEncryptor() : block_offset_(0), encrypted_counter_(AES_BLOCK_SIZE, 0), - counter_overflow_(false) { - COMPILE_ASSERT(AES_BLOCK_SIZE == kCencKeySize, - cenc_key_size_should_be_the_same_as_aes_block_size); -} + counter_overflow_(false) {} AesCtrEncryptor::~AesCtrEncryptor() {} -bool AesCtrEncryptor::InitializeWithIv(const std::vector& key, - const std::vector& iv) { - if (key.size() != kCencKeySize) { - LOG(ERROR) << "Invalid key size of " << key.size() << " for CENC."; - return false; +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; } - aes_key_.reset(new AES_KEY()); - CHECK_EQ(AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, aes_key_.get()), 0); - return SetIv(iv); + block_offset_ = 0; + set_iv(iv); + counter_ = iv; + counter_.resize(AES_BLOCK_SIZE, 0); + return true; } -size_t AesCtrEncryptor::NumPaddingBytes(size_t size) { - return 0; -} - -bool AesCtrEncryptor::EncryptData(const uint8_t* plaintext, - size_t plaintext_size, - uint8_t* ciphertext) { +bool AesCtrEncryptor::EncryptInternal(const uint8_t* plaintext, + size_t plaintext_size, + uint8_t* ciphertext) { DCHECK(plaintext); DCHECK(ciphertext); - DCHECK(aes_key_); + DCHECK(aes_key()); for (size_t i = 0; i < plaintext_size; ++i) { 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 // 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 @@ -125,195 +158,111 @@ bool AesCtrEncryptor::EncryptData(const uint8_t* plaintext, return true; } -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) { - 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& 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& key, - const std::vector& 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 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& 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& key, - const std::vector& 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) { +size_t AesCtrEncryptor::NumPaddingBytes(size_t size) const { + // No padding needed for CTR. return 0; } -bool AesCbcCtsEncryptor::EncryptData(const uint8_t* plaintext, - size_t size, - uint8_t* ciphertext) { - DCHECK(plaintext); - DCHECK(ciphertext); - - if (size < AES_BLOCK_SIZE) { - // Don't have a full block, leave unencrypted. - memcpy(ciphertext, plaintext, size); - return true; +AesCbcEncryptor::AesCbcEncryptor(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."; } +} +AesCbcEncryptor::~AesCbcEncryptor() {} - std::vector iv(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 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 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. } -void AesCbcCtsEncryptor::UpdateIv() {} - -bool AesCbcCtsEncryptor::SetIv(const std::vector& iv) { +bool AesCbcEncryptor::SetIv(const std::vector& iv) { if (iv.size() != AES_BLOCK_SIZE) { LOG(ERROR) << "Invalid IV size: " << iv.size(); return false; } - iv_ = iv; + set_iv(iv); 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 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 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(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 edash_packager diff --git a/packager/media/base/aes_encryptor.h b/packager/media/base/aes_encryptor.h index f584727def..72896d20fc 100644 --- a/packager/media/base/aes_encryptor.h +++ b/packager/media/base/aes_encryptor.h @@ -29,52 +29,64 @@ class AesEncryptor { /// Initialize the encryptor with specified key and a random generated IV /// of the specified size. /// @return true on successful initialization, false otherwise. - virtual bool InitializeWithRandomIv(const std::vector& key, + bool InitializeWithRandomIv(const std::vector& key, uint8_t iv_size); /// Initialize the encryptor with specified key and IV. /// @return true on successful initialization, false otherwise. - virtual bool InitializeWithIv(const std::vector& key, - const std::vector& iv) = 0; + bool InitializeWithIv(const std::vector& key, + const std::vector& iv); - virtual size_t NumPaddingBytes(size_t size) = 0; - - /// @name Various forms of encrypt and decrypt calls. + /// @name Various forms of encrypt calls. /// 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& plaintext, std::vector* 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. /// As recommended in ISO/IEC FDIS 23001-7: /// IV need to be updated per sample for CENC. - /// IV need not be unique per sample for CBC mode. virtual void UpdateIv() = 0; /// Set IV. /// @return true if successful, false if the input is invalid. virtual bool SetIv(const std::vector& iv) = 0; + /// @return The current iv. const std::vector& iv() const { return iv_; } 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& iv) { iv_ = iv; } + AES_KEY* aes_key() const { return aes_key_.get(); } + + private: // Initialization vector, with size 8 or 16. std::vector iv_; // Openssl AES_KEY. scoped_ptr aes_key_; - private: 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 { public: AesCtrEncryptor(); @@ -82,18 +94,6 @@ class AesCtrEncryptor : public AesEncryptor { /// @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& key, - const std::vector& 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. /// As recommended in ISO/IEC FDIS 23001-7: CENC spec, /// 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_; } + protected: + bool EncryptInternal(const uint8_t* plaintext, + size_t plaintext_size, + uint8_t* ciphertext) override; + size_t NumPaddingBytes(size_t size) const override; + private: // Current block offset. uint32_t block_offset_; @@ -118,58 +124,43 @@ class AesCtrEncryptor : public AesEncryptor { DISALLOW_COPY_AND_ASSIGN(AesCtrEncryptor); }; -// Class which implements AES-CBC (Cipher block chaining) encryption with -// PKCS#5 padding. -class AesCbcPkcs5Encryptor : public AesEncryptor { - public: - AesCbcPkcs5Encryptor(); - ~AesCbcPkcs5Encryptor() override; - - /// @name AesEncryptor implementation overrides. - /// @{ - bool InitializeWithIv(const std::vector& key, - const std::vector& 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& iv) override; - /// @} - - private: - DISALLOW_COPY_AND_ASSIGN(AesCbcPkcs5Encryptor); +enum CbcPaddingScheme { + kNoPadding, + kPkcs5Padding, + kCtsPadding, }; -// Class which implements AES-CBC (Cipher block chaining) encryption with -// Ciphertext stealing. -class AesCbcCtsEncryptor : public AesEncryptor { +const bool kChainAcrossCalls = true; + +// Class which implements AES-CBC (Cipher block chaining) encryption. +class AesCbcEncryptor : public AesEncryptor { public: - AesCbcCtsEncryptor(); - ~AesCbcCtsEncryptor() override; + /// @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); + ~AesCbcEncryptor() override; /// @name AesEncryptor implementation overrides. /// @{ - bool InitializeWithIv(const std::vector& key, - const std::vector& 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& 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: - DISALLOW_COPY_AND_ASSIGN(AesCbcCtsEncryptor); + const CbcPaddingScheme padding_scheme_; + const bool chain_across_calls_; + + DISALLOW_COPY_AND_ASSIGN(AesCbcEncryptor); }; } // namespace media diff --git a/packager/media/base/aes_encryptor_unittest.cc b/packager/media/base/aes_encryptor_unittest.cc index 71d2455587..435c7ca272 100644 --- a/packager/media/base/aes_encryptor_unittest.cc +++ b/packager/media/base/aes_encryptor_unittest.cc @@ -170,7 +170,7 @@ TEST_F(AesCtrEncryptorTest, NistTestCase) { TEST_F(AesCtrEncryptorTest, NistTestCaseInplaceEncryptionDecryption) { std::vector 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_TRUE(decryptor_.SetIv(iv_)); @@ -211,13 +211,12 @@ TEST_F(AesCtrEncryptorTest, 128BitIVBoundaryCaseEncryption) { ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_max64)); std::vector encrypted_verify(plaintext_.size(), 0); - EXPECT_TRUE(encryptor_.EncryptData(&plaintext_[0], kAesBlockSize, - &encrypted_verify[0])); + EXPECT_TRUE( + encryptor_.Encrypt(&plaintext_[0], kAesBlockSize, &encrypted_verify[0])); std::vector iv_zero(kIv128Zero, kIv128Zero + arraysize(kIv128Zero)); ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_zero)); - EXPECT_TRUE(encryptor_.EncryptData(&plaintext_[kAesBlockSize], - kAesBlockSize * 3, - &encrypted_verify[kAesBlockSize])); + EXPECT_TRUE(encryptor_.Encrypt(&plaintext_[kAesBlockSize], kAesBlockSize * 3, + &encrypted_verify[kAesBlockSize])); 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) { uint32_t len = test_case->subsample_sizes[i]; EXPECT_TRUE( - encryptor_.EncryptData(&plaintext_[offset], len, &encrypted[offset])); + encryptor_.Encrypt(&plaintext_[offset], len, &encrypted[offset])); offset += len; EXPECT_EQ(offset % kAesBlockSize, encryptor_.block_offset()); } @@ -301,30 +300,66 @@ INSTANTIATE_TEST_CASE_P(IvTestCases, AesCtrEncryptorIvTest, ::testing::ValuesIn(kIvTestCases)); -class AesCbcPkcs5EncryptorTestEncryptionDecryption : public testing::Test { +class AesCbcEncryptDecryptTest { public: - void TestEncryptionDecryption(const std::vector& key, - const std::vector& iv, - const std::string& plaintext, - const std::string& expected_ciphertext_hex) { - AesCbcPkcs5Encryptor encryptor; - EXPECT_TRUE(encryptor.InitializeWithIv(key, iv)); + AesCbcEncryptDecryptTest() + : encryptor_(new AesCbcEncryptor(kPkcs5Padding, !kChainAcrossCalls)), + decryptor_(new AesCbcDecryptor(kPkcs5Padding, !kChainAcrossCalls)), + key_(kAesKey, kAesKey + arraysize(kAesKey)), + iv_(kAesIv, kAesIv + arraysize(kAesIv)) {} - std::string ciphertext; - encryptor.Encrypt(plaintext, &ciphertext); - EXPECT_EQ(expected_ciphertext_hex, - base::HexEncode(ciphertext.data(), ciphertext.size())); + void TestEncryptDecrypt(const std::vector& plaintext, + const std::vector& expected_ciphertext) { + // Test Vector form. + TestEncryptDecryptSeparateBuffers(plaintext, expected_ciphertext); + TestEncryptDecryptInPlace(plaintext, expected_ciphertext); - AesCbcPkcs5Decryptor decryptor; - ASSERT_TRUE(decryptor.InitializeWithIv(key, iv)); + // Test string form. + 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; - EXPECT_TRUE(decryptor.Decrypt(ciphertext, &decrypted)); + protected: + template + 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); } + + template + 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 encryptor_; + scoped_ptr decryptor_; + std::vector key_; + std::vector 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. static const uint8_t kAesCbcKey[] = { 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, 0xe0, 0xc2, 0xa7, 0x2b, 0x4d, 0x80, 0xe6, 0x44}; - const std::vector key(kAesCbcKey, - kAesCbcKey + arraysize(kAesCbcKey)); - const std::vector iv(kAesCbcIv, kAesCbcIv + arraysize(kAesCbcIv)); - const std::string plaintext(reinterpret_cast(kAesCbcPlaintext), - sizeof(kAesCbcPlaintext)); - const std::string expected_ciphertext_hex = - base::HexEncode(kAesCbcCiphertext, sizeof(kAesCbcCiphertext)); + key_.assign(kAesCbcKey, kAesCbcKey + arraysize(kAesCbcKey)); + iv_.assign(kAesCbcIv, kAesCbcIv + arraysize(kAesCbcIv)); + const std::vector plaintext( + kAesCbcPlaintext, kAesCbcPlaintext + arraysize(kAesCbcPlaintext)); + const std::vector expected_ciphertext( + kAesCbcCiphertext, kAesCbcCiphertext + arraysize(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 kIv = "Sweet Sixteen IV"; const std::string kPlaintext = @@ -383,172 +417,201 @@ TEST_F(AesCbcPkcs5EncryptorTestEncryptionDecryption, EncryptAES128CBCRegression) "D4A67A0BA33C30F207344D81D1E944BBE65587C3D7D9939A" "C070C62B9C15A3EA312EA4AD1BC7929F4D3C16B03AD5ADA8"; - const std::vector key(kKey.begin(), kKey.end()); - const std::vector iv(kIv.begin(), kIv.end()); + key_.assign(kKey.begin(), kKey.end()); + iv_.assign(kIv.begin(), kIv.end()); - TestEncryptionDecryption(key, iv, kPlaintext, kExpectedCiphertextHex); + const std::vector plaintext(kPlaintext.begin(), kPlaintext.end()); + std::vector 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 kIv = "Sweet Sixteen IV"; const std::string kPlaintext = "Small text"; const std::string kExpectedCiphertextHex = "78DE5D7C2714FC5C61346C5416F6C89A"; - const std::vector key(kKey.begin(), kKey.end()); - const std::vector iv(kIv.begin(), kIv.end()); + key_.assign(kKey.begin(), kKey.end()); + iv_.assign(kIv.begin(), kIv.end()); - TestEncryptionDecryption(key, iv, kPlaintext, kExpectedCiphertextHex); + const std::vector plaintext(kPlaintext.begin(), kPlaintext.end()); + std::vector expected_ciphertext; + ASSERT_TRUE( + base::HexStringToBytes(kExpectedCiphertextHex, &expected_ciphertext)); + TestEncryptDecrypt(plaintext, expected_ciphertext); } -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()); - iv_.assign(kIv.begin(), kIv.end()); - } +TEST_F(AesCbcTest, NoPaddingNoChainAcrossCalls) { + const uint8_t kPlaintext[] = { + 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, + }; - protected: - std::vector key_; - std::vector iv_; -}; + std::vector plaintext(kPlaintext, + kPlaintext + arraysize(kPlaintext)); + std::vector ciphertext(kCiphertext, + kCiphertext + arraysize(kCiphertext)); -TEST_F(AesCbcPkcs5EncryptorTest, UnsupportedKeySize) { - AesCbcPkcs5Encryptor encryptor; - EXPECT_FALSE(encryptor.InitializeWithIv(std::vector(15, 0), iv_)); -} - -TEST_F(AesCbcPkcs5EncryptorTest, UnsupportedIvSize) { - AesCbcPkcs5Encryptor encryptor; - EXPECT_FALSE(encryptor.InitializeWithIv(key_, std::vector(14, 0))); -} - -TEST_F(AesCbcPkcs5EncryptorTest, EmptyEncrypt) { - AesCbcPkcs5Encryptor encryptor; + AesCbcEncryptor encryptor(kNoPadding, !kChainAcrossCalls); ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_)); - std::string ciphertext; - std::string expected_ciphertext_hex = "8518B8878D34E7185E300D0FCC426396"; - encryptor.Encrypt("", &ciphertext); - EXPECT_EQ(expected_ciphertext_hex, - base::HexEncode(ciphertext.data(), ciphertext.size())); -} + std::vector encrypted; + ASSERT_TRUE(encryptor.Encrypt(plaintext, &encrypted)); + EXPECT_EQ(ciphertext, encrypted); + // Iv should not have been updated. + EXPECT_EQ(iv_, encryptor.iv()); + ASSERT_TRUE(encryptor.Encrypt(plaintext, &encrypted)); + EXPECT_EQ(ciphertext, encrypted); -TEST_F(AesCbcPkcs5EncryptorTest, CipherTextNotMultipleOfBlockSize) { - AesCbcPkcs5Decryptor decryptor; + AesCbcDecryptor decryptor(kNoPadding, !kChainAcrossCalls); ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_)); - std::string plaintext; - EXPECT_FALSE(decryptor.Decrypt("1", &plaintext)); + std::vector 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); } -class AesCbcCtsEncryptorDecryptorTest : public testing::Test { - public: - void SetUp() override { - key_.assign(kAesKey, kAesKey + arraysize(kAesKey)); - iv_.assign(kAesIv, kAesIv + arraysize(kAesIv)); - } +TEST_F(AesCbcTest, NoPaddingChainAcrossCalls) { + const uint8_t kPlaintext[] = { + 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, + }; + const uint8_t kCiphertext2[] = { + 0xbd, 0xdd, 0xe4, 0x39, 0x52, 0x6f, 0x10, 0x0c, + 0x95, 0x45, 0xc2, 0x74, 0xd4, 0xf7, 0xfd, 0x3f, + }; - void TestEncryptDecryptSeparateBuffers( - const std::vector& plaintext, - const std::vector& expected_ciphertext) { - ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_)); - ASSERT_TRUE(decryptor_.InitializeWithIv(key_, iv_)); + std::vector plaintext(kPlaintext, + kPlaintext + arraysize(kPlaintext)); + std::vector ciphertext(kCiphertext, + kCiphertext + arraysize(kCiphertext)); + std::vector ciphertext2(kCiphertext2, + kCiphertext2 + arraysize(kCiphertext2)); - std::vector encrypted; - encryptor_.Encrypt(plaintext, &encrypted); - EXPECT_EQ(expected_ciphertext, encrypted); + AesCbcEncryptor encryptor(kNoPadding, kChainAcrossCalls); + ASSERT_TRUE(encryptor.InitializeWithIv(key_, iv_)); - std::vector decrypted; - decryptor_.Decrypt(encrypted, &decrypted); - EXPECT_EQ(plaintext, decrypted); - } + std::vector 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); - void TestEncryptDecryptInPlace( - const std::vector& plaintext, - const std::vector& expected_ciphertext) { - ASSERT_TRUE(encryptor_.InitializeWithIv(key_, iv_)); - ASSERT_TRUE(decryptor_.InitializeWithIv(key_, iv_)); + AesCbcDecryptor decryptor(kNoPadding, kChainAcrossCalls); + ASSERT_TRUE(decryptor.InitializeWithIv(key_, iv_)); - std::vector buffer(plaintext); - encryptor_.Encrypt(buffer, &buffer); - EXPECT_EQ(expected_ciphertext, buffer); - decryptor_.Decrypt(buffer, &buffer); - EXPECT_EQ(plaintext, buffer); - } + std::vector 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); +} - protected: - std::vector key_; - std::vector iv_; - AesCbcCtsEncryptor encryptor_; - AesCbcCtsDecryptor decryptor_; +TEST_F(AesCbcTest, UnsupportedKeySize) { + EXPECT_FALSE(encryptor_->InitializeWithIv(std::vector(15, 0), iv_)); + EXPECT_FALSE(decryptor_->InitializeWithIv(std::vector(15, 0), iv_)); +} + +TEST_F(AesCbcTest, UnsupportedIvSize) { + EXPECT_FALSE(encryptor_->InitializeWithIv(key_, std::vector(14, 0))); + EXPECT_FALSE(decryptor_->InitializeWithIv(key_, std::vector(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; }; -TEST_F(AesCbcCtsEncryptorDecryptorTest, TestWithResidualBytes) { +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" + "f917cdffc9ebb307141f23b0d3921e0ed7f86eb09381286f8e7a4f", + "b40a0b8704c74e22e8030cad6f272b34ace54cc7c9c64b2018bbcf23df018" + "39b14899441cf74a9fb2f2b229a609146f31be8e8a826eb6e857e"}, + // Cts padding with even blocks. + {kCtsPadding, + "3f593e7a204a5e70f2814dca05aa49d36f2daddc9a24e0515802c539efc3" + "1094b3ad6c26d6f5c0e387545ce6a4c2c14d", + "5f32cd0504b27b25ee04090d88d37d340c9c0a9fa50b05358b98fad4302ea" + "480148d8aa091f4e7d186a7223df153f6f7"}, + // Cts padding with one block and a half. + {kCtsPadding, "3f593e7a204a5e70f2814dca05aa49d36f2daddc9a4302ea", + "623fc113fe02ce85628deb58d652c6995f32cd0504b27b25"}, +}; + +class AesCbcCryptorVerificationTest + : public AesCbcEncryptDecryptTest, + public ::testing::TestWithParam {}; + +TEST_P(AesCbcCryptorVerificationTest, EncryptDecryptTest) { + encryptor_.reset( + new AesCbcEncryptor(GetParam().padding_scheme, !kChainAcrossCalls)); + decryptor_.reset( + new AesCbcDecryptor(GetParam().padding_scheme, !kChainAcrossCalls)); + std::vector plaintext; - ASSERT_TRUE(base::HexStringToBytes( - "e0818f2dc7caaa9edf09285a0c1fca98d39e9b08a47ab6911c4bbdf27d94" - "f917cdffc9ebb307141f23b0d3921e0ed7f86eb09381286f8e7a4f", - &plaintext)); + std::string plaintext_hex(GetParam().plaintext_hex); + if (!plaintext_hex.empty()) + ASSERT_TRUE(base::HexStringToBytes(plaintext_hex, &plaintext)); - std::vector ciphertext; - ASSERT_TRUE(base::HexStringToBytes( - "b40a0b8704c74e22e8030cad6f272b34ace54cc7c9c64b2018bbcf23df018" - "39b14899441cf74a9fb2f2b229a609146f31be8e8a826eb6e857e", - &ciphertext)); + std::vector expected_ciphertext; + std::string expected_ciphertext_hex(GetParam().expected_ciphertext_hex); + if (!expected_ciphertext_hex.empty()) { + ASSERT_TRUE(base::HexStringToBytes(GetParam().expected_ciphertext_hex, + &expected_ciphertext)); + } - TestEncryptDecryptSeparateBuffers(plaintext, ciphertext); - TestEncryptDecryptInPlace(plaintext, ciphertext); + TestEncryptDecrypt(plaintext, expected_ciphertext); } -TEST_F(AesCbcCtsEncryptorDecryptorTest, TestEvenBlocks) { - std::vector plaintext; - ASSERT_TRUE(base::HexStringToBytes( - "3f593e7a204a5e70f2814dca05aa49d36f2daddc9a24e0515802c539efc3" - "1094b3ad6c26d6f5c0e387545ce6a4c2c14d", - &plaintext)); - - std::vector ciphertext; - ASSERT_TRUE(base::HexStringToBytes( - "5f32cd0504b27b25ee04090d88d37d340c9c0a9fa50b05358b98fad4302ea" - "480148d8aa091f4e7d186a7223df153f6f7", - &ciphertext)); - - TestEncryptDecryptSeparateBuffers(plaintext, ciphertext); - TestEncryptDecryptInPlace(plaintext, ciphertext); -} - -TEST_F(AesCbcCtsEncryptorDecryptorTest, TestOneBlockAndAHalf) { - std::vector plaintext; - ASSERT_TRUE(base::HexStringToBytes( - "3f593e7a204a5e70f2814dca05aa49d36f2daddc9a4302ea", - &plaintext)); - - std::vector ciphertext; - ASSERT_TRUE(base::HexStringToBytes( - "623fc113fe02ce85628deb58d652c6995f32cd0504b27b25", - &ciphertext)); - - TestEncryptDecryptSeparateBuffers(plaintext, ciphertext); - TestEncryptDecryptInPlace(plaintext, ciphertext); -} - -TEST_F(AesCbcCtsEncryptorDecryptorTest, TestZeroEncryptedBlocks) { - std::vector plaintext; - ASSERT_TRUE(base::HexStringToBytes("3f593e7a204a5e70f2", &plaintext)); - - TestEncryptDecryptSeparateBuffers(plaintext, plaintext); - TestEncryptDecryptInPlace(plaintext, plaintext); -} - -TEST_F(AesCbcCtsEncryptorDecryptorTest, TestZeroBytes) { - std::vector plaintext; - - TestEncryptDecryptSeparateBuffers(plaintext, plaintext); - TestEncryptDecryptInPlace(plaintext, plaintext); -} +INSTANTIATE_TEST_CASE_P(CbcTestCases, + AesCbcCryptorVerificationTest, + ::testing::ValuesIn(kCbcTestCases)); } // namespace media } // namespace edash_packager diff --git a/packager/media/base/decryptor_source.cc b/packager/media/base/decryptor_source.cc index 64048e3f8d..099e715809 100644 --- a/packager/media/base/decryptor_source.cc +++ b/packager/media/base/decryptor_source.cc @@ -44,7 +44,7 @@ bool DecryptorSource::DecryptSampleBuffer(const DecryptConfig* decrypt_config, aes_decryptor.reset(new AesCtrDecryptor); break; case kEncryptionModeAesCbc: - aes_decryptor.reset(new AesCbcPkcs5Decryptor); + aes_decryptor.reset(new AesCbcDecryptor(kNoPadding, kChainAcrossCalls)); break; default: LOG(ERROR) << "Unsupported Decryption Mode: " diff --git a/packager/media/base/request_signer.cc b/packager/media/base/request_signer.cc index 38cae863b1..0ca0153705 100644 --- a/packager/media/base/request_signer.cc +++ b/packager/media/base/request_signer.cc @@ -19,7 +19,7 @@ RequestSigner::RequestSigner(const std::string& signer_name) RequestSigner::~RequestSigner() {} AesRequestSigner::AesRequestSigner(const std::string& signer_name, - scoped_ptr encryptor) + scoped_ptr encryptor) : RequestSigner(signer_name), aes_cbc_encryptor_(encryptor.Pass()) { DCHECK(aes_cbc_encryptor_); } @@ -39,7 +39,8 @@ AesRequestSigner* AesRequestSigner::CreateSigner(const std::string& signer_name, return NULL; } - scoped_ptr encryptor(new AesCbcPkcs5Encryptor()); + scoped_ptr encryptor( + new AesCbcEncryptor(kPkcs5Padding, !kChainAcrossCalls)); if (!encryptor->InitializeWithIv(aes_key, iv)) return NULL; return new AesRequestSigner(signer_name, encryptor.Pass()); diff --git a/packager/media/base/request_signer.h b/packager/media/base/request_signer.h index 886070ecb7..0075cba1ed 100644 --- a/packager/media/base/request_signer.h +++ b/packager/media/base/request_signer.h @@ -14,7 +14,7 @@ namespace edash_packager { namespace media { -class AesCbcPkcs5Encryptor; +class AesCbcEncryptor; class RsaPrivateKey; /// Abstract class used for signature generation. @@ -56,9 +56,9 @@ class AesRequestSigner : public RequestSigner { private: AesRequestSigner(const std::string& signer_name, - scoped_ptr encryptor); + scoped_ptr encryptor); - scoped_ptr aes_cbc_encryptor_; + scoped_ptr aes_cbc_encryptor_; DISALLOW_COPY_AND_ASSIGN(AesRequestSigner); }; diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index d6c17f507e..b878ca4023 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -23,7 +23,8 @@ namespace mp4 { namespace { // 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; // Adds one or more subsamples to |*subsamples|. This may add more than one @@ -175,16 +176,19 @@ void EncryptingFragmenter::FinalizeFragmentForEncryption() { Status EncryptingFragmenter::CreateEncryptor() { DCHECK(encryption_key_); scoped_ptr encryptor; + size_t default_iv_size = 0; if (encryption_mode_ == kEncryptionModeAesCtr) { encryptor.reset(new AesCtrEncryptor); + default_iv_size = kDefaultIvSizeForCtr; } else if (encryption_mode_ == kEncryptionModeAesCbc) { - encryptor.reset(new AesCbcPkcs5Encryptor); + encryptor.reset(new AesCbcEncryptor(kNoPadding, kChainAcrossCalls)); + default_iv_size = kDefaultIvSizeForCbc; } else { return Status(error::MUXER_FAILURE, "Unsupported encryption mode."); } const bool initialized = encryption_key_->iv.empty() ? encryptor->InitializeWithRandomIv( - encryption_key_->key, kDefaultIvSize) + encryption_key_->key, default_iv_size) : encryptor->InitializeWithIv( encryption_key_->key, encryption_key_->iv); if (!initialized) @@ -195,7 +199,7 @@ Status EncryptingFragmenter::CreateEncryptor() { void EncryptingFragmenter::EncryptBytes(uint8_t* data, uint32_t size) { DCHECK(encryptor_); - CHECK(encryptor_->EncryptData(data, size, data)); + CHECK(encryptor_->Encrypt(data, size, data)); } Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { @@ -223,8 +227,11 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { // encrypted bytes of each frame within the superframe must be block // aligned so that the counter state can be computed for each frame // within the superframe. - if (is_superframe) { - uint16_t misalign_bytes = subsample.cipher_bytes % kCencBlockSize; + // For AES-CBC mode 'cbc1' scheme, clear data is sized appropriately so + // 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.cipher_bytes -= misalign_bytes; } @@ -258,10 +265,18 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { if (video_slice_header_size < 0) return Status(error::MUXER_FAILURE, "Failed to read slice header."); - const uint64_t current_clear_bytes = nalu.header_size() + - video_slice_header_size; - const uint64_t cipher_bytes = - nalu.payload_size() - video_slice_header_size; + uint64_t current_clear_bytes = + nalu.header_size() + video_slice_header_size; + uint64_t cipher_bytes = 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; EncryptBytes(const_cast(nalu_data), cipher_bytes); @@ -285,7 +300,12 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { traf()->auxiliary_size.sample_info_sizes.push_back( sample_encryption_entry.ComputeSize()); } 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( diff --git a/packager/media/formats/webm/encryptor.cc b/packager/media/formats/webm/encryptor.cc index 0679864a00..554c1f9ff0 100644 --- a/packager/media/formats/webm/encryptor.cc +++ b/packager/media/formats/webm/encryptor.cc @@ -78,7 +78,7 @@ Status Encryptor::EncryptFrame(scoped_refptr sample, uint8_t* sample_data = sample->writable_data(); // 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."); } diff --git a/packager/media/formats/wvm/wvm_media_parser.cc b/packager/media/formats/wvm/wvm_media_parser.cc index 65333fc6ea..1e81093c1e 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); - AesCbcCtsDecryptor asset_decryptor; + AesCbcDecryptor asset_decryptor(kCtsPadding, !kChainAcrossCalls); if (!asset_decryptor.InitializeWithIv(asset_key, iv)) { LOG(ERROR) << "Failed to initialize asset_decryptor."; return false; @@ -1124,13 +1124,14 @@ bool WvmMediaParser::ProcessEcm() { kEcmFlagsSizeBytes + kEcmContentKeySizeBytes + kEcmPaddingSizeBytes; // flags + contentKey + padding. std::vector content_key_buffer(content_key_buffer_size); - asset_decryptor.Decrypt(ecm_data, content_key_buffer_size, - content_key_buffer.data()); + CHECK(asset_decryptor.Decrypt(ecm_data, content_key_buffer_size, + content_key_buffer.data())); std::vector decrypted_content_key_vec( content_key_buffer.begin() + 4, content_key_buffer.begin() + 20); - scoped_ptr content_decryptor(new AesCbcCtsDecryptor); + scoped_ptr content_decryptor( + new AesCbcDecryptor(kCtsPadding, !kChainAcrossCalls)); if (!content_decryptor->InitializeWithIv(decrypted_content_key_vec, iv)) { LOG(ERROR) << "Failed to initialize content decryptor."; return false; diff --git a/packager/media/formats/wvm/wvm_media_parser.h b/packager/media/formats/wvm/wvm_media_parser.h index ff7f74ca90..a71d4055a9 100644 --- a/packager/media/formats/wvm/wvm_media_parser.h +++ b/packager/media/formats/wvm/wvm_media_parser.h @@ -20,7 +20,7 @@ namespace edash_packager { namespace media { -class AesCbcCtsDecryptor; +class AesCbcDecryptor; class KeySource; struct EncryptionKey; @@ -252,7 +252,7 @@ class WvmMediaParser : public MediaParser { std::deque media_sample_queue_; std::vector sample_data_; KeySource* decryption_key_source_; - scoped_ptr content_decryptor_; + scoped_ptr content_decryptor_; DISALLOW_COPY_AND_ASSIGN(WvmMediaParser); };