Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decrypt string in C# that was encrypted with PHP openssl_encrypt

I have a customer encrypting a string in PHP with the following code:

    $password = 'Ty63rs4aVqcnh2vUqRJTbNT26caRZJ';
    $method = 'AES-256-CBC';
    texteACrypter = 'Whether you think you can, or you think you can\'t--you\'re right. - Henry Ford';

    $encrypted = openssl_encrypt($texteACrypter, $method, $password);

which results in this encrypted output: MzVWX4tH4yZWc/w75zUagUMEsP34ywSYISsIIS9fj0W3Q/lR0hBrHmdvMOt106PlKhN/1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0=

When I try to decrypt that string in C# it gives me a bunch of junk like so: Z�o�}'*2��I4y�J6S�� ��xz���{9^�ED�fF �}��گs�)�Q���i��$)�

I have tried changing the padding, using AesManaged instead of RijndaelManaged, changing the keysize, using a different key, etc. All result in either different junk strings or various exceptions. I must be missing something really basic here but I'm not sure what else to try at this point.

Here is my decryption code (that I shamelessly copied from another stackoverflow question: openssl using only .NET classes)

class Program
{
    //https://stackoverflow.com/questions/5452422/openssl-using-only-net-classes
    static void Main(string[] args)
    {
        var secret = "Ty63rs4aVqcnh2vUqRJTbNT26caRZJ";
        var encrypted = "MzVWX4tH4yZWc/w75zUagUMEsP34ywSYISsIIS9fj0W3Q/lR0hBrHmdvMOt106PlKhN/1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0=";

        var yeah = OpenSSLDecrypt(encrypted, secret);
        Console.WriteLine(yeah);
        Console.ReadKey();
    }

    public static string OpenSSLDecrypt(string encrypted, string passphrase)
    {
        // base 64 decode
        byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted);
        // extract salt (first 8 bytes of encrypted)
        byte[] salt = new byte[8];
        byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length - 8];
        Buffer.BlockCopy(encryptedBytesWithSalt, 8, salt, 0, salt.Length);
        Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + 8, encryptedBytes, 0, encryptedBytes.Length);
        // get key and iv
        byte[] key, iv;
        DeriveKeyAndIV(passphrase, salt, out key, out iv);
        return DecryptStringFromBytesAes(encryptedBytes, key, iv);
    }

    private static void DeriveKeyAndIV(string passphrase, byte[] salt, out byte[] key, out byte[] iv)
    {
        // generate key and iv
        List<byte> concatenatedHashes = new List<byte>(48);

        byte[] password = Encoding.UTF8.GetBytes(passphrase);
        byte[] currentHash = new byte[0];
        MD5 md5 = MD5.Create();
        bool enoughBytesForKey = false;
        // See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
        while (!enoughBytesForKey)
        {
            int preHashLength = currentHash.Length + password.Length + salt.Length;
            byte[] preHash = new byte[preHashLength];

            Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
            Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length);
            Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length);

            currentHash = md5.ComputeHash(preHash);
            concatenatedHashes.AddRange(currentHash);

            if (concatenatedHashes.Count >= 48)
                enoughBytesForKey = true;
        }

        key = new byte[32];
        iv = new byte[16];
        concatenatedHashes.CopyTo(0, key, 0, 32);
        concatenatedHashes.CopyTo(32, iv, 0, 16);

        md5.Clear();
    }

    static string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv)
    {
        // Check arguments.
        if (cipherText == null || cipherText.Length <= 0)
            throw new ArgumentNullException("cipherText");
        if (key == null || key.Length <= 0)
            throw new ArgumentNullException("key");
        if (iv == null || iv.Length <= 0)
            throw new ArgumentNullException("iv");

        // Declare the RijndaelManaged object
        // used to decrypt the data.
        RijndaelManaged aesAlg = null;

        // Declare the string used to hold
        // the decrypted text.
        string plaintext;

        // Create a RijndaelManaged object
        // with the specified key and IV.
        aesAlg = new RijndaelManaged { Mode = CipherMode.CBC, Padding = PaddingMode.None, KeySize = 256, BlockSize = 128, Key = key, IV = iv };

        // Create a decrytor to perform the stream transform.
        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
        // Create the streams used for decryption.
        using (MemoryStream msDecrypt = new MemoryStream(cipherText))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                {
                    // Read the decrypted bytes from the decrypting stream
                    // and place them in a string.
                    plaintext = srDecrypt.ReadToEnd();
                    srDecrypt.Close();
                }
            }
        }

        return plaintext;
    }
}
like image 667
Dave Graves Avatar asked Nov 01 '13 01:11

Dave Graves


1 Answers

Well this was fun to work out and required jumping into the PHP source code with some interesting results. Firstly PHP doesn't even use a key derivation algorithm it just takes the bytes of the passphrase and pads it out with zero's to the required length. That means the entire DeriveKeyAndIV method isn't necessary.

Because of the above that means the IV that is being used is a 16 length byte array containing zeros.

Finally the only other thing wrong with your code is that the source you copied it from used a salt in their implementation of encrypt which then had to be removed, PHP nor you are doing this so removing the salt bytes is incorrect.

So the all of this put together means you need to change the OpenSSLDecrypt method to this.

public static string OpenSSLDecrypt(string encrypted, string passphrase)
{
    //get the key bytes (not sure if UTF8 or ASCII should be used here doesn't matter if no extended chars in passphrase)
    var key = Encoding.UTF8.GetBytes(passphrase);

    //pad key out to 32 bytes (256bits) if its too short
    if (key.Length < 32)
    {
        var paddedkey = new byte[32];
        Buffer.BlockCopy(key, 0, paddedkey, 0, key.Length);
        key = paddedkey;
    }

    //setup an empty iv
    var iv = new byte[16];

    //get the encrypted data and decrypt
    byte[] encryptedBytes = Convert.FromBase64String(encrypted);
    return DecryptStringFromBytesAes(encryptedBytes, key, iv);
}

And very finally the resulting string has some extra chars at the end namely a set of 3 of the ETX char but these should be easy enough to filter out. I actually can't figure out where these are coming from.

Thanks to @neubert for pointing out the padding is a part of the standard PKCS padding if you want the framework to remove this just specify that as the padding mode when instantiating the RijndaelManaged object.

new RijndaelManaged { Padding = PaddingMode.PKCS7 };
like image 96
David Ewen Avatar answered Sep 24 '22 15:09

David Ewen