Storing block ID is job of VersionCountingBlockStore, not EncryptedBlockStore.

This commit is contained in:
Sebastian Messmer 2017-09-16 00:09:15 +01:00
parent 446e6e2654
commit 00d098952b
6 changed files with 140 additions and 76 deletions

View File

@ -34,17 +34,21 @@ public:
private:
// This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
static constexpr unsigned int HEADER_LENGTH = Key::BINARY_LENGTH;
#ifndef CRYFS_NO_COMPATIBILITY
static constexpr uint16_t FORMAT_VERSION_HEADER_OLD = 0;
#endif
static constexpr uint16_t FORMAT_VERSION_HEADER = 1;
cpputils::Data _encrypt(const Key &key, const cpputils::Data &data) const;
cpputils::Data _encrypt(const cpputils::Data &data) const;
boost::optional<cpputils::Data> _tryDecrypt(const Key &key, const cpputils::Data &data) const;
static cpputils::Data _prependKeyHeaderToData(const Key &key, const cpputils::Data &data);
static bool _keyHeaderIsCorrect(const Key &key, const cpputils::Data &data);
static cpputils::Data _prependFormatHeaderToData(const cpputils::Data &data);
static cpputils::Data _removeKeyHeader(const cpputils::Data &data);
static cpputils::Data _checkAndRemoveFormatHeader(const cpputils::Data &data);
#ifndef CRYFS_NO_COMPATIBILITY
static bool _keyHeaderIsCorrect(const Key &key, const cpputils::Data &data);
static cpputils::Data _migrateBlock(const cpputils::Data &data);
#endif
static void _checkFormatHeader(const cpputils::Data &data);
static uint16_t _readFormatHeader(const cpputils::Data &data);
cpputils::unique_ref<BlockStore2> _baseBlockStore;
typename Cipher::EncryptionKey _encKey;
@ -52,11 +56,13 @@ private:
DISALLOW_COPY_AND_ASSIGN(EncryptedBlockStore2);
};
#ifndef CRYFS_NO_COMPATIBILITY
template<class Cipher>
constexpr uint16_t EncryptedBlockStore2<Cipher>::FORMAT_VERSION_HEADER;
constexpr uint16_t EncryptedBlockStore2<Cipher>::FORMAT_VERSION_HEADER_OLD;
#endif
template<class Cipher>
constexpr unsigned int EncryptedBlockStore2<Cipher>::HEADER_LENGTH;
constexpr uint16_t EncryptedBlockStore2<Cipher>::FORMAT_VERSION_HEADER;
template<class Cipher>
inline EncryptedBlockStore2<Cipher>::EncryptedBlockStore2(cpputils::unique_ref<BlockStore2> baseBlockStore, const typename Cipher::EncryptionKey &encKey)
@ -65,7 +71,7 @@ inline EncryptedBlockStore2<Cipher>::EncryptedBlockStore2(cpputils::unique_ref<B
template<class Cipher>
inline bool EncryptedBlockStore2<Cipher>::tryCreate(const Key &key, const cpputils::Data &data) {
cpputils::Data encrypted = _encrypt(key, data);
cpputils::Data encrypted = _encrypt(data);
return _baseBlockStore->tryCreate(key, encrypted);
}
@ -86,7 +92,7 @@ inline boost::optional<cpputils::Data> EncryptedBlockStore2<Cipher>::load(const
template<class Cipher>
inline void EncryptedBlockStore2<Cipher>::store(const Key &key, const cpputils::Data &data) {
cpputils::Data encrypted = _encrypt(key, data);
cpputils::Data encrypted = _encrypt(data);
return _baseBlockStore->store(key, encrypted);
}
@ -103,10 +109,10 @@ inline uint64_t EncryptedBlockStore2<Cipher>::estimateNumFreeBytes() const {
template<class Cipher>
inline uint64_t EncryptedBlockStore2<Cipher>::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const {
uint64_t baseBlockSize = _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize);
if (baseBlockSize <= Cipher::ciphertextSize(HEADER_LENGTH) + sizeof(FORMAT_VERSION_HEADER)) {
if (baseBlockSize <= Cipher::ciphertextSize(0) + sizeof(FORMAT_VERSION_HEADER)) {
return 0;
}
return Cipher::plaintextSize(baseBlockSize - sizeof(FORMAT_VERSION_HEADER)) - HEADER_LENGTH;
return Cipher::plaintextSize(baseBlockSize - sizeof(FORMAT_VERSION_HEADER));
}
template<class Cipher>
@ -115,39 +121,45 @@ inline void EncryptedBlockStore2<Cipher>::forEachBlock(std::function<void (const
}
template<class Cipher>
inline cpputils::Data EncryptedBlockStore2<Cipher>::_encrypt(const Key &key, const cpputils::Data &data) const {
cpputils::Data plaintextWithHeader = _prependKeyHeaderToData(key, data);
cpputils::Data encrypted = Cipher::encrypt((CryptoPP::byte*)plaintextWithHeader.data(), plaintextWithHeader.size(), _encKey);
inline cpputils::Data EncryptedBlockStore2<Cipher>::_encrypt(const cpputils::Data &data) const {
cpputils::Data encrypted = Cipher::encrypt((CryptoPP::byte*)data.data(), data.size(), _encKey);
return _prependFormatHeaderToData(encrypted);
}
template<class Cipher>
inline boost::optional<cpputils::Data> EncryptedBlockStore2<Cipher>::_tryDecrypt(const Key &key, const cpputils::Data &data) const {
auto ciphertext = _checkAndRemoveFormatHeader(data);
boost::optional<cpputils::Data> decrypted = Cipher::decrypt((CryptoPP::byte*)ciphertext.data(), ciphertext.size(), _encKey);
if (boost::none == decrypted) {
// TODO Warning
_checkFormatHeader(data);
boost::optional<cpputils::Data> decrypted = Cipher::decrypt((CryptoPP::byte*)data.dataOffset(sizeof(FORMAT_VERSION_HEADER)), data.size() - sizeof(FORMAT_VERSION_HEADER), _encKey);
if (decrypted == boost::none) {
// TODO Log warning
return boost::none;
}
if (!_keyHeaderIsCorrect(key, *decrypted)) {
// TODO Warning
return boost::none;
#ifndef CRYFS_NO_COMPATIBILITY
if (FORMAT_VERSION_HEADER_OLD == _readFormatHeader(data)) {
if (!_keyHeaderIsCorrect(key, *decrypted)) {
return boost::none;
}
*decrypted = _migrateBlock(*decrypted);
// no need to write migrated back to block store because
// this migration happens in line with a migration in VersionCountingBlockStore2
// which then writes it back
}
return _removeKeyHeader(*decrypted);
#endif
return decrypted;
}
#ifndef CRYFS_NO_COMPATIBILITY
template<class Cipher>
inline cpputils::Data EncryptedBlockStore2<Cipher>::_prependKeyHeaderToData(const Key &key, const cpputils::Data &data) {
cpputils::Data result(data.size() + Key::BINARY_LENGTH);
std::memcpy(result.data(), key.data(), Key::BINARY_LENGTH);
std::memcpy((uint8_t*)result.data() + Key::BINARY_LENGTH, data.data(), data.size());
return result;
inline cpputils::Data EncryptedBlockStore2<Cipher>::_migrateBlock(const cpputils::Data &data) {
return data.copyAndRemovePrefix(Key::BINARY_LENGTH);
}
template<class Cipher>
inline bool EncryptedBlockStore2<Cipher>::_keyHeaderIsCorrect(const Key &key, const cpputils::Data &data) {
return 0 == std::memcmp(key.data(), data.data(), Key::BINARY_LENGTH);
}
#endif
template<class Cipher>
inline cpputils::Data EncryptedBlockStore2<Cipher>::_prependFormatHeaderToData(const cpputils::Data &data) {
@ -158,16 +170,21 @@ inline cpputils::Data EncryptedBlockStore2<Cipher>::_prependFormatHeaderToData(c
}
template<class Cipher>
inline cpputils::Data EncryptedBlockStore2<Cipher>::_removeKeyHeader(const cpputils::Data &data) {
return data.copyAndRemovePrefix(Key::BINARY_LENGTH);
inline void EncryptedBlockStore2<Cipher>::_checkFormatHeader(const cpputils::Data &data) {
const uint16_t formatVersionHeader = _readFormatHeader(data);
#ifndef CRYFS_NO_COMPATIBILITY
const bool formatVersionHeaderValid = formatVersionHeader == FORMAT_VERSION_HEADER || formatVersionHeader == FORMAT_VERSION_HEADER_OLD;
#else
const bool formatVersionHeaderValid = formatVersionHeader == FORMAT_VERSION_HEADER;
#endif
if (!formatVersionHeaderValid) {
throw std::runtime_error("The encrypted block has the wrong format. Was it created with a newer version of CryFS?");
}
}
template<class Cipher>
inline cpputils::Data EncryptedBlockStore2<Cipher>::_checkAndRemoveFormatHeader(const cpputils::Data &data) {
if (*reinterpret_cast<decltype(FORMAT_VERSION_HEADER)*>(data.data()) != FORMAT_VERSION_HEADER) {
throw std::runtime_error("The encrypted block has the wrong format. Was it created with a newer version of CryFS?");
}
return data.copyAndRemovePrefix(sizeof(FORMAT_VERSION_HEADER));
uint16_t EncryptedBlockStore2<Cipher>::_readFormatHeader(const cpputils::Data &data) {
return *reinterpret_cast<decltype(FORMAT_VERSION_HEADER)*>(data.data());
}
template<class Cipher>

View File

@ -12,16 +12,21 @@ using namespace cpputils::logging;
namespace blockstore {
namespace versioncounting {
#ifndef CRYFS_NO_COMPATIBILITY
constexpr uint16_t VersionCountingBlockStore2::FORMAT_VERSION_HEADER_OLD;
#endif
constexpr uint16_t VersionCountingBlockStore2::FORMAT_VERSION_HEADER;
constexpr uint64_t VersionCountingBlockStore2::VERSION_ZERO;
constexpr unsigned int VersionCountingBlockStore2::ID_HEADER_OFFSET;
constexpr unsigned int VersionCountingBlockStore2::CLIENTID_HEADER_OFFSET;
constexpr unsigned int VersionCountingBlockStore2::VERSION_HEADER_OFFSET;
constexpr unsigned int VersionCountingBlockStore2::HEADER_LENGTH;
Data VersionCountingBlockStore2::_prependHeaderToData(uint32_t myClientId, uint64_t version, const Data &data) {
static_assert(HEADER_LENGTH == sizeof(FORMAT_VERSION_HEADER) + sizeof(myClientId) + sizeof(version), "Wrong header length");
Data VersionCountingBlockStore2::_prependHeaderToData(const Key& key, uint32_t myClientId, uint64_t version, const Data &data) {
static_assert(HEADER_LENGTH == sizeof(FORMAT_VERSION_HEADER) + Key::BINARY_LENGTH + sizeof(myClientId) + sizeof(version), "Wrong header length");
Data result(data.size() + HEADER_LENGTH);
std::memcpy(result.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
std::memcpy(result.dataOffset(ID_HEADER_OFFSET), key.data(), Key::BINARY_LENGTH);
std::memcpy(result.dataOffset(CLIENTID_HEADER_OFFSET), &myClientId, sizeof(myClientId));
std::memcpy(result.dataOffset(VERSION_HEADER_OFFSET), &version, sizeof(version));
std::memcpy((uint8_t*)result.dataOffset(HEADER_LENGTH), data.data(), data.size());
@ -30,11 +35,12 @@ Data VersionCountingBlockStore2::_prependHeaderToData(uint32_t myClientId, uint6
void VersionCountingBlockStore2::_checkHeader(const Key &key, const Data &data) const {
_checkFormatHeader(data);
_checkIdHeader(key, data);
_checkVersionHeader(key, data);
}
void VersionCountingBlockStore2::_checkFormatHeader(const Data &data) const {
if (*reinterpret_cast<decltype(FORMAT_VERSION_HEADER)*>(data.data()) != FORMAT_VERSION_HEADER) {
if (FORMAT_VERSION_HEADER != _readFormatHeader(data)) {
throw std::runtime_error("The versioned block has the wrong format. Was it created with a newer version of CryFS?");
}
}
@ -48,19 +54,34 @@ void VersionCountingBlockStore2::_checkVersionHeader(const Key &key, const Data
}
}
void VersionCountingBlockStore2::_checkIdHeader(const Key &expectedKey, const Data &data) const {
Key actualKey = _readBlockId(data);
if (expectedKey != actualKey) {
integrityViolationDetected("The block key is wrong. Did an attacker try to rename some blocks?");
}
}
uint16_t VersionCountingBlockStore2::_readFormatHeader(const Data &data) {
return *reinterpret_cast<decltype(FORMAT_VERSION_HEADER)*>(data.data());
}
uint32_t VersionCountingBlockStore2::_readClientId(const Data &data) {
uint32_t clientId;
std::memcpy(&clientId, data.dataOffset(CLIENTID_HEADER_OFFSET), sizeof(clientId));
return clientId;
}
Key VersionCountingBlockStore2::_readBlockId(const Data &data) {
return Key::FromBinary(data.dataOffset(ID_HEADER_OFFSET));
}
uint64_t VersionCountingBlockStore2::_readVersion(const Data &data) {
uint64_t version;
std::memcpy(&version, data.dataOffset(VERSION_HEADER_OFFSET), sizeof(version));
return version;
}
Data VersionCountingBlockStore2::_removeHeader(const Data &data) const {
Data VersionCountingBlockStore2::_removeHeader(const Data &data) {
return data.copyAndRemovePrefix(HEADER_LENGTH);
}
@ -86,7 +107,7 @@ VersionCountingBlockStore2::VersionCountingBlockStore2(unique_ref<BlockStore2> b
bool VersionCountingBlockStore2::tryCreate(const Key &key, const Data &data) {
_checkNoPastIntegrityViolations();
uint64_t version = _knownBlockVersions.incrementVersion(key);
Data dataWithHeader = _prependHeaderToData(_knownBlockVersions.myClientId(), version, data);
Data dataWithHeader = _prependHeaderToData(key, _knownBlockVersions.myClientId(), version, data);
return _baseBlockStore->tryCreate(key, dataWithHeader);
}
@ -105,14 +126,34 @@ optional<Data> VersionCountingBlockStore2::load(const Key &key) const {
}
return optional<Data>(none);
}
#ifndef CRYFS_NO_COMPATIBILITY
if (FORMAT_VERSION_HEADER_OLD == _readFormatHeader(*loaded)) {
Data migrated = _migrateBlock(key, *loaded);
_checkHeader(key, migrated);
Data content = _removeHeader(migrated);
const_cast<VersionCountingBlockStore2*>(this)->store(key, content);
return optional<Data>(_removeHeader(migrated));
}
#endif
_checkHeader(key, *loaded);
return optional<Data>(_removeHeader(*loaded));
}
#ifndef CRYFS_NO_COMPATIBILITY
Data VersionCountingBlockStore2::_migrateBlock(const Key &key, const Data &data) {
Data migrated(data.size() + Key::BINARY_LENGTH);
std::memcpy(migrated.dataOffset(0), &FORMAT_VERSION_HEADER, sizeof(FORMAT_VERSION_HEADER));
std::memcpy(migrated.dataOffset(ID_HEADER_OFFSET), key.data(), Key::BINARY_LENGTH);
std::memcpy(migrated.dataOffset(ID_HEADER_OFFSET + Key::BINARY_LENGTH), data.dataOffset(sizeof(FORMAT_VERSION_HEADER)), data.size() - sizeof(FORMAT_VERSION_HEADER));
ASSERT(migrated.size() == sizeof(FORMAT_VERSION_HEADER) + Key::BINARY_LENGTH + (data.size() - sizeof(FORMAT_VERSION_HEADER)), "Wrong offset computation");
return migrated;
}
#endif
void VersionCountingBlockStore2::store(const Key &key, const Data &data) {
_checkNoPastIntegrityViolations();
uint64_t version = _knownBlockVersions.incrementVersion(key);
Data dataWithHeader = _prependHeaderToData(_knownBlockVersions.myClientId(), version, data);
Data dataWithHeader = _prependHeaderToData(key, _knownBlockVersions.myClientId(), version, data);
return _baseBlockStore->store(key, dataWithHeader);
}
@ -170,7 +211,7 @@ void VersionCountingBlockStore2::migrateBlockFromBlockstoreWithoutVersionNumbers
return;
}
cpputils::Data data = std::move(*data_);
cpputils::Data dataWithHeader = _prependHeaderToData(knownBlockVersions->myClientId(), version, std::move(data));
cpputils::Data dataWithHeader = _prependHeaderToData(key, knownBlockVersions->myClientId(), version, std::move(data));
baseBlockStore->store(key, std::move(dataWithHeader));
}
#endif

View File

@ -26,14 +26,18 @@ public:
void forEachBlock(std::function<void (const Key &)> callback) const override;
private:
// This header is prepended to blocks to allow future versions to have compatibility.
static constexpr uint16_t FORMAT_VERSION_HEADER = 0;
// This format version is prepended to blocks to allow future versions to have compatibility.
#ifndef CRYFS_NO_COMPATIBILITY
static constexpr uint16_t FORMAT_VERSION_HEADER_OLD = 0;
#endif
static constexpr uint16_t FORMAT_VERSION_HEADER = 1;
public:
static constexpr uint64_t VERSION_ZERO = 0;
static constexpr unsigned int CLIENTID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER);
static constexpr unsigned int VERSION_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t);
static constexpr unsigned int HEADER_LENGTH = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint32_t) + sizeof(VERSION_ZERO);
static constexpr unsigned int ID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER);
static constexpr unsigned int CLIENTID_HEADER_OFFSET = ID_HEADER_OFFSET + Key::BINARY_LENGTH;
static constexpr unsigned int VERSION_HEADER_OFFSET = CLIENTID_HEADER_OFFSET + sizeof(uint32_t);
static constexpr unsigned int HEADER_LENGTH = VERSION_HEADER_OFFSET + sizeof(VERSION_ZERO);
#ifndef CRYFS_NO_COMPATIBILITY
static void migrateFromBlockstoreWithoutVersionNumbers(BlockStore2 *baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId);
@ -42,13 +46,19 @@ public:
private:
static cpputils::Data _prependHeaderToData(uint32_t myClientId, uint64_t version, const cpputils::Data &data);
static cpputils::Data _prependHeaderToData(const Key& key, uint32_t myClientId, uint64_t version, const cpputils::Data &data);
void _checkHeader(const Key &key, const cpputils::Data &data) const;
void _checkFormatHeader(const cpputils::Data &data) const;
void _checkIdHeader(const Key &expectedKey, const cpputils::Data &data) const;
void _checkVersionHeader(const Key &key, const cpputils::Data &data) const;
static uint16_t _readFormatHeader(const cpputils::Data &data);
static uint32_t _readClientId(const cpputils::Data &data);
static Key _readBlockId(const cpputils::Data &data);
static uint64_t _readVersion(const cpputils::Data &data);
cpputils::Data _removeHeader(const cpputils::Data &data) const;
#ifndef CRYFS_NO_COMPATIBILITY
static cpputils::Data _migrateBlock(const Key &key, const cpputils::Data &data);
#endif
static cpputils::Data _removeHeader(const cpputils::Data &data);
void _checkNoPastIntegrityViolations() const;
void integrityViolationDetected(const std::string &reason) const;

View File

@ -105,20 +105,6 @@ TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteSeparately) {
EXPECT_EQ(boost::none, loaded);
}
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentBlockIdFails_WriteOnCreate) {
auto key = CreateBlockDirectlyWithFixtureAndReturnKey();
auto key2 = CopyBaseBlock(key);
auto loaded = blockStore->load(key2);
EXPECT_EQ(boost::none, loaded);
}
TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentBlockIdFails_WriteSeparately) {
auto key = CreateBlockWriteFixtureToItAndReturnKey();
auto key2 = CopyBaseBlock(key);
auto loaded = blockStore->load(key2);
EXPECT_EQ(boost::none, loaded);
}
TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_zerophysical) {
EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(0));
}

View File

@ -235,6 +235,16 @@ TEST_F(VersionCountingBlockStoreTest, DeletionPrevention_InForEachBlock_DoesntAl
);
}
TEST_F(VersionCountingBlockStoreTest, LoadingWithDifferentBlockIdFails) {
auto key = CreateBlockReturnKey();
blockstore::Key key2 = blockstore::Key::FromString("1491BB4932A389EE14BC7090AC772972");
baseBlockStore->store(key2, baseBlockStore->load(key).value());
EXPECT_THROW(
blockStore->load(key2),
IntegrityViolationError
);
}
// TODO Test more integrity cases:
// - RollbackPrevention_DoesntAllowReintroducingDeletedBlocks with different client id (i.e. trying to re-introduce the newest block of a different client)
// - RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber with different client id

View File

@ -25,7 +25,7 @@ namespace cpputils {
uint8_t value;
};
// This is a fake cipher that uses an indeterministic caesar chiffre and a 4-byte parity for a simple authentication mechanism
// This is a fake cipher that uses an indeterministic caesar chiffre and a 4-byte checksum for a simple authentication mechanism
class FakeAuthenticatedCipher {
public:
BOOST_CONCEPT_ASSERT((CipherConcept<FakeAuthenticatedCipher>));
@ -58,22 +58,22 @@ namespace cpputils {
//Use caesar chiffre on plaintext
_caesar((CryptoPP::byte *) result.data() + 1, plaintext, plaintextSize, encKey.value + iv);
//Add parity information
int32_t parity = _parity((CryptoPP::byte *) result.data(), plaintextSize + 1);
std::memcpy((CryptoPP::byte *) result.data() + plaintextSize + 1, &parity, 4);
//Add checksum information
int32_t checksum = _checksum((CryptoPP::byte *) result.data(), encKey, plaintextSize + 1);
std::memcpy((CryptoPP::byte *) result.data() + plaintextSize + 1, &checksum, 4);
return result;
}
static boost::optional <Data> decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize,
const EncryptionKey &encKey) {
//We need at least 5 bytes (iv + parity)
//We need at least 5 bytes (iv + checksum)
if (ciphertextSize < 5) {
return boost::none;
}
//Check parity
int32_t expectedParity = _parity(ciphertext, plaintextSize(ciphertextSize) + 1);
//Check checksum
int32_t expectedParity = _checksum(ciphertext, encKey, plaintextSize(ciphertextSize) + 1);
int32_t actualParity = *(int32_t * )(ciphertext + plaintextSize(ciphertextSize) + 1);
if (expectedParity != actualParity) {
return boost::none;
@ -89,18 +89,18 @@ namespace cpputils {
static constexpr const char *NAME = "FakeAuthenticatedCipher";
private:
static int32_t _parity(const CryptoPP::byte *data, unsigned int size) {
int32_t parity = 34343435; // some init value
static int32_t _checksum(const CryptoPP::byte *data, FakeKey encKey, unsigned int size) {
int32_t checksum = 34343435 * encKey.value; // some init value
const int32_t *intData = reinterpret_cast<const int32_t *>(data);
unsigned int intSize = size / sizeof(int32_t);
for (unsigned int i = 0; i < intSize; ++i) {
parity = ((int64_t)parity) + intData[i];
checksum = ((int64_t)checksum) + intData[i];
}
unsigned int remainingBytes = size - 4 * intSize;
for (unsigned int i = 0; i < remainingBytes; ++i) {
parity = ((int64_t)parity) + (data[4 * intSize + i] << (24 - 8 * i));
checksum = ((int64_t)checksum) + (data[4 * intSize + i] << (24 - 8 * i));
}
return parity;
return checksum;
}
static void _caesar(CryptoPP::byte *dst, const CryptoPP::byte *src, unsigned int size, uint8_t key) {