1

I'm looking into porting this "Decryption" function from C# to Python (Source: https://stackoverflow.com/a/10177020)

public static string Decrypt(string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

I have encrypted "Hello World" in C# and managed to get to the point where I have the correct values all the way to (and including) keyBytes in Python. I have tried to get the decrypted text using pprp (https://pypi.org/project/pprp/) and other libraries, but no matter what I try I only get "Wrong IV size" or junk data. I assume that I'm struggling with the PKCS7 padding, but at this point I'm just completely lost. Would highly appreciate any help on the last part. :)

import pprp
import base64
from pkcs7 import PKCS7Encoder      


cipherText = "JKjzaiOSreH+l0GSzsatS8nmohRisvINOwrEjHOwIqIXo88CQT0/al7V7vXOmuamfTeJ235O1SZ0Yd2BZk2e2V4MznT7hyzqzu5J326JIReXPeH6EdtPFrxhdTPsfb8Q"
passphrase = "a78356254f093b00e45434828110c7b5"

# This constant is used to determine the keysize of the encryption algorithm in bits.
# We divide this by 8 within the code below to get the equivalent number of bytes.
keysize = 256

# This constant determines the number of iterations for the password bytes generation function.
derivationIterations = 1000

# Get the complete stream of bytes that represent:
# [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
cipherTextBytesWithSaltAndIv = base64.b64decode(cipherText)
cipherTextBytesWithSaltAndIv=list(bytearray(cipherTextBytesWithSaltAndIv))

# Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
saltStringBytes = cipherTextBytesWithSaltAndIv[:int(keysize / 8)]

# Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
ivStringBytes = cipherTextBytesWithSaltAndIv[int(keysize / 8):int((keysize / 8) * 2)]

# Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.    
cipherTextBytes = cipherTextBytesWithSaltAndIv[int((keysize / 8) * 2):]

keyBytes = pprp.pbkdf2(passphrase.encode('utf-8'), bytes(saltStringBytes), int(keysize / 8))

print(base64.b64encode(keyBytes))
NeoID
  • 901
  • 1
  • 11
  • 29

1 Answers1

0

It would have made sense if you had posted the complete code, including the Rijndael-encryption (which you have implemented according to your description). Anyway, the existing Python code including the key generation using PBKDF2 generates the same data as the C# code.

pprp implements only the Rijndael-block cipher itself. This allows only a single complete block to be encrypted. Both padding and mode of operation are largely not implemented, i.e. they still have to be implemented by the user. The mode of operation defines how multiple blocks are encrypted based on a block cipher. "Largely" means that there is already an adapter that implements the (insecure) ECB-mode and PKCS7-padding. This does not help in this case, because the C#-code uses the CBC-mode and PKCS7-padding. For this the appropriate adapter must be implemented first. The existing implementation of the ECB-mode (adapters.py) can be used as a blueprint. A possible implementation (for the decryption) is:

import pprp
import base64

# CBC-Adapter
def rjindael_decrypt_gen_CBC(key, s, iv, block_size=pprp.config.DEFAULT_BLOCK_SIZE_B):
    r = pprp.crypto_3.rijndael(key, block_size=block_size) # for Python 2 use crypto_2 instead of crypto_3

    i = 0
    for block in s:

        decrypted = r.decrypt(block)
        decrypted = xor(decrypted, iv)  
        iv = block

        yield decrypted
        i += 1

def xor(block, iv):
    resultList = [ (a ^ b) for (a,b) in zip(block, iv) ]
    return bytes(resultList)

#
# Posted code goes here...
#

# Decryption
blocksize = 32
sg = pprp.data_source_gen(cipherTextBytes, blocksize)
dg = rjindael_decrypt_gen_CBC(keyBytes, sg, ivStringBytes, blocksize);
decrypted = pprp.decrypt_sink(dg, blocksize)
print("Decrypted data: " + str(decrypted))

With regard to the "Wrong IV size" error message, I suspect that the other implementations you tried are actually AES-implementations. AES is not identical to Rijndael, but a special variant of Rijndael. For example, AES has a fixed blocksize of 16 Bytes, while Rijndael allows 16, 20, 24, 28 or 32 Bytes. In the C# code Rijndael with a blocksize of 32 Bytes is used, so that the IV, whose length must correspond to the blocksize, is also 32 Bytes large. If you use this IV in an AES-implementation, you will inevitably get an error message like "Wrong IV size" or something similar. In addition, pprp supports the blocksizes 16, 24 and 32 Bytes. By default a blocksize of 16 Bytes is used.

Topaco
  • 40,594
  • 4
  • 35
  • 62