2

For compatibility/legacy reasons I need to use RC2 encryption in CBC mode. I am writing a test - but I get completely different results in C#, Python and with Online Tools, with the (seemingly) same input values.

For all implementations I used the following data:

Data: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 
Key: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00    
IV: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08   
Mode: CBC    
Padding: PKCS7

I tried testing this with Python, C# and by using an online tool called CyberChef. All gave me totally different results.

Python Result: d123f2ac56146f3cebd19b285eb1e1744b828a177778be07

C# Result: f278304ee422a8bbccd54c9157afa818ac4e5b21858ff267

CyberChef Result: c91e276fc97e71acb72426f29c3a6c6f5181d8e83dcf1a98

The python scripts:

from Crypto.Cipher import ARC2
from Crypto.Util.Padding import pad
input = bytes([0]*16)
key = bytes([0]*8)
iv = b"\x01\x02\x03\x04\x05\x06\x07\x08"

cipher = ARC2.new(key, ARC2.MODE_CBC, iv=iv)

msg = cipher.encrypt(pad(input,8,style='pkcs7'))
print("{} {}".format(len(msg), msg.hex()))

C# Script (parts):

public byte[] Encrypt(Rc2CryptoParameters cryptoParameters)
{
    using var outputStream = new MemoryStream();
    using var provider = GetRc2Provider(cryptoParameters);
    using var encryptor = provider.CreateEncryptor();
    using var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write);

    cryptoStream.Write(cryptoParameters.Data);
    cryptoStream.Close();
    return outputStream.ToArray();
}

private RC2CryptoServiceProvider GetRc2Provider(Rc2CryptoParameters cryptoParameters)
{
    return new RC2CryptoServiceProvider
    {
        Key = cryptoParameters.Key,
        BlockSize = cryptoParameters.BlockSize,
        KeySize = cryptoParameters.KeySize,
        Padding = cryptoParameters.PaddingMode,
        Mode = cryptoParameters.CipherMode
    };
}

public Rc2CryptoParameters(byte[] data, byte[] key, byte[] iv)
{
    Data = data;
    Iv = iv;
    Key = key;
    CipherMode = CipherMode.CBC;
    PaddingMode = PaddingMode.PKCS7;
}

So - why am I getting different results everywhere? I tried using some CBC testvectors, the only ones I could find were these: http://cryptomanager.com/tv.html

How can I ensure which of the results is correct? Why are all the implementations producing different results?

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Kyu96
  • 1,159
  • 2
  • 17
  • 35
  • Crypto is designed to be very sensitive to small differences in input. For example, different character encodings will give different results. You *must* specify everything precisely and not rely on system defaults, which will differ between different systems. – rossum Feb 18 '20 at 21:34
  • @rossum We are working with raw bytes here, not with text. What other parameters could be different? – Kyu96 Feb 18 '20 at 21:50
  • Are you going to provide us with actual RC2 parameters in C# or do you have another indirection for us to parse? Why is `Data` in there? – Maarten Bodewes Feb 19 '20 at 01:33
  • I have tested 3 different ones myself: your python code, my Java code and the website (I'm on Linux, my C# environment is uh... well). If you use an all zero IV you can test against the RC2 test vectors [here](https://tools.ietf.org/html/rfc2268), created by Mr Rivest himself. Only my Java results match. Everything else is **borked**. The C# version may be correct, but I don't see the key / IV creation and the data handling is kinda weird. – Maarten Bodewes Feb 19 '20 at 02:00
  • The cyberchef code manages to affect the **second** ciphertext block when I change the IV. I've never seen that kind of tripe happen. Don't use online tools for validation. – Maarten Bodewes Feb 19 '20 at 14:44
  • @MaartenBodewes Thanks for your comment. The test vectors that are described in the RFC are not for CBC mode tho, as far as I can tell? I'll try to add the IV creation for my C# code in there. My main goal is to get the correct implementation in C#, I only used python & cyberchef to understand where the problem is. – Kyu96 Feb 19 '20 at 21:43
  • 1
    Yeah, that's true, but if you set the IV to all zero then the first block will behave the same way. As your first block was already incorrect in all situations, it's likely that the block cipher / key value itself is to blame (although the shenanigans of cyberchef show that you can even mess up something as simple as an IV). So I tested the cipher using an all zero IV, and still got the wrong results (which makes sense if you read the excellent answer of Topaco). – Maarten Bodewes Feb 19 '20 at 21:45
  • @MaartenBodewes Yeah. I added my C# code to his post. Apparently, as you already assumed, something is borked. The C# code results in a different value every run.. – Kyu96 Feb 19 '20 at 21:49

1 Answers1

4

RC2 is described in RFC2268. It's a block cipher with variable key length and has an additional parameter called effective key length in bits, see RFC2268, Section 2. In the two codes and on the website, a different effective key length in bits is used, causing the different results.

In the Python code, when using PyCryptodome, the effective key length in bits is specified with the parameter effective_keylen upon creation of the ARC2-cipher instance, which may have values between 40 and 1024, where 1024 is the default value. Since the parameter isn't explicitly specified in the posted Python code, the default value is used. Note that this parameter is described in the PyCrypto documentation, but not in the PyCryptodome documentation.

The ciphertext of the website results for effective_keylen = 128. On the website there seems to be no possibility to change the effective key length in bits.

The ciphertext of the C# code can't be reproduced, probably because the IV isn't set in GetRc2Provider (so that the randomly generated IV is used). If this is fixed, it turns out that the effective key length in bits (RC2CryptoServiceProvider#EffectiveKeySize) is implicitly set to the actual key length. If the parameter is explicitly switched to a another value, a System.Security.Cryptography.CryptographicUnexpectedOperationException: EffectiveKeySize must be the same as KeySize in this implementation. is thrown.

Community
  • 1
  • 1
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • 1
    As common **bad** practice, I presume that the python code just fills to the right with zero bytes? Never mind, it indeed does. Ugh. For 64 bit keys `effective_keylen=64` does indeed produce the same values as the known good Java implementation. – Maarten Bodewes Feb 19 '20 at 14:40
  • Thanks for your reply. It surely clarifies a few things. For one: That the python docu of pycryptodome isnt well documented. Secondly, as @MaartenBodewes already suspected, my IV creation in C# is borked. Here is my C# code: https://pastebin.com/ychq0H6V Apparently I get a different result with every run, - instead of a static one. So what would you recommend me to change? What effective key_length should I go with? – Kyu96 Feb 19 '20 at 21:49
  • 128 bit would be plenty. But you said that you needed RC2 for backwards compatibility. That probably means that you have to use a pre-configured key size, otherwise you might as well switch to AES. – Maarten Bodewes Feb 19 '20 at 21:54
  • @Maarten Bodewes - The PyCryptodome implementation behaves exactly like the Java implementation with respect to the test vectors from RFC2268 (which check equal, but also shorter and longer key lengths in relation to the effective key lengths): Except for one, all tests are passed successfully. The failed test concerns the test vector with a key length of 1 byte. Both implementations expect a key length of at least 5 bytes and hence throw an exception for this test case (legacy PyCrypto passes even this test successfully). – Topaco Feb 20 '20 at 08:46
  • 1
    @Kyu96 - The cause of the issue lies in `GetRc2Provider`. When setting the `KeySize` the key is implicitly set to `null` (in the base class `SymmetricAlgorithm`). Thus the previously assigned key is ignored and a new key is generated in the further course. To prevent this, first `KeySize` and then `Key` must be assigned. The same applies to the `BlockSize` and the IV, `BlockSize` must be assigned first and then `IV`. Note that in `GetRc2Provider` the assignment of the IV is entirely missing, which must be added (subsequently to the assignment of `BlockSize` as already stated). – Topaco Feb 20 '20 at 15:43
  • @Topaco Thanks, I'll see if this work, and will report back :) – Kyu96 Feb 20 '20 at 18:42