可以在 C#/dotNet 5 中解密由 openssl enc -k 加密的 AES 密码保护文件吗?

It is possible to decrypt AES password protected file in C# / dotNet 5 encrypted by openssl enc -k?

我需要解密来自 linux 盒子的文件,密码受 Openssl 和 AES 保护。 加密完成

openssl enc -aes-256-cbc -k <pwd>

目前,我在 Windows 上使用以下脚本正确解密:

"openssl.exe" enc -d -aes-256-cbc -k <pwd> -in <inputFile> -out <output>

到目前为止,我在我的项目中包含了 openssl exe 和 2 dll。

但是,我想摆脱那些依赖关系并直接在 C# 中对其进行解码。

上面 openssl enc -d 的 C# 等价物是什么?

反正可以吗? 我从 https://security.stackexchange.com/questions/20628/where-is-the-salt-on-the-openssl-aes-encryption 了解到 openssl enc 是一种非标准的,并且使用给定密码中的随机盐。

受其他一些类似主题的启发,我目前的方法总是遇到“填充无效”问题,例如这个问题 AES-256-CBC Decrypt Error Stating Padding is invalid and cannot be removed

这个有 10 年历史的帖子 OpenSSL encryption using .NET classes 提出了一个解决方案,检索盐和 IV 更加复杂,但这不再有效。我也遇到了“填充无效”的问题。

(删除了密码的 Rfc2898DeriveBytes 对象的原始代码,openssl 不使用这个 Rfc2898DeriveBytes 东西)。请参阅已接受答案中的工作代码。

来自 10 year old question you linked 的代码实际上在稍作修改后仍然有效。首先请注意,默认情况下 OpenSSL 现在使用 SHA256 作为哈希函数而不是 MD5,我们可以轻松解决这个问题。然后,该答案假设您为 openssl 提供“-base64”选项,并以 base64 格式获得结果,而不是 OpenSSL 默认使用的奇怪格式,但这也很容易修复。只需将目标文件读取为字节,然后从开头剥离 ascii 编码的“SALTED__”字符串:

var input = File.ReadAllBytes(@"your encrypted file");
input = input.Skip(Encoding.ASCII.GetBytes("SALTED__").Length).ToArray();

现在调整它从那里提取盐分和加密数据的方式,并使用 PKCS7 填充,它就会起作用。从上面的答案复制的完整代码以及提到的修复:

public class Protection
{
    public string OpenSSLDecrypt(byte[] encryptedBytesWithSalt, string passphrase)
    {
        // extract salt (first 8 bytes of encrypted)
        byte[] salt = new byte[8];
        byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length];
        Buffer.BlockCopy(encryptedBytesWithSalt, 0, salt, 0, salt.Length);
        Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length, 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];
        var md5 = SHA256.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();
        md5 = null;
    }
    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;

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

            // 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();
                    }
                }
            }
        }
        finally
        {
            // Clear the RijndaelManaged object.
            if (aesAlg != null)
                aesAlg.Clear();
        }

        return plaintext;
    }
}

然后:

var input = File.ReadAllBytes(@"path to your encrypted file");
input = input.Skip(Encoding.ASCII.GetBytes("SALTED__").Length).ToArray();
var decrypted= new Protection().OpenSSLDecrypt(input, "123123");

如果解密非字符串数据,将DecryptStringFromBytesAes改成这样:

static byte[] 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;

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

        // 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 (var output = new MemoryStream()) {
                    csDecrypt.CopyTo(output);
                    return output.ToArray();
                }
            }
        }
    }
    finally {
        // Clear the RijndaelManaged object.
        if (aesAlg != null)
            aesAlg.Clear();
    }
}