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
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.