'Decrypt Cookies (encrypted_value) from Chrome/Chromium 80+ in C# - Issue with Auth Tag
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.
Solution 1:[1]
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" />
Sources:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |