Description
While developing support for an external app that uses the HC-256 cipher, I noticed inconsistencies with the CryptoPP::HC256
algorithm's output. Further digging showed that when either the key or IV contain non-repeating bytes (e.g. not just all 00
or 36
...), the output does not match that of other libraries, or the original reference library.
I have tested against BouncyCastle and the original hc256.c
ECRYPT source (which I had to use instead of CryptoPP for this project).
Compiled CryptoPP 8.9.0 using Visual Studio (x64) on Windows 10.
I have found the problem seems to be with the endian flip happening with the key and IV at these two locations. Such an endian flip is not present in BouncyCastle or hc256.c
.
Lines 107 to 111 in 60f81a7
Lines 135 to 139 in 60f81a7
If I replace the loop bodies with these respective changes, the unit output matches that of BouncyCastle and hc256.c
.
m_key[i >> 2] |= ((word32)userKey[i] << (8 * (i & 0x3)));
...
m_iv[i >> 2] |= ((word32)iv[i] << (8 * (i & 0x3)));
Here is a test program that uses a BouncyCastle test vector to show the mismatch. The code throws an assertion with CryptoPP 8.9.0, and passes with the above changes.
#include <cryptopp/hc256.h>
int main()
{
auto key = std::vector<uint8_t>{
0x00, 0x53, 0xA6, 0xF9, 0x4C, 0x9F, 0xF2, 0x45, 0x98, 0xEB, 0x3E, 0x91, 0xE4, 0x37, 0x8A, 0xDD,
0x30, 0x83, 0xD6, 0x29, 0x7C, 0xCF, 0x22, 0x75, 0xC8, 0x1B, 0x6E, 0xC1, 0x14, 0x67, 0xBA, 0x0D
};
auto iv = std::vector<uint8_t>{
0x0D, 0x74, 0xDB, 0x42, 0xA9, 0x10, 0x77, 0xDE, 0x45, 0xAC, 0x13, 0x7A, 0xE1, 0x48, 0xAF, 0x16,
0x7D, 0xE4, 0x4B, 0xB2, 0x19, 0x80, 0xE7, 0x4E, 0xB5, 0x1C, 0x83, 0xEA, 0x51, 0xB8, 0x1F, 0x86
};
auto buffer = std::vector<uint8_t>(0x40, 0x00);
// BouncyCastle Set 6, vector# 0
// https://github.com/bcgit/bc-java/blob/0520ebcd3291d6d3176ea25c571f1c7e0a963847/core/src/test/java/org/bouncycastle/crypto/test/HCFamilyTest.java#L104-L112
// https://github.com/bcgit/bc-csharp/blob/f5432325fbeadade37ec2542a44b3702395c7650/crypto/test/data/hc256/hc256/ecrypt_HC-256_256K_256IV.txt#L3151-L3159
auto expected = std::vector<uint8_t>{
0x23, 0xD9, 0xE7, 0x0A, 0x45, 0xEB, 0x01, 0x27, 0x88, 0x4D, 0x66, 0xD9, 0xF6, 0xF2, 0x3C, 0x01,
0xD1, 0xF8, 0x8A, 0xFD, 0x62, 0x92, 0x70, 0x12, 0x72, 0x47, 0x25, 0x6C, 0x1F, 0xFF, 0x91, 0xE9,
0x1A, 0x79, 0x7B, 0xD9, 0x8A, 0xDD, 0x23, 0xAE, 0x15, 0xBE, 0xE6, 0xEE, 0xA3, 0xCE, 0xFD, 0xBF,
0xA3, 0xED, 0x6D, 0x22, 0xD9, 0xC4, 0xF4, 0x59, 0xDB, 0x10, 0xC4, 0x0C, 0xDF, 0x4F, 0x4D, 0xFF
};
CryptoPP::HC256::Encryption hc256;
hc256.SetKeyWithIV(key.data(), key.size(), iv.data(), iv.size());
hc256.ProcessData(buffer.data(), buffer.data(), buffer.size());
CRYPTOPP_ASSERT(CRYPTOPP_VERSION >= 890);
CRYPTOPP_ASSERT(memcmp(buffer.data(), expected.data(), buffer.size()) == 0);
}
Furthermore, the sample on the wiki is not decryptable with BouncyCastle.
Key: CBCBA550FE1A5C34E0C4D6A14D056A08032EA11CF700BD7B9D409B68DC47B9B7
IV: 2E58581CFD129ACE30BD1F86739C713A867550A3DD6D9E0886F63C01B6F9531F
Plain: HC-256 stream cipher test
CryptoPP Cipher: 957BD81955B68A34A4C151D73F89E837945C364CE5B2942CC2
BouncyCastle Cipher: 3531AD8ADC1DEFED1C9D3B705D8B9E87063D1D05E40C19ADB2
The CryptoPP::HC128
algorithm appears fine; it also does not do endian flipping of the key and IV, instead explicitly using GetUserKey(LITTLE_ENDIAN_ORDER ...)
.
Activity