2

I want to decrypt in VB.net strings encrypted in PHP via openssl_encrypt function:

$encrypted_string = openssl_encrypt(
    $string,
    'AES256',
    'secret_password',
    0,
    'initialization_vector'
);

I tried with this class:

Public Class Aes256Encrypter
    Public Function Encrypt(ByVal plainText As String, ByVal secretKey As String) As Byte()
        Dim encryptedPassword As Byte()
        Using outputStream As MemoryStream = New MemoryStream()
            Dim algorithm As RijndaelManaged = getAlgorithm(secretKey)
            Using cryptoStream As CryptoStream = New CryptoStream(outputStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write)
                Dim inputBuffer() As Byte = Encoding.Unicode.GetBytes(plainText)
                cryptoStream.Write(inputBuffer, 0, inputBuffer.Length)
                cryptoStream.FlushFinalBlock()
                encryptedPassword = outputStream.ToArray()
            End Using
        End Using
        Return encryptedPassword
    End Function

    Public Function Decrypt(ByVal encryptedBytes As Byte(), ByVal secretKey As String) As String
        Dim plainText As String = Nothing
        Using inputStream As MemoryStream = New MemoryStream(encryptedBytes)
            Dim algorithm As RijndaelManaged = getAlgorithm(secretKey)
            Using cryptoStream As CryptoStream = New    CryptoStream(inputStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read)
                Dim outputBuffer(0 To CType(inputStream.Length - 1, Integer)) As Byte
                Dim readBytes As Integer = cryptoStream.Read(outputBuffer, 0, CType(inputStream.Length, Integer))
                plainText = Encoding.Unicode.GetString(outputBuffer, 0, readBytes)
            End Using
        End Using
        Return plainText
    End Function

    Private Function getAlgorithm(ByVal secretKey As String) As RijndaelManaged
        Const salt As String = "put your salt here"
        Const keySize As Integer = 256

        Dim keyBuilder As Rfc2898DeriveBytes = New Rfc2898DeriveBytes(secretKey, Encoding.Unicode.GetBytes(salt))
        Dim algorithm As RijndaelManaged = New RijndaelManaged()
        algorithm.KeySize = keySize
        algorithm.IV = keyBuilder.GetBytes(CType(algorithm.BlockSize / 8, Integer))
        algorithm.Key = keyBuilder.GetBytes(CType(algorithm.KeySize / 8, Integer))
        algorithm.Padding = PaddingMode.PKCS7
        Return algorithm
    End Function
End Class

I use IV "initialization_vector" as salt string, but at runtime an error is thrown: 'System.Security.Cryptography.CryptographicException' in mscorlib.dll. An error occurred: Padding is invalid and cannot be removed.

Where am I doing wrong?

Community
  • 1
  • 1
Blazeag
  • 481
  • 8
  • 13
  • 1
    Possible duplicate of [Standard library for AES encryption for VB.NET?](http://stackoverflow.com/questions/16420173/standard-library-for-aes-encryption-for-vb-net) – SaggingRufus Jan 09 '17 at 13:23
  • Thank you, I'm trying with this right now. – Blazeag Jan 09 '17 at 13:28
  • Sorry @SaggingRufus but still nothing. It's not clear how should I use the initialization vector. If I put it as a salt, at runtime it throws an exception, 'System.Security.Cryptography.CryptographicException' in mscorlib.dll: Padding is invalid and cannot be removed – Blazeag Jan 09 '17 at 13:41
  • is the salt padded with spaces? – SaggingRufus Jan 09 '17 at 14:05
  • @SaggingRufus if with "salt" is meant the initialization vector, it is alphanumeric, with no spaces. I don't know what type of padding does openssl_encrypt function uses as default – Blazeag Jan 09 '17 at 14:10
  • Based on this: http://stackoverflow.com/questions/23406135/error-rijndaelmanaged-padding-is-invalid-and-cannot-be-removed it looks like the salts aren't matching – SaggingRufus Jan 09 '17 at 14:13
  • maybe try the padmode.zeroes – SaggingRufus Jan 09 '17 at 14:14
  • @SaggingRufus tried with PaddingMode.Zeros, it doesn't throw errors anymore but decrypted string totally wrong. I'm sure I'm using the same password and parameters as in PHP – Blazeag Jan 09 '17 at 14:18
  • did you change the Const salt As String = "put your salt here" to your actual salt value? – SaggingRufus Jan 09 '17 at 14:22
  • @SaggingRufus yes, I changed it to my initialization vector (in the example it is "initialization_vector" – Blazeag Jan 09 '17 at 14:26
  • In PHP The IV should be a hexademical representation of the salt. I am pretty sure you will need to convert that back to text for VB to use it properly – SaggingRufus Jan 09 '17 at 14:28
  • @zaph you're right, the example IV is 22-bytes long, but the one I'm using in the real code is 16-bytes long (otherwise it would throw an error in PHP encrypting too). I now tried specifying algorithm.Mode = CipherMode.CBC, but still no correct decrypt. With PaddingMode.PKCS7 is still thrown "Padding is invalid and cannot be removed" error. – Blazeag Jan 09 '17 at 14:55
  • @Blazeag Under `PaddingMode.None` you can examine what the unpadded data looks like. If you have more than 16 bytes of decrypted data then errors in the first 16 bytes only means you have an IV reconstruction problem, errors beyond that means you have a key reconstruction problem. PHP will zero-pad (on the right) keys that are "too small" (from whatever the default keysize of the algorithm is) otherwise uses the `$password` variable as the key. PBKDF2 is not involved at all. The hard part here is figuring out what parsing PHP has done (if any). – bartonjs Jan 09 '17 at 18:26
  • It really does suck that the default block size is not in the documentation and one needs to look at the "Reference Source". – zaph Jan 09 '17 at 18:36
  • @zaph https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndael(v=vs.110).aspx#Remarks, https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged(v=vs.110).aspx#Remarks – bartonjs Jan 09 '17 at 19:26
  • @bartonjs I see that but it is under "Remarks", that is not the correct place, the correct place is on the `BlockSize` parameter. The parameter states "(Inherited from SymmetricAlgorithm.)" which is not helpful. MSDN could do much better. – zaph Jan 09 '17 at 20:11
  • You should probably break this into two questions: the first being derivation, and the second being encryption (and decryption). You should start by ensuring the string *"secret_password"* is derived into the same bytes in both PHP and .Net. .Net uses 16-bit strings for `String` class. I'm guessing that's your first problem. – jww Jan 09 '17 at 21:13
  • @bartonjs I don't think the problem is the password length, since it is longer than the string to crypt (I use a 60 chars password on a 15 chars string). I'm now trying to check further about strings bit length in .net, as suggested by jww – Blazeag Jan 10 '17 at 10:08

2 Answers2

4

Okay, so this is a C# answer, and I don't like it; but I've produced a thing which gives the same answer as php's openssl_encrypt.

  • PHP passes the string as if it were bytes, so Encoding.ASCII (not sure what it does with non-ASCII data, actually)
  • PHP/OpenSSL silently truncates long keys (so your 60 character password is a 32 character password)
  • Working with the same data in ASCII strings and hex bytes is a pain in the rear.

Code:

private static byte[] php_encode(string php_string, int expectedLength)
{
    byte[] temp = Encoding.ASCII.GetBytes(php_string);

    if (temp.Length == expectedLength)
    {
        return temp;
    }

    byte[] ret = new byte[expectedLength];
    Buffer.BlockCopy(temp, 0, ret, 0, Math.Min(temp.Length, expectedLength));
    return ret;
}

private static string php_encrypt_aes256(string php_data, string php_password, string php_iv)
{
    byte[] key = php_encode(php_password, 32);
    byte[] iv = php_encode(php_iv, 16);
    byte[] data = Encoding.ASCII.GetBytes(php_data);

    using (Aes aes = Aes.Create())
    using (ICryptoTransform encryptor = aes.CreateEncryptor(key, iv))
    {
        return Convert.ToBase64String(encryptor.TransformFinalBlock(data, 0, data.Length));
    }
}

Test inputs:

PHP:

$ /usr/bin/php -r "print(openssl_encrypt('potato', 'AES256', '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 0, '0123456789ABCDEF') . \"\n\");"
wiZGvfABfairN4gKcnqD2Q==
$ /usr/bin/php -r "print(openssl_encrypt('potato', 'AES256', '0123456789abcdefghijklmnopqrstuv', 0, '0123456789ABCDEF') . \"\n\");"
wiZGvfABfairN4gKcnqD2Q==
$ /usr/bin/php -r "print(openssl_encrypt('potato', 'AES256', '0123456789abcdefghijklmnopqrstu', 0, '0123456789ABCDEF') . \"\n\");"
XUkYwaomkIXYstlOQlNoBQ==
$ /usr/bin/php -r "print(openssl_encrypt('potato', 'AES256', '0123456789abcdefghijklmnopqrstuv', 0, '0123456789ABCDE') . \"\n\");"
PHP Warning:  openssl_encrypt(): IV passed is only 15 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0 in Command line code on line 1
ah+yXvQEocgLNES2nh2kXA==

openssl enc

$ openssl enc -aes-256-cbc -K 303132333435363738396162636465666768696a6b6c6d6e6f70717273747576 -in enc.data -a -iv 30313233343536373839414243444546
wiZGvfABfairN4gKcnqD2Q==
$ openssl enc -aes-256-cbc -K 303132333435363738396162636465666768696a6b6c6d6e6f70717273747500 -in enc.data -a -iv 30313233343536373839414243444546
XUkYwaomkIXYstlOQlNoBQ==
$ openssl enc -aes-256-cbc -K 303132333435363738396162636465666768696a6b6c6d6e6f70717273747576 -in enc.data -a -iv 30313233343536373839414243444500
ah+yXvQEocgLNES2nh2kXA==

My code

Console.WriteLine(php_encrypt_aes256("potato", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789ABCDEF"));
Console.WriteLine(php_encrypt_aes256("potato", "0123456789abcdefghijklmnopqrstuv", "0123456789ABCDEF"));
Console.WriteLine(php_encrypt_aes256("potato", "0123456789abcdefghijklmnopqrstu", "0123456789ABCDEF"));
Console.WriteLine(php_encrypt_aes256("potato", "0123456789abcdefghijklmnopqrstuv", "0123456789ABCDE"));

Output:

wiZGvfABfairN4gKcnqD2Q==
wiZGvfABfairN4gKcnqD2Q==
XUkYwaomkIXYstlOQlNoBQ==
ah+yXvQEocgLNES2nh2kXA==
bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Great, this works fine! I get the same results as openssl_encrypt. But in order to achieve the reverse operation, what should I do? – Blazeag Jan 12 '17 at 09:21
  • 1
    Reverse the operations: Convert.FromBase64String the string to data, then use CreateDecryptor instead of CreateEncryptor, and Encoding.ASCII.GetString the bytes back to the original string. Same key/IV handling code. – bartonjs Jan 12 '17 at 15:44
1

I modified it slightly, but I got decryption working in VB.net if anyone needs it.

Private Shared Function php_decrypt_aes256(ByVal php_password As String, ByVal php_iv As String, ByVal php_data As String) As String
    Dim key As Byte() = php_encode(php_password, 32)
    Dim iv As Byte() = php_encode(php_iv, 16)
    Dim buff As Byte() = Encoding.ASCII.GetBytes(php_data)

    Using aes As Aes = Aes.Create()
        Using decryptor As ICryptoTransform = aes.CreateDecryptor(key, iv)
            buff = Convert.FromBase64String(php_data)
            Return ASCIIEncoding.ASCII.GetString(decryptor.TransformFinalBlock(buff, 0, buff.Length))
        End Using
    End Using
End Function
user18954
  • 11
  • 1