У меня есть клиент, который шифрует строку в 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, изменив ключ, используя другой ключ и т. Д. Все результаты приводят к разным мусорным строкам или различным исключениям. Я должен упустить что-то действительно основное, но я не уверен, что еще попробовать в этот момент.
Вот мой код дешифрования (который я бесстыдно скопировал из другого вопроса 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<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; } }
Ну, это было весело для разработки и потребовалось перейти в исходный код PHP с некоторыми интересными результатами. Во-первых, PHP даже не использует алгоритм деривации ключей, он просто берет байты кодовой фразы и заполняет нуль нужной длиной. Это означает, что весь метод DeriveKeyAndIV не нужен.
Из-за вышеизложенного это означает, что IV, который используется, представляет собой массив длиной 16 байт, содержащий нули.
И, наконец, единственное, что не так с вашим кодом, это то, что источник, который вы скопировали из него, использовал соль в их реализации шифрования, который затем должен был быть удален, PHP или вы делаете это, поэтому удаление солевых байтов неверно.
Таким образом, все это означает, что вам нужно изменить метод 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 };