解压缩使用PHP openssl_encrypt加密的C#中的字符串

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

$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); 

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

当我尝试在C#中解密该字符串时,它给了我一堆像这样的垃圾: Z o }'*2 I4y J6S xz {9^ ED fF } گs ) Q i $)

我已经尝试使用AesManaged而不是RijndaelManaged更改填充,更改密钥大小,使用不同的密钥等。所有这些都会导致不同的垃圾字符串或各种exception。 我必须遗漏一些非常基本的东西,但我不确定此时还有什么可以尝试的。

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

 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 concatenatedHashes = new List(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; } } 

好吧,这很有趣,需要跳转到PHP源代码并获得一些有趣的结果。 首先,PHP甚至不使用密钥派生算法,它只需要密码短语的字节,并用零填充它到所需的长度。 这意味着不需要整个DeriveKeyAndIV方法。

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

最后,你的代码唯一的另一个错误是你复制它的源在他们的加密实现中使用salt然后必须被删除,PHP也不是你这样做,所以删除salt字节是不正确的。

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

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

最后,结果字符串最后有一些额外的字符,即一组3个ETX字符,但这些应该很容易过滤掉。 我实际上无法弄清楚这些来自哪里。

感谢@neubert指出填充是标准PKCS填充的一部分,如果您希望框架删除它,只需在实例化RijndaelManaged对象时指定为填充模式。

 new RijndaelManaged { Padding = PaddingMode.PKCS7 };