0

I have a c# client that decrypts an AES encrypted message. I tried to implement the c# logic in my python client, but the result is not the same and is full of question marks and vague characters.

I am using python 3.5 with pycrypto running mint x64. the code for both c# client and my python version of the code provided below:

c# code:

string EncryptionKey = "MAKV2SPBNI99212"; 
byte[] cipherBytes = Convert.FromBase64String(cipherText); //Get the encrypted message's bytes
using (Aes encryptor = Aes.Create()) //Create a new AES object
                {
                    //Decrypt the text
                    Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
                    encryptor.Key = pdb.GetBytes(32);
                    encryptor.IV = pdb.GetBytes(16);
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                        {
                            cs.Write(cipherBytes, 0, cipherBytes.Length);
                            cs.Close();
                        }
                        plainText = Encoding.Unicode.GetString(ms.ToArray());
                    }

my python version:

def decode_base64(data, altchars=b'+/'):
    """Decode base64, padding being optional.

    :param data: Base64 data as an ASCII byte string
    :returns: The decoded byte string.

    """
    data = re.sub(rb'[^a-zA-Z0-9%s]+' % altchars, b'', data)  # normalize
    missing_padding = len(data) % 4
    if missing_padding:
        data += b'='* (4 - missing_padding)
    return base64.b64decode(data, altchars)

def decode_message(data, key):
    enc_txt = decode_base64(bytes(data, 'utf-16'))
    salt_t = ["0x49", "0x76", "0x61", "0x6e", "0x20", "0x4d", "0x65", "0x64", "0x76", "0x65", "0x64", "0x65", "0x76"]
    salt = bytes([int(x, 0) for x in salt_t])
    key_bytes = KDF.PBKDF2(key, salt, 32, 1000)
    # iv = enc_txt[:16] // using this line instead of the below line, has no effects on final result
    iv = KDF.PBKDF2(key, salt, 16, 1000)
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
    return cipher.decrypt(enc_txt).decode('utf-16')

the c# client is working as expected, but my python client is resulting in vague characters and not the actual expected message.

I ran into this post I think I have a similar problem but I couldn't understand the provided answer. any answer would be appreciated. thanks in advance.

UPDATE: C# Server Side Encryption: This is the C# server side encryption code as well, i think this question covers multiple aspects of the scenario based on the linked questions and could be a reference for anybody that face the same issues (encodings, encryption, padding...)

string EncryptionKey = "MAKV2SPBNI99212"; //Declare the encryption key (it's not the best thing to do)
byte[] clearBytes = Encoding.Unicode.GetBytes(clearText); //Get the bytes of the message
using (Aes encryptor = Aes.Create()) //Create a new aes object
            {
                Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
                encryptor.Key = pdb.GetBytes(32); //Set the encryption key
                encryptor.IV = pdb.GetBytes(16); //Set the encryption IV

                using (MemoryStream ms = new MemoryStream()) //Create a new memory stream
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) //Create a new crypto stream
                    {
                        cs.Write(clearBytes, 0, clearBytes.Length); //Write the command to the crypto stream
                        cs.Close(); //Close the crypto stream
                    }
                    cipherText = System.Convert.ToBase64String(ms.ToArray()); //Convert the encrypted bytes to a Base64 string
pouya
  • 53
  • 1
  • 7
  • Have you tried `utf-8`? – Ali Bahrami Jun 25 '19 at 04:46
  • hello there! yes but it was not functional.I had a situation with encodings similar to this [post](https://stackoverflow.com/questions/10565896/decrypting-in-python-an-string-encrypted-using-net/10565933) – pouya Jun 25 '19 at 12:56
  • It would be helpful if you would post the C# encryption portion to know what the ciphertext looks like (with regard to the BOM). Can only the Python code be changed or also the C# code? – Topaco Jun 25 '19 at 13:47

2 Answers2

1
  • In the Python code the IV is wrongly determined and the code should be changed as follows:

    keyiv = KDF.PBKDF2(key, salt, 48, 1000)
    key = keyiv[:32]
    iv = keyiv[32:48]
    
  • In addition, PKCS7 padding is used in the C# code, so that unpadding is necessary in the Python code during decryption. One possibility is Crypto.Util.Padding:

    import Crypto.Util.Padding as padding
    
    ...
    
    decryptedPadded = cipher.decrypt(enc_txt)
    decrypted = padding.unpad(decryptedPadded, 16)  # Pkcs7 unpadding
    return decrypted.decode('utf-16')               # UTF-16LE decoding including BOM-removal
    

    In the C# code UTF-16LE (Encoding.Unicode) encoded data are encrypted. The data are preceded by a 2 byte BOM (0xFFFE). This BOM is automatically removed during UTF-16LE decoding.

  • The method decode_base64 in the Python code seems to have been adopted from here. This method should reconstruct a lost Base64 padding. I'm not quite sure why this should be necessary here. Also the UTF-16 encoding of the ciphertext when calling the method seems pointless to me. Actually a simple Base64 decoding of the ciphertext should be enough:

    import base64
    ...
    enc_txt = base64.b64decode(data)
    

    But maybe there are aspects here that I missed.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • i appreciate your kind and absolutely useful solution, it solved my case. thanks in advance. about the decode_base64 you mentioned, i am totally agree with you. my case is a special one that i do not decrypt the whole received base64 message, but only a portion of it, so it is prone to padding errors. – pouya Jun 25 '19 at 19:00
1

@Topaco thanks for the explanation and answer.

Pasting the complete code here for those facing the same issue.

import base64
import Crypto.Util.Padding as padding
from Crypto.Cipher import AES
from Crypto.Protocol import KDF
from pbkdf2 import PBKDF2

def decrypt(data, key):
        enc_txt = base64.b64decode(data)
        salt_t = ["0x49", "0x76", "0x61", "0x6e", "0x20", "0x4d", "0x65", "0x64", "0x76", "0x65", "0x64", "0x65", "0x76"]
        salt = bytes([int(x, 0) for x in salt_t])
        key_bytes = KDF.PBKDF2(key, salt, 32, 1000)
        iv = KDF.PBKDF2(key, salt, 48, 1000)[32:48]
        cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
        decryptedPadded = cipher.decrypt(enc_txt)
        decrypted = padding.unpad(decryptedPadded, 16)  # Pkcs7 unpadding
        return decrypted.decode('utf-16')        # UTF-16LE decoding including BOM-removal
Savan
  • 161
  • 3
  • 11