Manuel Rueda Blog

Agile Keychain decryption with JavaScript

October 19, 2015 - 5 minutes read

DISCLAMER

DATE: February 2019

This is a blog entry that I wrote before I change my blog, most of it may be old and probably outdated.

On this publication I’m going to talk about Agile Keychain Design. Its structure, decryption flow and implementation.

A little bit of history

This sensitive data storage was created by AgileBits in 2008 to replace the OS X Keychain in 1Password. The main benefits of replacing OS X Keychain was:

  • Multi OS support
  • Change DES to AES as encryption standard
  • Performance improvement compared to OS X KeyChain.

The structure

The storage is based on a zip-like file with the .agilekeychain_zip extension. It have an internal folder structure like this:

myStorage.agilekeychain_zip
+-- config
    +-- domains
    +-- use-thumbnails
    +-- version
+-- data
    +-- default
        +-- 1password.keys
        +-- *.1password
        +-- content.js
        +-- encryptionKeys.js
    +-- <more profiles>

encryptionKeys.js

Let’s take a look to encryptionKeys.js:

{
  "list": [{
    "data": "U2FsdGVkX19cm85vzXmRCZyaVYd...",
    "validation": "U2FsdGVkX1/YdmmuN6l2tJ6...",
    "identifier": "30737A49AF124394B4CF97F65761769D",
    "level": "SL5",
    "iterations": 10000
  }, {
    "data": "U2FsdGVkX19s+dZpjkrrUeUsNWO...",
    "validation": "U2FsdGVkX1/8LVqKXktw5MULM1...",
    "identifier": "A866C664DD424046A26487AAB677308F",
    "level": "SL3",
    "iterations": 10000
  }],
  "SL3": "A866C664DD424046A26487AAB677308F",
  "SL5": "30737A49AF124394B4CF97F65761769D"
}
  • The list property lists all the keys to decrypt the passwords.
  • The level represent the security level related to the password content.
  • The data contains a BASE-64 representation of the content key encrypted with the master key with PBKDF2 the number of iterations defined in iterations.
  • The validation is the decrypted key encrypted with it self, to validate the decryption process.

The 1password.keys contains the same information but in a XML format

content.js

This JSON lists all the content available in the keychain, this file only give you the UUID, Type and Name.

*.1password

All the files with this extension are content keys and are a JSON with this properties:

  • keyID: UUID of the content key to decrypt the password.
  • uuid: UUID of the password.
  • securityLevel: Define with content key need to decrypt.
  • encrypted: BASE-64 data of the password to decrypt.

There are other properties of 1Password, like types, names, URLs, etc. But there are not critical to the decrypt process.

{
  "keyID": "30737A49AF124394B4CF97F65761769D",
  "locationKey": "someSite.com",
  "typeName": "webforms.WebForm",
  "location": "https://someSite.com/Account/Login",
  "uuid": "CD4D7DE21F414354A01A6ABA626CA2AB",
  "createdAt": 1429537038,
  "title": "someSite.com",
  "securityLevel": "SL5",
  "openContents": {
    "autosubmit": "default"
  },
  "updatedAt": 1429537040,
  "txTimestamp": 1429537040,
  "contentsHash": "1836a185",
  "encrypted": "U2FsdGVkX1/rlHC..."
}

Decryption process

To decrypt a password you need to follow this steps.

  • Convert the master password, content key and content data into buffers.
  • Divide the content key into the salt and data.
  • Generate the derived key with PBKDF2, using the iterations number and the master key.
  • Divide the derived key in the AES key and the AES IV.
  • Decrypt the content key data with AES-128-CBC using the AES Key and AES IV to get the Raw Key.
  • Divide the content data in the salt and the data.
  • Derive the Raw Key with MD5 using the content data salt to get the key and iv.
  • Decrypt the ‘content data’ data with AES-128-CBC using the key and iv just created.
  • Parse the result data as JSON.

This explanation could be a little bit hard to understand, because of that… here is this implemented in Node.js.

var crypto = require('crypto');

var content = 'U2FsdGVkX1/rlHCO4K1g3M...';
var contentKey = 'U2FsdGVkX19s+dZpjkrrUeU...';
var masterKey = 'MyMasterKey';
var iterations = 10000;
var result;

var masterKeyBuffer = new Buffer(masterKey);
var contentKeyBuffer = new Buffer(contentKey, 'base64');
var contentBuffer = new Buffer(content, 'base64');

var derivedKey = crypto.pbkdf2Sync(masterKeyBuffer, contentKeyBuffer.slice(8, 16), iterations, 32);

var decryptedContentKey = decrypt(contentKeyBuffer.slice(16),  derivedKey.slice(0, 16), derivedKey.slice(16, 32));

var passwordAux = deriveKey(decryptedContentKey, contentBuffer.slice(8, 16));

result = JSON.parse(decrypt(contentBuffer.slice(16), passwordAux.key, passwordAux.iv));

console.log(result);

function decrypt(data, key, iv){
  var cipher = crypto.createDecipheriv('aes-128-cbc', key, iv);

  var upBuf = cipher.update(data);
  var finBuf = cipher.final();

  return Buffer.concat([upBuf, finBuf]);
}

function deriveKey(key, salt){
  var rounds = 2;
  var data = Buffer.concat([key, salt]);
  var md5Hashes = [[],[]];
  var md5sum = crypto.createHash('md5');
  md5sum.update(data);
  var sum = md5sum.digest();
  md5Hashes[0] = sum;
  for(var i = 1; i < rounds; i++){
    md5sum = crypto.createHash('md5');
    md5sum.update(Buffer.concat([md5Hashes[i - 1], data]));
    sum = md5sum.digest();
    md5Hashes[i] = sum;
  }
  return {
    key:md5Hashes[0],
    iv: md5Hashes[1]
  };

}

The result logged in the console will be something like this, the result can be different for different types of passwords.

{
  htmlAction: 'https://mysite.com/',
  htmlID: 'passwordExpired',
  htmlName: 'passwordExpired',
  htmlMethod: 'POST',
  passwordHistory: [{
    value: '4FgrFhedhewgM8k78gd8G_',
    time: 1435664836
  }, {
    value: 'MyRealPassword',
    time: 1443537200
  }],
  URLs: [{
    url: 'https://mysite.com/'
  }],
  fields: [{
    type: 'P',
    name: 'stateBean.nuPassword1',
    value: 'MyRealPassword',
    designation: 'password'
  }]
}

In this GitHub repo is an real implementation of this in a basic Key Chain manager. https://github.com/ManRueda/1password-manager


My name is Manuel Rueda and welcome to my blog. You can also follow me on Twitter and/or Github.