c# - System.Security.Cryptography.CryptographicException:“参数不正确”/Aes 256 Gcm 解密
问题描述
我这两天遇到同样的问题:System.Security.Cryptography.CryptographicException:参数不正确
在 System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope 范围)
我将这个项目用于我的代码:https ://github.com/jabiel/BrowserPass/tree/master/BrowserPass
在那个项目中,错误应该在 ChromePassReader.cs 中,我猜是第 42 行。
我的电脑上一切正常,我的一个朋友让我在他的电脑上试了试,但对他不起作用。我也在其他电脑上尝试过,但没有成功。
最合适的答案(由 Topaco 提出):我使用的解密类型对于使用 DPAPI 加密的数据很有用。从 v80.0 或更高版本的 Chrome 版本开始,密码数据使用 Aes 256 Gcm 加密,因此:
更新
我尝试编写代码来解密 AesGcm256 密码数据。我从位于 Google Chrome 文件夹中的数据库中获取用户数据,称为登录数据。然后我应该使用 AesGcm256 Decryption 解密我从那里获得的密码,但我做不到。这是我的尝试:
我在哪里获得 URL、用户名、密码:
using System;
using System.Collections.Generic;
using System.Net;
using System.Data.SQLite;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.IO;
using System.Security.Cryptography;
using System.Diagnostics;
using SalsaClient.Algorithm;
namespace SalsaClient.CDS
{
class ChromePassReader : IPassReader
{
public string BrowserName { get { return "Chrome"; } }
private const string LOGIN_DATA_PATH = "\\..\\Local\\Google\\Chrome\\User Data\\Default\\Login Data";
public IEnumerable<CredentialModel> ReadPasswords()
{
var result = new List<CredentialModel>();
var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);// APPDATA
var p = Path.GetFullPath(appdata + LOGIN_DATA_PATH);
if (File.Exists(p))
{
Process[] chromeInstances = Process.GetProcessesByName("chrome");
foreach (Process proc in chromeInstances)
proc.Kill();
using (var conn = new SQLiteConnection($"Data Source={p};"))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT action_url, username_value, password_value FROM logins";
using (var reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
var pass = AesGcm256.decrypt(GetBytes(reader, 2)); //encrypted data
result.Add(new CredentialModel()
{
Url = reader.GetString(0),
Username = reader.GetString(1),
Password = pass
});
}
}
}
}
conn.Close();
}
}
else
{
throw new FileNotFoundException("Cannot find chrome logins file");
}
return result;
}
private byte[] GetBytes(SQLiteDataReader reader, int columnIndex)
{
const int CHUNK_SIZE = 2 * 1024;
byte[] buffer = new byte[CHUNK_SIZE];
long bytesRead;
long fieldOffset = 0;
using (MemoryStream stream = new MemoryStream())
{
while ((bytesRead = reader.GetBytes(columnIndex, fieldOffset, buffer, 0, buffer.Length)) > 0)
{
stream.Write(buffer, 0, (int)bytesRead);
fieldOffset += bytesRead;
}
return stream.ToArray();
}
}
}
}
算法:
using Newtonsoft.Json;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace SalsaClient.Algorithm
{
class AesGcm256
{
public static string GetKey()
{
string sR = string.Empty;
var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);// APPDATA
var path = Path.GetFullPath(appdata + "\\..\\Local\\Google\\Chrome\\User Data\\Local State");
string v = File.ReadAllText(path);
dynamic json = JsonConvert.DeserializeObject(v);
string key = json.os_crypt.encrypted_key;
byte[] src = Convert.FromBase64String(key);
byte[] encryptedKey = src.Skip(5).ToArray();
byte[] data = Convert.FromBase64String(encodedString);
string decodedString = Encoding.UTF8.GetString(data);
byte[] decryptedKey = ProtectedData.Unprotect(encryptedKey, null, DataProtectionScope.CurrentUser);
}
public static string decrypt(string EncryptedText, byte[] key, byte[] iv)
{
string sR = string.Empty;
try
{
byte[] encryptedBytes = Convert.FromBase64String(EncryptedText);
GcmBlockCipher cipher = new GcmBlockCipher(new AesFastEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, iv, null);
cipher.Init(false, parameters);
byte[] plainBytes = new byte[cipher.GetOutputSize(encryptedBytes.Length)];
Int32 retLen = cipher.ProcessBytes(encryptedBytes, 0, encryptedBytes.Length, plainBytes, 0);
cipher.DoFinal(plainBytes, retLen);
sR = Encoding.UTF8.GetString(plainBytes).TrimEnd("\r\n\0".ToCharArray());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
return sR;
}
}
}
解决方案
不幸的是,您没有描述究竟是什么不起作用。但是,大多数代码似乎都正确实现了。在某些部分,需要进行一些小的更改和添加:
AES 密钥的 DPAPI 解密发生在
SalsaClient.Algorithm.AesGcm256.GetKey()
. 这里缺少返回语句。返回值的类型为string
。由于密钥通常由任意二进制数据组成,因此如果要以字符串形式返回数据,则应使用适当的编码(如 Base64 或十六进制)。或者,密钥可以返回为byte[]
,如下面的改编:public static byte[] GetKey() { string sR = string.Empty; var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);// APPDATA var path = Path.GetFullPath(appdata + "\\..\\Local\\Google\\Chrome\\User Data\\Local State"); string v = File.ReadAllText(path); dynamic json = JsonConvert.DeserializeObject(v); string key = json.os_crypt.encrypted_key; byte[] src = Convert.FromBase64String(key); byte[] encryptedKey = src.Skip(5).ToArray(); byte[] decryptedKey = ProtectedData.Unprotect(encryptedKey, null, DataProtectionScope.CurrentUser); return decryptedKey; }
接下来,
SalsaClient.CDSChromePassReader#ReadPasswords()
必须稍作修改:- 必须确定 AES 密钥。
- 随机数和实际密文必须从从数据库读取的数据中确定。
- 必须执行密码的解密(使用 AES 密钥、随机数和实际密文)。
全面的:... byte[] key = AesGcm256.GetKey(); while (reader.Read()) { byte[] encryptedData = GetBytes(reader, 2); byte[] nonce, ciphertextTag; AesGcm256.prepare(encryptedData, out nonce, out ciphertextTag); string pass = AesGcm256.decrypt(ciphertextTag, key, nonce); ... } ...
在新方法中进行nonce和实际密文的确定
SalsaClient.Algorithm.AesGcm256.prepare()
。此时需要链接文章中的信息: 与密码相关的数据由以下部分组成:- 前三个字节是 ASCII 编码
v10 (0x763130)
。 - 接下来的 12 个字节是 nonce。
- 接下来是实际的密文。
- 最后 16 个字节是 GCM 模式的认证标签。
实际的密文和认证标签不需要分开,因为 AES-GCM 的 C#/BC 实现将这两个部分一起处理:
public static void prepare(byte[] encryptedData, out byte[] nonce, out byte[] ciphertextTag) { nonce = new byte[12]; ciphertextTag = new byte[encryptedData.Length - 3 - nonce.Length]; System.Array.Copy(encryptedData, 3, nonce, 0, nonce.Length); System.Array.Copy(encryptedData, 3 + nonce.Length, ciphertextTag, 0, ciphertextTag.Length); }
- 前三个字节是 ASCII 编码
由于加密数据(实际密文和身份验证标签)是二进制格式,因此将这些数据传递给
SalsaClient.Algorithm.AesGcm256.decrypt()
asbyte[]
而不是 as更方便string
:public static string decrypt(byte[] encryptedBytes, byte[] key, byte[] iv) { string sR = string.Empty; try { GcmBlockCipher cipher = new GcmBlockCipher(new AesFastEngine()); ...
解密的数据在最后发布的代码中被修剪(换行符和 0 值)。其实这不应该是必要的。但也许你对此有特殊的原因。
至此,所有必要的更改都已完成。然后通过在
Main
方法中执行来完成密码的解密:SalsaClient.CDS.ChromePassReader chromePassReader = new SalsaClient.CDS.ChromePassReader(); IEnumerable<CredentialModel> credentialList = chromePassReader.ReadPasswords();
使用此代码,我可以解密我机器上的 Chrome 密码(其中我的数据库专门包含 AES-GCM 加密密码)。但是请注意,并非所有密码都必须经过 AES-GCM 加密。旧密码(从 v80 之前开始)仍然可以进行 DPAPI 加密。当然,它们不能以上述方式解密,而必须进行 DPAPI 解密。如上所述,使用 AES-GCM 加密的密码可以通过以 ASCII 编码开头的事实来识别v10 (0x763130)
。也许您需要在代码中添加相应的大小写区别(至少如果 DPAPI 加密密码仍存储在数据库中)。
推荐阅读
- php - 如何根据数组的长度循环数组
- python - 设置简单的 Tensorflow 线性回归返回 NaN 值?
- rest - 使用耗时的 HTTP 请求接近调度程序的设计
- ios - cordova - Xcode 收到错误命令 Ld 失败,退出代码为非零
- ruby-on-rails - 如何在页面代码中填充下拉选择
- python-3.x - 将环境变量设置为 /urs/local - 底图安装
- node.js - 如何使用 sinon 存根 require('firebase-admin').auth().getUserByEmail()?
- android - 将标题添加到列表视图
- python - 我的常规句子有什么问题?
- bash - 保护makefile变量中的shell特殊字符