1

我正在尝试使用 C# 控制台应用程序读取 cookie 的加密值。

我的 cookie 阅读器课程

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using Microsoft.EntityFrameworkCore;

namespace ConsoleApp1.Models
{
    public class ChromeCookieReader
    {
        public IEnumerable<Tuple<string, string>> ReadCookies(string hostName)
        {
            if (hostName == null) throw new ArgumentNullException("hostName");

            using var context = new ChromeCookieDbContext();

            var cookies = context
                .Cookies
                .Where(c => c.HostKey.Equals("localhost"))
                .AsNoTracking();

            foreach (var cookie in cookies)
            {
                var decodedData = ProtectedData
                    .Unprotect(cookie.EncryptedValue,
                        null, 
                        DataProtectionScope.CurrentUser);

                var decodedValue = Encoding.UTF8.GetString(decodedData);

                yield return Tuple.Create(cookie.Name, decodedValue);
            }
        }
    }
}

我的 EF DbContext

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
using Microsoft.EntityFrameworkCore;

namespace ConsoleApp1.Models
{
    public class Cookie
    {
        [Column("host_key")]
        public string HostKey { get; set; }

        [Column("name")] 
        public string Name { get; set; }

        [Column("encrypted_value")]
        public byte[] EncryptedValue { get; set; }
    }

    public class ChromeCookieDbContext : DbContext
    {
        public DbSet<Cookie> Cookies { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            // var dbPath = Environment.GetFolderPath(
            //    Environment.SpecialFolder.LocalApplicationData) 
            //             + @"\Google\Chrome\User Data\Default\Cookies";

            var dbPath = Environment.GetFolderPath(
                             Environment.SpecialFolder.LocalApplicationData)
                         + @"\BraveSoftware\Brave-Browser\User Data\Default\Cookies";

            if (!System.IO.File.Exists(dbPath)) throw new System.IO.FileNotFoundException("Cant find cookie store", dbPath); // race condition, but i'll risk it

            var connectionString = "Data Source=" + dbPath + ";Mode=ReadOnly;";

            optionsBuilder
                .UseSqlite(connectionString);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Cookie>().ToTable("cookies").HasNoKey();
        }
    }
}

我尝试的解决方案受到Chrome 中的加密 cookie 的启发,但是尽管 Brave 浏览器基于 Chromium,但它看起来并不一样。相反,Windows 数据保护 API 会引发异常。

Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException
  HResult=0x0000000D
  Message=The data is invalid.
  Source=System.Security.Cryptography.ProtectedData
  StackTrace:
   at System.Security.Cryptography.ProtectedData.ProtectOrUnprotect(Byte[] inputData, Byte[] optionalEntropy, DataProtectionScope scope, Boolean protect)
   at System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope)
   at ConsoleApp1.Models.ChromeCookieReader.<ReadCookies>d__0.MoveNext()

其他已知问题:如果 Brave 已打开,EF Core 会“吓坏”SQLite 数据库被锁定并且不会读取任何内容。

4

1 回答 1

1

在 Chromium 80 及更高版本中,Google 修改了 cookie 的加密方式,为用户提供额外的安全性。您不能再将 cookie 直接传递给 Windows DPAPI 以进行解密。而是 Chrome 的本地状态存储使用 Windows DPAI 解密的加密密钥,您必须使用该密钥来解密 cookie。我在应得的地方给予信任,因为我自己没有发现这一点,并使用https://stackoverflow.com/a/60611673/6481581上的答案中的信息来解决我的问题。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;

namespace BraveBrowserCookieReaderDemo
{
    public class BraveCookieReader
    {
        public IEnumerable<Tuple<string, string>> ReadCookies(string hostName)
        {
            if (hostName == null) throw new ArgumentNullException("hostName");

            using var context = new BraveCookieDbContext();

            var cookies = context
                .Cookies
                .Where(c => c.HostKey.Equals(hostName))
                .AsNoTracking();

            // Big thanks to https://stackoverflow.com/a/60611673/6481581 for answering how Chrome 80 and up changed the way cookies are encrypted.

            string encKey = File.ReadAllText(System.Environment.GetEnvironmentVariable("LOCALAPPDATA") + @"\BraveSoftware\Brave-Browser\User Data\Local State");
            encKey = JObject.Parse(encKey)["os_crypt"]["encrypted_key"].ToString();
            var decodedKey = System.Security.Cryptography.ProtectedData.Unprotect(Convert.FromBase64String(encKey).Skip(5).ToArray(), null, System.Security.Cryptography.DataProtectionScope.LocalMachine);

            foreach (var cookie in cookies)
            {

                var data = cookie.EncryptedValue;

                var decodedValue = _decryptWithKey(data, decodedKey, 3);


                yield return Tuple.Create(cookie.Name, decodedValue);
            }
        }


        private string _decryptWithKey(byte[] message, byte[] key, int nonSecretPayloadLength)
        {
            const int KEY_BIT_SIZE = 256;
            const int MAC_BIT_SIZE = 128;
            const int NONCE_BIT_SIZE = 96;

            if (key == null || key.Length != KEY_BIT_SIZE / 8)
                throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");
            if (message == null || message.Length == 0)
                throw new ArgumentException("Message required!", "message");

            using (var cipherStream = new MemoryStream(message))
            using (var cipherReader = new BinaryReader(cipherStream))
            {
                var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
                var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
                var cipher = new GcmBlockCipher(new AesEngine());
                var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce);
                cipher.Init(false, parameters);
                var cipherText = cipherReader.ReadBytes(message.Length);
                var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
                try
                {
                    var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
                    cipher.DoFinal(plainText, len);
                }
                catch (InvalidCipherTextException)
                {
                    return null;
                }
                return Encoding.Default.GetString(plainText);
            }
        }
    }
}
于 2020-04-18T19:43:26.603 回答