3

Hello I need to pass a C # encryption algorithm to python, but I can not get the same result in the final hash, would anyone know tell me what I am doing wrong?

This is the C# AES Cipher code:

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;

public class Program
{
    public static void Main()
    {
        string data = "leandro";
        string encrypt = Encrypt(data);
        Console.WriteLine(encrypt);

    }

    static readonly char[] padding = { '=' };
    private const string EncryptionKey = "teste123";
    public static string Encrypt(string clearText)
    {

        byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
        Console.WriteLine(clearBytes);
        using (Aes encryptor = Aes.Create())
        {
            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.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(clearBytes, 0, clearBytes.Length);
                    cs.Close();
                }
                clearText = Convert.ToBase64String(ms.ToArray()).TrimEnd(padding).Replace('+', '-').Replace('/', '_');
            }
        }
        return clearText;

    }

}

Output: DTyK3ABF4337NRNHPoTliQ

And this is my python version:

import base64

from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2



class AESCipher(object):

    def __init__(self, key, interactions=1000):
        self.bs = AES.block_size
        self.key = key
        self.interactions = interactions

    def encrypt(self, raw):
        raw = self._pad(raw)
        nbytes = [0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76,
                  0x65, 0x64, 0x65, 0x76]
        salt = bytes(nbytes)
        keyiv = PBKDF2(self.key, salt, 48, self.interactions)
        key = keyiv[:32]
        iv = keyiv[32:48]
        cipher = AES.new(key, AES.MODE_CBC, iv)
        enc = base64.b64encode(iv + cipher.encrypt(raw.encode('utf-16le')))
        return self._base64_url_safe(str(enc, "utf-8"))


    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * \
            chr(self.bs - len(s) % self.bs)

    def _base64_url_safe(self, s):
        return s.replace('+', '-').replace('/', '_').replace('=', '')

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s) - 1:])]


enc = AESCipher("teste123")
dec = enc.encrypt("leandro")
print(dec)

Output: LJTFEn0vmz8IvqFZJ87k8lI8DPh8-oIOSIxmS5NE4D0

6
  • I would start by seeing if you can get both programs to output the same inputs (e.g. the key). It's possible you're extracting bytes from them in a different way. Your code example doesn't include the implementation of self.key Commented Jan 13, 2020 at 19:09
  • @JohnWu sorry for my mistake in the post, i already edited the question, but that's not the problem: on python the key is implemented on def __init__(self, key): self.bs = AES.block_size self.key = key ANS ON C#, private const string EncryptionKey = "KEY"; I already tried with the same keys but kept getting different results Commented Jan 13, 2020 at 19:21
  • Your key is not a string. It is an array of bytes that you derive from a string. See if you can get them to match-- output them or view them with a debugger. If they do match, move on to the next step and get all of your inputs and outputs to match line by line. It's very hard to tell what is wrong by looking only at the end result. Commented Jan 13, 2020 at 19:24
  • The PHP prepends the IV, however, the C# is not. One can see from the output sizes. Base64 prevents the detection of this. Also, there is no - in base64. Commented Jan 13, 2020 at 19:29
  • @kelalaka It's converting between base 64 to base 64 url (base 64 using an URL-safe alphabet). No idea if that happens as it should be, but that's the idea. It's in the base 64 RFC. Commented Jan 13, 2020 at 23:51

1 Answer 1

3

You are using Encoding.Unicode.GetBytes(clearText) which returns UTF-16LE while Python (more sensibly) defaults to UTF-8 for raw.encode(). I'd use Encoding.UTF8 for your C# code.

As already mentioned in the comments, the Python also adds the IV in front of the ciphertext, while the C# code simply performs the encryption and calculates the IV during decryption (so it doesn't need to be stored).

Here is a Python program that does the same:

import base64

from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Util.Padding import pad


class AESCipher(object):

    def __init__(self, key, interactions=1000):
        self.bs = AES.block_size
        self.key = key
        self.interactions = interactions

    def encrypt(self, raw):
        nbytes = [0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76,
                  0x65, 0x64, 0x65, 0x76]

        salt = bytes(nbytes)
        keyiv = PBKDF2(self.key, salt, 48, self.interactions)
        key = keyiv[:32]
        iv = keyiv[32:48]

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

        encoded = raw.encode('utf-16le')
        encodedpad = pad(encoded, self.bs)

        ct = cipher.encrypt(encodedpad)

        cip = base64.b64encode(ct)
        return self._base64_url_safe(str(cip, "utf-8"))

    def _base64_url_safe(self, s):
        return s.replace('+', '-').replace('/', '_').replace('=', '')

enc = AESCipher("teste123")
dec = enc.encrypt("leandro")
print(dec)

Before you shout Eureka, please do understand that the ciphertext that the C# code is producing is not integrity protected nor authenticated. Moreover, it is vulnerable to padding oracle attacks if those are present at the receiver. Padding oracle attacks are terribly efficient and you would loose complete confidentiality of the message if they apply.

Furthermore, if the salt is non-random then the key and IV are also non-random. That in turn means that the ciphertext is as random as the plaintext. In other words, it leaks data if the same plaintext blocks are encountered. So I hope the non-random salt is just there for testing purposes.

In the end, having encryption running as expected doesn't mean that your solution is secure.

Sign up to request clarification or add additional context in comments.

8 Comments

I can't change C# code, i need to reproduce same hash from c# to python: i have tried base64.b64encode(iv + cipher.encrypt(raw.encode().decode('utf-8').encode('utf-16le'))
What about raw.encode('utf-16le')? I don't see the reason for all this back and forth encoding / decoding... Let me know if that works for you (and don't forget to add the IV to the memory stream before encoding).
Thanks for answer, bus python hash still lot bigger than c# hash
Python shows a base 64 encoding of 32 bytes, I checked that. So if your C# does indeed provide IV (on block / 16bytes) + ciphertext (one block / 16 bytes) then it should have the exact same size. If not, you're doing something wrong.
python3 hash b'LJTFEn0vmz8IvqFZJ87k8oPl/rJxBPBKfByZNADSbTt/Aniifh518Ki7mN6Ykrbl'c# hash DTyK3ABF4337NRNHPoTliQ
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.