Tim Taubert: Implementing a PBKDF2-based Password Storage Scheme for Firefox OS |
My esteemed colleague Frederik Braun recently took on to rewrite the module responsible for storing and checking passcodes that unlock Firefox OS phones. While we are still working on actually landing it in Gaia I wanted to seize the chance to talk about this great use case of the WebCrypto API in the wild and highlight a few important points when using password-based key derivation (PBKDF2) to store passwords.
Let us take a closer look at not the verbatim implementation but at a slightly simplified version. The API offers the only two operations such a module needs to support: setting a new passcode and verifying that a given passcode matches the stored one.
let Passcode = { store(code) { // ... }, verify(code) { // ... } };
When setting up the phone for the first time - or when changing the passcode
later - we call Passcode.store()
to write a new code to disk.
Passcode.verify()
will help us determine whether we should unlock the phone.
Both methods return a Promise as all operations exposed by the WebCrypto API
are asynchronous.
Passcode.store("1234").then(() => { return Passcode.verify("1234"); }).then(valid => { console.log(valid); }); // Output: true
The module should absolutely not store passcodes in the clear. We will use PBKDF2 as a pseudorandom function (PRF) to retrieve a result that looks random. An attacker with read access to the part of the disk storing the user’s passcode should not be able to recover the original input, assuming limited computational resources.
The function deriveBits()
is a PRF that takes a passcode and returns a Promise
resolving to a random looking sequence of bytes. To be a little more specific,
it uses PBKDF2 to derive pseudorandom bits.
function deriveBits(code) { // Convert string to a TypedArray. let bytes = new TextEncoder("utf-8").encode(code); // Create the base key to derive from. let importedKey = crypto.subtle.importKey( "raw", bytes, "PBKDF2", false, ["deriveBits"]); return importedKey.then(key => { // Salt should be at least 64 bits. let salt = crypto.getRandomValues(new Uint8Array(8)); // All required PBKDF2 parameters. let params = {name: "PBKDF2", hash: "SHA-1", salt, iterations: 5000}; // Derive 160 bits using PBKDF2. return crypto.subtle.deriveBits(params, key, 160); }); }
As you can see above PBKDF2 takes a whole bunch of parameters. Choosing good values is crucial for the security of our passcode module so it is best to take a detailed look at every single one of them.
PBKDF2 is a big PRF that iterates a small PRF. The small PRF, iterated multiple times (more on why this is done later), is fixed to be an HMAC construction; you are however allowed to specify the cryptographic hash function used inside HMAC itself. To understand why you need to select a hash function it helps to take a look at HMAC’s definition, here with SHA-1 at its core:
HMAC-SHA-1(k, m) = SHA-1((k
Комментировать | « Пред. запись — К дневнику — След. запись » | Страницы: [1] [Новые] |