我需要解密来自 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 内容)。请参阅已接受答案中的工作代码。
您链接的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();
}
}
受上面回复的启发,以及我对他们的评论,这是我在 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;
}
}
上面的代码仍然会导致 System.Security.Cryptography.CryptographyException: “填充无效且无法删除。”异常