Estoy tratando de obtener sesión desde mi navegador Chrome. Puedo ver 2 archivos de cookies en Herramientas para desarrolladores. pero esto es inconveniente para el usuario para obtener valores de cookies del navegador, me gustaría hacerlo en código. así que uso este código para obtener el perfil predeterminado de Chrome cookie sqlite DB:

string local = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string path = @"Google\Chrome\User Data\Default\Cookies";

path = Path.Combine(local, path);

A continuación, creo una conexión SQLite y solicito

var cmd = new SQLiteCommand("SELECT encrypted_value, name FROM cookies WHERE host_key = 'my_host_ip'", con);

Entonces leo los resultados

byte[] encryptedCookie = (byte[])r.GetValue(r.GetOrdinal("encrypted_value"));

E intenta descifrarlo:

var decodedData = ProtectedData.Unprotect(encryptedCookie, null, DataProtectionScope.CurrentUser);
var plainText = Encoding.ASCII.GetString(decodedData);

Y aquí tengo una excepción

System.Security.Cryptography.CryptographicException

Sé que DEBO descifrar el contenido de las cookies en la misma cuenta de usuario con la que se inició el navegador (en la misma máquina), y para ello se utiliza el parámetro DataProtectionScope.CurrentUser

Veo 63 bytes en el depurador (en la matriz encryptedCookie), también veo estos bytes en el campo BLOB de SQLite DB. pero el método Unprotect arroja el error System.Security.Cryptography.CryptographicException: Invalid data.

Mi código funciona bien en 5 PC diferentes en mi oficina (win10, win7), pero no funcionó en mi PC de desarrollador (win10, vs2019).

Creo que el problema está en mi configuración de Windows o en otro lugar, no en mi código. Entonces, ¿qué estoy haciendo mal?

Nota interesante: encontré el script de PowerShell que hace lo mismo (a través de Add-Type -AssemblyName System.Security): obtener la cookie y descifrarla. este script también funciona bien en 5 PC de oficina, pero no funcionó en mi PC.

Mi instalación de Windows es nueva, no tengo software AV. nos conectamos al mismo dominio corporativo y tenemos la misma configuración de seguridad.

UPD 1 un pequeño experimento

  1. obtener el valor de la cookie del navegador Chrome (32 caracteres, JSESSIONID)
  2. cree una aplicación simple que proteja este valor con el alcance de protección CurrentUser. ahora tengo una matriz de 178 bytes (resultado # 1)
  3. vea la base de datos de cookies de Chrome con a) https://sqliteonline.com/ yb) la aplicación de escritorio DataBase.Net. Estos dos métodos me dan el mismo resultado: solo 63 bytes de datos de cookies cifrados (resultado # 2). También puedo obtener el mismo resultado con mi aplicación C # usando System.Data.SQLite

Entonces, los resultados no son iguales en longitud o contenido ¡resultado # 1! = resultado # 2

Parece el valor de cookie de Chrome protegido por un alcance diferente (¿tal vez una cuenta de administrador?), pero veo el nombre de mi cuenta de usuario en el Administrador de tareas en el proceso de Chrome

PD yo uso .net 4.7.2

UPD 2 Encontré este método en fuentes de cromo

bool OSCrypt::DecryptString(const std::string& ciphertext,
                            std::string* plaintext) {
  if (!base::StartsWith(ciphertext, kEncryptionVersionPrefix,
                        base::CompareCase::SENSITIVE))
    return DecryptStringWithDPAPI(ciphertext, plaintext);

  crypto::Aead aead(crypto::Aead::AES_256_GCM);

  auto key = GetEncryptionKeyInternal();
  aead.Init(&key);

  // Obtain the nonce.
  std::string nonce =
      ciphertext.substr(sizeof(kEncryptionVersionPrefix) - 1, kNonceLength);
  // Strip off the versioning prefix before decrypting.
  std::string raw_ciphertext =
      ciphertext.substr(kNonceLength + (sizeof(kEncryptionVersionPrefix) - 1));

  return aead.Open(raw_ciphertext, nonce, std::string(), plaintext);
}

Entonces DPAPI solo se usa cuando BLOB NO comienza con caracteres v10. pero mis BLOB de cookies comienzan con caracteres v10 y, según el código, se usa otro algoritmo criptográfico, pero no entiendo POR QUÉ.

1
cerberus 14 feb. 2020 a las 19:48

2 respuestas

La mejor respuesta

Finalmente lo resolví. Según las fuentes de Chromium, se utilizan dos métodos para descifrar el valor de la cookie.

  1. si el valor de la cookie comienza con v10 caracteres, usamos AES_256_GCM
  2. de lo contrario, se utiliza DPAPI

Para el primer método necesitamos key y nonce. la clave se encuentra en los archivos de Google Chrome y nonce se encuentra en el valor de cookie cifrado.

No me queda claro: qué determina qué método se utiliza

4
cerberus 25 feb. 2020 a las 12:34

Para las personas que buscan el código, estoy ampliando la respuesta de Cerberus. A partir de la versión de Chrome 80, las cookies se cifran con el algoritmo AES256-GCM, y la clave de cifrado AES se cifra con el sistema de cifrado DPAPI, y la clave cifrada se almacena dentro del archivo "Estado local".

byte[] encryptedData=<data stored in cookie file>
string encKey = File.ReadAllText(localAppDataPath + @"\Google\Chrome\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);
_cookie = _decryptWithKey(encryptedData, decodedKey, 3);

El tamaño de la clave es de 256 bits. El formato del mensaje cifrado es, carga de pago ('v12') + nonce (12 bytes) + texto cifrado

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);
    }
}

Paquetes necesarios

1) Newtonsoft JSON .net

2) Paquete criptográfico Bouncy Castle

5
Arun Karuppaiah 14 mar. 2020 a las 04:43