使用PHP openssl_encrypt加密的C#中的解密字符串 [英] Decrypt string in C# that was encrypted with PHP openssl_encrypt

查看:203
本文介绍了使用PHP openssl_encrypt加密的C#中的解密字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个客户使用以下代码加密PHP中的字符串:

  $ password ='Ty63rs4aVqcnh2vUqRJTbNT26caRZJ'; 
$ method ='AES-256-CBC';
texteACrypter ='你是否认为可以,或者你认为你不能 - 你是对的。 - 亨利·福特

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

导致此加密输出: MzVWX4tH4yZWc / w75zUagUMEsP34ywSYISsIIS9fj0W3Q / lR0hBrHmdvMOt106PlKhN / 1zXFBPbyKmI6nWC5BN54GuGFSjkxfuansJkfoi0 =



当我尝试解密C#中的字符串时,它会给我一堆垃圾:Z o }'*2 I4y J6S
xz {9 ^ ED fF?
} >>>>>>>>>>>>>>>>>>>>> ,使用AesManaged代替RijndaelManaged,更改keysize,使用不同的键等。所有这些都会导致不同的垃圾串或各种异常。我必须在这里遗漏一些非常基本的东西,但我不知道在这一点上还有什么可以尝试的。



这是我的解密代码(我无耻地从另一个stackoverflow问题:仅使用.NET类的openssl

  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(是的);
Console.ReadKey();
}

public static string OpenSSLDecrypt(string encrypted,string passphrase)
{
// base 64 decode
byte [] encryptedBytesWithSalt = Convert.FromBase64String (加密的);
//提取盐(加密的前8个字节)
byte [] salt = new byte [8];
byte [] encryptedBytes = new byte [encryptedBytesWithSalt.Length - salt.Length - 8];
BufferBlockCopy(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键和iv
列表< byte> concatenatedHash = new List< byte>(48);

byte [] password = Encoding.UTF8.GetBytes(passphrase);
byte [] currentHash = new byte [0];
MD5 md5 = MD5.Create();
bool enoughBytesForKey = false;
//参见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];

BufferBlockCopy(currentHash,0,preHash,0,currentHash.Length);
Buffer.BlockCopy(password,0,preHash,currentHash.Length,password.Length);
BufferBlockCopy(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 =新字节[16];
concatenatedHashes.CopyTo(0,key,0,32);
concatenatedHashes.CopyTo(32,iv,0,16);

md5.Clear();
}

static string DecryptStringFromBytesAes(byte [] cipherText,byte [] key,byte [] iv)
{
//检查参数。
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);

//声明RijndaelManaged对象
//用于解密数据。
RijndaelManaged aesAlg = null;

//声明用于保存
//解密文本的字符串。
string plaintext;

//使用指定的键和IV创建一个RijndaelManaged对象
//。
aesAlg = new RijndaelManaged {Mode = CipherMode.CBC,Padding = PaddingMode.None,KeySize = 256,BlockSize = 128,Key = key,IV = iv};

//创建一个解密器来执行流转换。
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key,aesAlg.IV);
//创建用于解密的流。
使用(MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using(CryptoStream csDecrypt = new CryptoStream(msDecrypt,decryptor,CryptoStreamMode.Read))
{
使用(StreamReader srDecrypt = new StreamReader(csDecrypt))
{
//从解密流
//读取解密的字节,并将它们放在字符串中。
plaintext = srDecrypt.ReadToEnd();
srDecrypt.Close();
}
}
}

返回明文;
}
}


解决方案

这是很有趣的工作,需要跳入PHP源代码与一些有趣的结果。首先,PHP甚至不使用密钥派生算法只需要使用密码短片的字节,并将其以零的形式填充到所需的长度。这意味着整个DeriveKeyAndIV方法是不必要的。



由于上述意味着正在使用的IV是包含零的16个长度字节数组。



最后你的代码错误的唯一其他的事情是,你复制的源使用一个盐在执行加密,然后必须被删除,PHP也没有这样做,所以删除盐字节是不正确的。 >

所有这一切放在一起意味着您需要将 OpenSSLDecrypt 方法更改为此。

  public static string OpenSSLDecrypt(string encrypted,string passphrase)
{
//获取关键字节(不确定如果UTF8或ASCII应该在这里使用没关系,如果没有扩展字符在密码短语)
var key = Encoding.UTF8.GetBytes(passphrase);

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

//设置一个空的iv
var iv = new byte [16];

//获取加密数据并解密
byte [] encryptedBytes = Convert.FromBase64String(encrypted);
return DecryptStringFromBytesAes(encryptedBytes,key,iv);
}

最后,结果字符串最后有一些额外的字符,即一组的3个 ETX char ,但这些应该很简单足够过滤掉。我实际上不知道这些来自哪里。



感谢@neubert指出填充是标准PKCS填充的一部分,如果你想要框架删除这个只是在实例化 RijndaelManaged 对象时指定为填充模式。

  new RijndaelManaged {Padding = PaddingMode.PKCS7}; 


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;
    }
}

解决方案

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 };

这篇关于使用PHP openssl_encrypt加密的C#中的解密字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆