Bad Decryp In Python from Encrypted with C#

55 views Asked by At

I've written the C# code below to encrypt a string but the decryption code I've written in Python fails, apparently because the IV is derived incorrectly.

Encryption code:

public string Encrypt(string plainText)
{
    // Transformar la clave de encriptación de Base64 a bytes
    var base64Key = "MiEncryptKey=".Replace('_', '/').Replace('-', '+');
    var keyBytes = Convert.FromBase64String(base64Key);

    // Derivar la clave y el IV usando Rfc2898DeriveBytes con SHA256
    var salt = Encoding.UTF8.GetBytes("AgenteSalt");
    var pdb = new Rfc2898DeriveBytes(keyBytes, salt, 1000);
    var key = pdb.GetBytes(32);
    var iv = pdb.GetBytes(16);

    // Configurar el cifrador AES en modo CBC con PKCS7 padding
    using (var aes = new RijndaelManaged())
    {
        aes.KeySize = 256;
        aes.BlockSize = 128;
        aes.Padding = PaddingMode.PKCS7;
        aes.Mode = CipherMode.CBC;
        aes.Key = key;
        aes.IV = iv;

        // Encriptar el texto
        using (var ms = new MemoryStream())
        {
            using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
            {
                var bytes = Encoding.UTF8.GetBytes(plainText);
                cs.Write(bytes, 0, bytes.Length);
                cs.Close();
            }
            var encrypted = ms.ToArray();

            // Codificar el resultado en Base64
            return Convert.ToBase64String(encrypted);
        }
    }
}

This is the code for decrypting in Python, which doesn't work.

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import base64

class Decryptor:
    def __init__(self, base64_key, salt=b"MiSalt"):
        self.base64_key = base64_key.replace('_', '/').replace('-', '+')
        self.encryption_key = base64.b64decode(self.base64_key)
        self.salt = salt
        self.iterations = 1000

    def _get_cipher(self):
        # Usando SHA1 aquí para coincidir con el algoritmo de hash de C#
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA1(),
            length=32,
            salt=self.salt,
            iterations=self.iterations,
            backend=default_backend()
        )
        key = kdf.derive(self.encryption_key)
        iv = key[:16]
        return Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

    def decrypt(self, cipher_text):
        cipher = self._get_cipher()
        decryptor = cipher.decryptor()
        ct = base64.b64decode(cipher_text)
        decrypted = decryptor.update(ct) + decryptor.finalize()
        return self._unpad(decrypted)

    def _unpad(self, s):
        pad_size = s[-1]
        if pad_size > 16:
            raise ValueError("Invalid padding")
        return s[:-pad_size]

I was the original string encrypted in C#, decryp it with Python, but don't know what I'm doing wrong

I followed this recommendations, but they don't work The key aspects to consider in adjusting the implementation in C# to match that of Python are:

Encryption Key Transformation: Make sure that the encryption key is handled in the same way in C# as it is in Python. This includes how you transform from Base64 to bytes.

Key and IV derivation: In Python, we're using PBKDF2HMAC with SHA256 to derive the key and IV. Make sure that C# uses the same algorithm and process to generate the key.

1

There are 1 answers

0
Binapps On

HELLO I lasted, a week looking for the solution, I hope you don't have to suffer so long :)

SOLUTION:

In C#, I was getting both the key and the IV from the same Rfc2898DeriveBytes object. This is an unusual practice, as the IV is usually randomly generated and transmitted along with the ciphertext, or kept constant and known by both sides of the encryption and decryption process. In your case, you're generating the IV from the same source (the key and the salt), but after generating the key, which means that the IV will depend on the key.

In Python, I was doing something similar: I was using PBKDF2 to generate both the key and the IV from the same source. However, since I don't use the same PBKDF2 object (as is done in C# with Rfc2898DeriveBytes), it's very likely that the IV generated in Python doesn't match the IV generated in C#.

This is the correct way to encrypt in C# and then be able to decrypt in python

To clarify, I have my encryption key in the Visual Studio development secrets trunk you can replace your key, in this variable: mybase64Key ='myEncryptionKey'

Use this approach to solve the problem: Randomly Generate IV and Transmit It A more common and secure practice is to randomly generate the IV for each cipher and then transmit it along with the ciphertext. This means that the IV does not need to be derived from the key.

Code C# correct that encrypt

using System.Security.Cryptography;
using System;
using System.IO;
using System.Text;
using Chatne.WebApp.Server.Services.Contracts;
using Chatne.WebApp.Server.Models;

namespace Chatne.WebApp.Server.Services
{
    public class HelpersService : IHelpersService
    {
        private static readonly byte[] Salt = Encoding.ASCII.GetBytes("MiSalt");
        private const int Iterations = 1000; // Puede ser ajustado

        private readonly IConfiguration _configuration;
        private readonly byte[] _encryptionKey;
        private readonly byte[] _salt;
        private readonly int _iterations;

        public HelpersService(IConfiguration configuration)
        {
            _configuration = configuration;
            var mybase64Key = _configuration["Encryption:Key"]
                .Replace('_', '/')
                .Replace('-', '+');

            _encryptionKey = Convert.FromBase64String(mybase64Key);
            _salt = Encoding.UTF8.GetBytes("MiSalt");
            _iterations = 1000; // Número de iteraciones para PBKDF2
        }


        public string Encrypt(string plainText, string salt = null)
        {
            if (string.IsNullOrEmpty(plainText))
                return null;

            byte[] saltBytes = _salt;
            if (!string.IsNullOrEmpty(salt))
            {
                saltBytes = Encoding.UTF8.GetBytes(salt);
            }

            using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(_encryptionKey, saltBytes, _iterations))
            {
                byte[] key = rfc2898DeriveBytes.GetBytes(32); // 256 bits para la clave
                byte[] iv = rfc2898DeriveBytes.GetBytes(16);  // 128 bits para el IV

                using (var aes = Aes.Create())
                {
                    aes.KeySize = 256;
                    aes.BlockSize = 128;
                    aes.Mode = CipherMode.CBC;
                    aes.Padding = PaddingMode.PKCS7;
                    aes.Key = key;
                    aes.GenerateIV(); // Generar un IV aleatorio
                    iv = aes.IV;


                    using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
                    using (var ms = new MemoryStream())
                    using (var cryptoStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
                        cryptoStream.Write(plainBytes, 0, plainBytes.Length);
                        cryptoStream.FlushFinalBlock();

                        byte[] encrypted = ms.ToArray();
                        // El IV debe ser enviado junto con el texto cifrado
                        byte[] encryptedAndIv = new byte[iv.Length + encrypted.Length];
                        Buffer.BlockCopy(iv, 0, encryptedAndIv, 0, iv.Length);
                        Buffer.BlockCopy(encrypted, 0, encryptedAndIv, iv.Length, encrypted.Length);


                        return Convert.ToBase64String(encryptedAndIv);
                    }
                }
            }
        }
}

Code correct that decrypt using Python

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

class Decryptor:
    def __init__(self, encryption_key):
        self.encryption_key = base64.b64decode(encryption_key)
        self.salt = b'AgenteSalt'  # El salt debe ser el mismo que en C#
        self.iterations = 1000  # El número de iteraciones debe ser el mismo que en C#

    def decrypt(self, cipher_text):
        encrypted_bytes = base64.b64decode(cipher_text)
        iv = encrypted_bytes[:16]  # Asume que los primeros 16 bytes son el IV
        encrypted_bytes = encrypted_bytes[16:]  # Resto es el texto cifrado

        key = PBKDF2(self.encryption_key, self.salt, dkLen=32, count=self.iterations)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(encrypted_bytes)

        # Remover el padding PKCS7
        pad_size = decrypted[-1]
        if isinstance(pad_size, str):  # Python 2.x compatibilidad
            pad_size = ord(pad_size)
        decrypted = decrypted[:-pad_size]

        return decrypted.decode('utf-8')