Иногда бывает, что забываешь пароль от сайта, но браузер его помнит — возникает задача его достать. В настройках Google Chrome можно посмотреть пароль для каждого отдельного сайта, но иногда бывает полезно посмотреть пароли ото всех сайтов сразу (например, в случае амнезии). Кроме того, просто интересно, как Chrome хранит данные форм.
Данные форм Chrome хранит в базе SQLite по адресу C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\Web Data. В этой базе находятся таблицы, отвечающие за автозаполнение, импортированные данные из IE, данные веб-приложений и другие, но нас интересует таблица logins — именно тут хранятся логины и пароли.

Названия полей говорят сами за себя, но вот пароли хранятся в зашифрованном виде. Чтобы понять, как они шифруются, стоит посмотреть на функцию в коде Chrome, которая непосредственно добавляет логин и пароль в таблицу:
Данные форм Chrome хранит в базе SQLite по адресу C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\Web Data. В этой базе находятся таблицы, отвечающие за автозаполнение, импортированные данные из IE, данные веб-приложений и другие, но нас интересует таблица logins — именно тут хранятся логины и пароли.

Названия полей говорят сами за себя, но вот пароли хранятся в зашифрованном виде. Чтобы понять, как они шифруются, стоит посмотреть на функцию в коде Chrome, которая непосредственно добавляет логин и пароль в таблицу:
bool WebDatabase::AddLogin(const PasswordForm& form) {
SQLStatement s;
std::string encrypted_password;
if (s.prepare(db_,
"INSERT OR REPLACE INTO logins "
"(origin_url, action_url, username_element, username_value, "
" password_element, password_value, submit_element, "
" signon_realm, ssl_valid, preferred, date_created, "
" blacklisted_by_user, scheme) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") != SQLITE_OK) {
NOTREACHED() << "Statement prepare failed";
return false;
}
s.bind_string(0, form.origin.spec());
s.bind_string(1, form.action.spec());
s.bind_wstring(2, form.username_element);
s.bind_wstring(3, form.username_value);
s.bind_wstring(4, form.password_element);
Encryptor::EncryptWideString(form.password_value, &encrypted_password);
s.bind_blob(5, encrypted_password.data(),
static_cast<int>(encrypted_password.length()));
s.bind_wstring(6, form.submit_element);
s.bind_string(7, form.signon_realm);
s.bind_int(8, form.ssl_valid);
s.bind_int(9, form.preferred);
s.bind_int64(10, form.date_created.ToTimeT());
s.bind_int(11, form.blacklisted_by_user);
s.bind_int(12, form.scheme);
if (s.step() != SQLITE_DONE) {
NOTREACHED();
return false;
}
return true;
}
* This source code was highlighted with Source Code Highlighter.
Для зашифрования пароля функция использует класс Encryptor, реализация метода которого выглядит так:
bool Encryptor::EncryptString(const std::string& plaintext,
std::string* ciphertext) {
DATA_BLOB input;
input.pbData = const_cast<BYTE*>(reinterpret_cast<const BYTE*>(plaintext.data()));
input.cbData = static_cast<DWORD>(plaintext.length());
DATA_BLOB output;
BOOL result = CryptProtectData(&input, L"", NULL, NULL, NULL, 0, &output);
if (!result)
return false;
// this does a copy
ciphertext->assign(
reinterpret_cast<std::string::value_type*>(output.pbData), output.cbData);
LocalFree(output.pbData);
return true;
}
* This source code was highlighted with Source Code Highlighter.
То есть в конечном счете пароль шифруется Windows-функцией CryptProtectData — да, так просто!
Напоследок небольшая прога, записывающая сохраненные Chrome пароли в файл. Чтобы расшифровать пароль, достаточно открыть базу и применить к каждому password_value функцию CryptUnprotectData, что и было сделано:
#pragma comment(lib, "crypt32.lib")
#include <string>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <Wincrypt.h>
#include <sqlite3.h>
using namespace std;
bool Decrypt(const byte *ciphertext, int length, char **plaintext)
{
DATA_BLOB input;
input.pbData = const_cast<BYTE *>(ciphertext);
input.cbData = static_cast<DWORD>(length);
DATA_BLOB output;
BOOL result = CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output);
if (!result)
return false;
*plaintext = static_cast<char *>(malloc(output.cbData + 1));
strncpy_s(*plaintext, output.cbData + 1, reinterpret_cast<char *>(output.pbData), output.cbData);
LocalFree(output.pbData);
return true;
}
int main()
{
sqlite3 *db;
int rc;
rc = sqlite3_open_v2("C:\\Users\\Username\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Web Data", &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK)
{
cerr << "Can't open database: " << sqlite3_errmsg(db) << endl;
sqlite3_close(db);
getchar();
return 1;
}
sqlite3_stmt *stmt;
char *query = "SELECT origin_url AS url, username_value AS username, password_value AS password FROM logins";
rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (rc != SQLITE_OK)
{
cerr << "Can't prepare statement: " << sqlite3_errmsg(db) << endl;
sqlite3_finalize(stmt);
sqlite3_close(db);
return 1;
}
ofstream fout;
fout.open("C:\\p.csv", ios::out | ios::trunc);
if (!fout.is_open())
{
cerr << "Can't open file";
fout.close();
sqlite3_finalize(stmt);
sqlite3_close(db);
return 1;
}
while (sqlite3_step(stmt) == SQLITE_ROW)
{
const unsigned char *url = sqlite3_column_text(stmt, 0);
const unsigned char *user = sqlite3_column_text(stmt, 1);
const byte *encrypted_password = static_cast<const byte *>(sqlite3_column_blob(stmt, 2));
int enctypted_password_length = sqlite3_column_bytes(stmt, 2);
char *password = "";
Decrypt(encrypted_password, enctypted_password_length, &password);
fout << url << "," << user << "," << password << endl;
}
fout.close();
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
}
* This source code was highlighted with Source Code Highlighter.
P.S. Разумеется, речи о том, чтобы таким образом взять чужую базу и прочитать из нее пароли, быть не может, потому что CryptProtectData шифрует данные так, что расшифровать их может только тот пользователь, который их шифровал.