Reputation: 23
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
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:
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:
v10 (0x763130)
.
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