2

I got an issue with decrypting cookies that are stored in Chrome's sqlite db under encrypted_value.

The extraction from the sqlite db works just fine:


// filePath = absolute cookies.sqlite path
// query = "SELECT creation_utc, host_key, name, encrypted_value, path, expires_utc from cookies WHERE host_key like \"%<target_site>%\"

using (var connection = new SqliteConnection($"Data Source={filePath}"))
{
    connection.Open();

    var command = connection.CreateCommand();
    command.CommandText = query;

    using (var reader = command.ExecuteReader())
    {
          while (reader.Read())
          {
               var creationTime = reader.GetString(0);
               var host = reader.GetString(1);
               var name = reader.GetString(2);
               var value = reader.GetString(3);
               var path = reader.GetString(4);
               var expiryTime = reader.GetString(5);
 
               /* here the below code is placed */
          }
    }

}

however on decrypting the values I get a mismatch between the auth tag and the expected auth tag. Im running under windows.

The below code is annoted with comments to show my reasoning

// get encrypted blob from row
byte[] encryptedData = new byte[reader.GetBytes(3, 0, null, 0, int.MaxValue) - 1]; // 3 = encrypted_value column
reader.GetBytes(3, 0, encryptedData, 0, encryptedData.Length);

// Get encrypted key from local state file:
string encKey = File.ReadAllText(filePath + @"/../../../Local State");
encKey = JObject.Parse(encKey)["os_crypt"]["encrypted_key"].ToString();

// The encrypted key starts with the ASCII encoding of DPAPI (i.e. 0x4450415049) and is Base64 encoded,
// i.e. the key must first be Base64 decoded and the first 5 bytes must be removed.
// Afterwards a decryption with win32crypt.CryptUnprotectData is possible.
var decryptedKey = System.Security.Cryptography.ProtectedData.Unprotect(Convert.FromBase64String(encKey).Skip(5).ToArray(), null, System.Security.Cryptography.DataProtectionScope.LocalMachine);

// try decryption
try
{
    // The encrypted data start with the ASCII encoding of v10 (i.e. 0x763130) ...
    if (value.StartsWith("v10"))
    {
        using (var aes = new System.Security.Cryptography.AesGcm(decryptedKey))
        {
            // ... followed by the 12 bytes nonce,
            var nonce = encryptedData[3..15];
            // the actual ciphertext 
            var encData = encryptedData[15..(encryptedData.Length - 16)];
            // and finally the 16 bytes authentication tag.
            var auth_tag = encryptedData[(encryptedData.Length - 16)..(encryptedData.Length)];

            byte[] plaintextBytes = new byte[encData.Length];

            aes.Decrypt(nonce, encData, auth_tag, plaintextBytes);
            value = Encoding.UTF8.GetString(plaintextBytes);
        }
    }
    else
    {
        // TODO
        throw new Exception("[!] Cookie encrypted with DPAPI");
    }
                                    
}
catch (Exception e)
{
    Console.WriteLine(e);
    Console.WriteLine($"[*] Could not decode cookie with encrypted value {value}");
}

The exception I am getting is

System.Security.Cryptography.CryptographicException: The computed authentication tag did not match the input authentication tag.
at System.Security.Cryptography.AesAEAD.Decrypt(SafeAlgorithmHandle algorithm, SafeKeyHandle keyHandle, ReadOnlySpan`1 nonce, ReadOnlySpan`1 associatedData, ReadOnlySpan`1 ciphertext, ReadOnlySpan`1 tag, Span`1 plaintext, Boolean clearPlaintextOnFailure)
at System.Security.Cryptography.AesGcm.Decrypt(Byte[] nonce, Byte[] ciphertext, Byte[] tag, Byte[] plaintext, Byte[] associatedData)
at <REDACTED>:line 123                                  

I am fairly certain that I got the parsing of the nonce, ciphertext and auth_tag right, but apparently not? I am not sure where this issue is coming from.

Also, this is running under the same user/on the same browser that saved the cookies.

Thanks in advance.

sorh
  • 125
  • 1
  • 11

1 Answers1

4

Found myself with the same problem and here is the solution:

using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Data.Sqlite;

public class ChromeCookieRetriever
{
    class LocalStateDto
    {
        [JsonPropertyName("os_crypt")]
        public OsCrypt OsCrypt { get; set; }
    }

    class OsCrypt
    {
        [JsonPropertyName("encrypted_key")]
        public string EncryptedKey { get; set; }
    }

    private const string CookiesFileName = @"Default\Network\Cookies";
    private const string LocalStateFileName = "Local State";

    public ChromeCookieRetriever()
    {
    }

    public ICollection<Cookie> GetCookies(string baseFolder)
    {
        byte[] key = GetKey(baseFolder);
        ICollection<Cookie> cookies = ReadFromDb(baseFolder, key);
        return cookies;
    }
    
    private byte[] GetKey(string baseFolder)
    {
        string file = Path.Combine(baseFolder, LocalStateFileName);
        string localStateContent = File.ReadAllText(file);
        LocalStateDto localState = JsonSerializer.Deserialize<LocalStateDto>(localStateContent);
        string encryptedKey = localState?.OsCrypt?.EncryptedKey;

        var keyWithPrefix = Convert.FromBase64String(encryptedKey);
        var key = keyWithPrefix[5..];
        var masterKey = ProtectedData.Unprotect(key, null, DataProtectionScope.CurrentUser);
        return masterKey;
    }
    
    private ICollection<Cookie> ReadFromDb(string baseFolder, byte[] key)
    {
        ICollection<Cookie> result = new List<Cookie>();
        string dbFileName = Path.Combine(baseFolder, CookiesFileName);
        using (SqliteConnection connection = new SqliteConnection($"Data Source={dbFileName}"))
        {
            connection.Open();

            long expireTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            SqliteCommand command = connection.CreateCommand();
            command.CommandText =
                @"select   creation_utc,
                           host_key,
                           top_frame_site_key,
                           name,
                           value,
                           encrypted_value,
                           path,
                           expires_utc,
                           is_secure,
                           is_httponly,
                           last_access_utc,
                           has_expires,
                           is_persistent,
                           priority,
                           samesite,
                           source_scheme,
                           source_port,
                           is_same_party
                    from cookies
                    WHERE has_expires = 0 or (has_expires = 1 and expires_utc > $expireTime)
                    ";
            command.Parameters.AddWithValue("$expireTime", expireTime);
            using (SqliteDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    string name = reader["name"].ToString();
                    string path = reader["path"].ToString();
                    string domain = reader["host_key"].ToString();
                    byte[] encrypted_value = (byte[])reader["encrypted_value"];


                    string value = DecryptCookie(key, encrypted_value);

                    Cookie cookie = new Cookie(name, value, path, domain);
                    result.Add(cookie);
                }
            }

            return result;
        }
    }

    private string DecryptCookie(byte[] masterKey, byte[] cookie)
    {
        byte[] nonce = cookie[3..15];
        byte[] ciphertext = cookie[15..(cookie.Length - 16)];
        byte[] tag = cookie[(cookie.Length - 16)..(cookie.Length)];

        byte[] resultBytes = new byte[ciphertext.Length];
        
        using AesGcm aesGcm = new AesGcm(masterKey);
        aesGcm.Decrypt(nonce, ciphertext, tag, resultBytes);
        string cookieValue = Encoding.UTF8.GetString(resultBytes);
        return cookieValue;
    }
}

Nuget's used:

<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.4" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="6.0.0" />

UPDATE 25.08.23:

Working example repository

WARNING: It is possible to decrypt only cookies files what was created on same machine and same windows user as it runs this app

The key for cookies file is stored with current user protection scope

ProtectedData.Unprotect(key, null, DataProtectionScope.CurrentUser);

Sources:

Georgy Tarasov
  • 1,534
  • 9
  • 11