How Google Chrome Stores Passwords
Every browser released in the last decade has some sort of password management system, and Google Chrome is no different. Sift through the Chrome source code, released under the open source project Chromium, and you’ll found out how they do it.
Let’s start at the top and work our way down. Any time a password is saved, you’re first prompted with the save password bar.
In Chrome, this object is called PasswordManager
. This object is responsible for a lot of stuff, but what we really care about is what happens when you click “Save Password”.
When you click the save button, it calls the following function:
form_manager_->Save();
BeginClose();
}
form_manager is another object, PasswordFormManager, that sits between the user interface and the database. All this function does is call that object’s Save method. It then instructs the save password prompt to close. Here’s what the Save function looks like. |
DCHECK_EQ(state_, POST_MATCHING_PHASE);
DCHECK(!profile_->IsOffTheRecord());
if (IsNewLogin())
SaveAsNewLogin();
else
UpdateLogin();
}
Again, pretty straight forward. The first two items will log information for debugging purposes and aren’t compiled in release mode. It then checks if it is adding a new password or updating an existing one. For the purposes of this tutorial, let’s look at adding a new password.
DCHECK_EQ(state_, POST_MATCHING_PHASE);
DCHECK(IsNewLogin());
// The new_form is being used to sign in, so it is preferred.
DCHECK(pending_credentials_.preferred);
// new_form contains the same basic data as observed_form_ (because its the
// same form), but with the newly added credentials.
DCHECK(!profile_->IsOffTheRecord());
WebDataService* web_data_service =
profile_->GetWebDataService(Profile::IMPLICIT_ACCESS);
if (!web_data_service) {
NOTREACHED();
return;
}
pending_credentials_.date_created = Time::Now();
web_data_service->AddLogin(pending_credentials_);
}
Most of this function is debug code. What we care about is the call to AddLogin
. The WebDataService
object is responsible for meta data associated with a web page.
GenericRequest<PasswordForm>* request =
new GenericRequest<PasswordForm>
(this, GetNextRequestHandle(), NULL, form);
RegisterRequest(request);
ScheduleTask(NewRunnableMethod
(this, &WebDataService::AddLoginImpl, request));
}
Now we’re getting a little more complicated. Adding a password is done asynchronously and this function handles scheduling that task. It seemed to be very important that nothing interrupt Chrome’s user interface – this keeps it feeling fast and responsive. Now let’s take a look at what happens when this task is run.
GenericRequest<PasswordForm>* request) {
if (db_ && !request->IsCancelled()) {
if (db_->AddLogin(request->GetArgument()))
ScheduleCommit();
}
request->RequestComplete();
}
We’re almost at the heart of it all. The important call here is AddLogin
, so let’s dive into that.
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;
}
We’ve finally reached the end of the line. This function actually builds the SQL statement for adding a new password to Chrome’s SQLite database. Of course, the password isn’t stored in plain text so Chrome has an Encryptor
object responsible for encrypting the password first. Let’s take a look at that.
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;
}
The important piece here is CryptProtectData
, which is a Windows API function for encrypting data. Data encrypted with this function is pretty solid. It can only be decrypted on the same machine and by the same user that encrypted it in the first place.
So what’d we learn by investigating Chrome’s password management system? Well, we learned that Google uses SQLite as the storage mechanism for passwords and other web page related data. We also see that Google has done a great job extracting Windows specific code from the cross-platform stuff. The only Windows specific code here is the encryption function, which can easily be ported by creating a different Encryptor object for each OS.
Credits to: http://blog.paranoidferret.com
on September 16th, 2008 at 3:54 am
i keep learning about more and more little advantages and features with Chrome, with privacy, for example; now if only they would take care of it’s cookie management glitches…
on October 6th, 2008 at 10:48 am
[…] take on this matter is to encrypt your passwords on the hard drive, but it doesn’t offer you the option to protect them from prying eyes. For […]
on August 9th, 2009 at 12:34 pm
Thanks, exactly the information I was searching for.