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

问题描述 投票:0回答:3

我需要解密来自 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解密错误指出填充无效且无法删除

这个已有 10 年历史的线程使用 .NET 类的 OpenSSL 加密提出了一个解决方案,甚至更复杂地检索盐和 IV,但这不再起作用。我还遇到“填充无效”问题。

(带有用于 pwd 的 Rfc2898DeriveBytes 对象的原始代码已删除,openssl 不使用此 Rfc2898DeriveBytes 内容)。请参阅已接受答案中的工作代码。

c# .net-core openssl aes
3个回答
3
投票

您链接的10年前的问题中的代码实际上只需稍作修改即可工作。首先请注意,默认情况下 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();
    }
}

0
投票

受上面回复的启发,以及我对他们的评论,这是我在 VS 2022 中的 .NET 6 项目中使用的类。

public static class OpenSslUtils
{
    public static byte[] OpenSSLDecrypt(byte[] encryptedBytesWithSalt, string passphrase)
    {
        // remove the SALTED prefix
        byte[] input = encryptedBytesWithSalt.Skip(Encoding.ASCII.GetBytes("Salted__").Length).ToArray();
        // extract salt (first 8 bytes of encrypted)
        byte[] salt = new byte[8];
        byte[] encryptedBytes = new byte[input.Length - salt.Length];
        Buffer.BlockCopy(input, 0, salt, 0, salt.Length);
        Buffer.BlockCopy(input, salt.Length, encryptedBytes, 0, encryptedBytes.Length);
        // get key and iv
        DeriveKeyAndIV(passphrase, salt, out byte[] key, out byte[] iv);
        return DecryptFromBytesAes(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(48);

        byte[] password = Encoding.UTF8.GetBytes(passphrase);
        byte[] currentHash = Array.Empty<byte>();
        var hash = 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 = hash.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);

        hash.Dispose();
    }
    private static byte[] DecryptFromBytesAes(byte[] cipherText, byte[] key, byte[] iv)
    {
        // Check arguments.
        if (cipherText == null || cipherText.Length <= 0)
            throw new ArgumentNullException(nameof(cipherText));
        if (key == null || key.Length <= 0)
            throw new ArgumentNullException(nameof(key));
        if (iv == null || iv.Length <= 0)
            throw new ArgumentNullException(nameof(iv));

        // Declare the Aes object used to decrypt the data.
        Aes? aesAlg = null;

        // Declare the byte[] used to hold the decrypted text.
        byte[]? decryptedOutput = null;

        try
        {
            // Create an AES object
            // with the specified key and IV.
            aesAlg = Aes.Create();
            aesAlg.Mode = CipherMode.CBC;
            aesAlg.KeySize = 256;
            aesAlg.BlockSize = 128;
            aesAlg.Key = key;
            aesAlg.IV = iv;
            aesAlg.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(cipherText);
            using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
            using MemoryStream output = new();
            csDecrypt.CopyTo(output);
            decryptedOutput = output.ToArray();
        }
        finally
        {
            // Clear the object.
            if (aesAlg != null)
            {
                aesAlg.Dispose();
            }
        }

        return decryptedOutput;
    }
}

0
投票

上面的代码仍然会导致 System.Security.Cryptography.CryptographyException: “填充无效且无法删除。”异常

© www.soinside.com 2019 - 2024. All rights reserved.