1 Password 2 John
1 Password 2 John
/usr/bin/env python3
# Written by Dhiru Kholia <dhiru at openwall.com> in July 2012 for JtR project.
# Copyright (c) 2012-2013, Dhiru Kholia.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# Code borrowed from https://github.com/Roguelazer/onepasswordpy
#
# Copyright (c) 2013, James Brown
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# Code borrowed from https://bitbucket.org/gwik/agilekeychain
#
# Copyright (c) 2009 Antonin Amand <[email protected]>
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation.
#
# THE AUTHOR PROVIDES THIS SOFTWARE 'AS IS' AND ANY EXPRESSED OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# References,
#
# https://support.1password.com/agile-keychain-design/
# https://support.1password.com/opvault-design/ (formerly called "Cloud Keychain")
#
# August 2017 notes,
#
# In December 2012, AgileBits introduced the OPVault format to replace the
# Agile Keychain format, which had been introduced in 2008. OPVault format is
# used by default when syncing with iCloud or Dropbox.
#
# December 2017 notes,
#
# Thanks goes to Matt Page and philsmd for figuring out the details related to
# 1Password 6 for macOS.
#
# https://github.com/mpage/onepassword/blob/master/vault.go has some new
# "attack vectors" as well.
#
# https://github.com/mpage/onepassword/blob/master/notes.md is useful too.
#
# Agile Keychain format uses encryptionKeys.js file. Cloud Keychain (and
# OPVault) format uses profile.js file. However, 1Password 6 (and 1Password 4)
# for macOS stores data in .sqlite files.
#
# $ ls -l ~/Library/Application\ Support/1Password\ 4/Data/ # macOS 10.13.2 +
1Password 6.8.5
# -rw-r--r-- 1 lulu staff 4096 Dec 29 15:29 B5.sqlite
# -rw-r--r-- 1 lulu staff 4096 Dec 29 15:29 OnePassword.sqlite
#
# From https://blog.elcomsoft.com/2017/08/attacking-the-1password-master-password-
follow-up/,
#
# B5.sqlite -> this database is used in macOS for synchronizing with 1Password
# proprietary cloud service.
#
# We can only extract the "hash" from the OnePassword.sqlite file (and not the
# B5.sqlite file) at the moment.
#
# Please note that the "profiles" table in OnePassword.sqlite can be empty,
# unless the "Local Vaults" option under 1Password 6 -> Preferences -> Advanced
# is selected.
import os
import sys
import struct
import base64
import sqlite3
PY3 = sys.version_info[0] == 3
if not PY3:
reload(sys)
sys.setdefaultencoding('utf8')
try:
import json
assert json
except ImportError:
try:
import simplejson as json
except ImportError:
sys.stderr.write("Please install json / simplejson module which is
currently not installed.\n")
sys.exit(1)
OPDATA1_MINIMUM_SIZE = 80
DEFAULT_PBKDF_ITERATIONS = 1000
MINIMUM_PBKDF_ITERATIONS = 1000
A_AES_SIZE = 128
C_AES_SIZE = 256
KEY_SIZE = {
128: 16,
192: 24,
256: 32,
}
INITIAL_KEY_OFFSET = 12
PY3 = sys.version_info[0] == 3
PMV = sys.version_info[1] >= 6
class Key(object):
""" A Key in the keyring
"""
if PY3 or PMV:
exec("SALTED_PREFIX=b'Salted__'")
else:
SALTED_PREFIX = 'Salted__'
ZERO_IV = "\0" * 16
ITERATIONS = 1000
BLOCK_SIZE = 16
Nr = 14
Nb = 4
Nk = 8
def opdata1_unpack(data):
HEADER_LENGTH = 8
TOTAL_HEADER_LENGTH = 32
HMAC_LENGTH = 32
if data[:HEADER_LENGTH] != b"opdata01":
data = base64.b64decode(data)
if PY3 or PMV:
MAGIC = b"opdata01"
else:
MAGIC = "opdata01"
if data[:HEADER_LENGTH] != MAGIC:
raise TypeError("expected opdata1 format message")
plaintext_length, iv = struct.unpack("<Q16s",
data[HEADER_LENGTH:TOTAL_HEADER_LENGTH])
cryptext = data[TOTAL_HEADER_LENGTH:-HMAC_LENGTH]
expected_hmac = data[-HMAC_LENGTH:]
hmac_d_data = data[:-HMAC_LENGTH]
return plaintext_length, iv, cryptext, expected_hmac, hmac_d_data
def __repr__(self):
return '<%s.CloudKeychain path="%s">' % (self.__module__, self.path)
def __open_keys_file(self):
try:
keys_file_path = \
os.path.join(self.path, 'default', 'profile.js')
if os.path.exists(keys_file_path):
self.processed = True
else:
return
f = open(keys_file_path, 'rb')
ds = f.read()[INITIAL_KEY_OFFSET:-1]
data = json.loads(ds)
salt = base64.b64decode(data['salt'])
masterKey = base64.b64decode(data['masterKey'])
sys.stdout.write("$cloudkeychain$%s$%s$%s$%s$%s" % (len(salt),
binascii.hexlify(salt).decode("ascii"),
data["iterations"],
len(masterKey),
binascii.hexlify(masterKey).decode("ascii")))
sys.stdout.write("$%s$%s$%s$%s$%s$%s$%s$%s$%s\n" %
(plaintext_length, len(iv),
binascii.hexlify(iv).decode("ascii"), len(cryptext),
binascii.hexlify(cryptext).decode("ascii"),
len(expected_hmac),
binascii.hexlify(expected_hmac).decode("ascii"),
len(hmac_d_data),
binascii.hexlify(hmac_d_data).decode("ascii")))
class AgileKeychain(object):
def __init__(self, path, name='default'):
self.path = path
self.name = name
self.entries = None
self.keys = list()
ret = self.__open_keys_file()
if ret:
self.john_output()
def __repr__(self):
return '<%s.AgileKeychain path="%s">' % (self.__module__, self.path)
def __open_keys_file(self):
"""Open the json file containing the keys for decrypting the
real keychain and parse it
"""
try:
keys_file_path = \
os.path.join(self.path, 'data', self.name, 'encryptionKeys.js')
keys_file = open(keys_file_path, 'r')
try:
keys = json.loads(keys_file.read())
self.keys = []
for kd in keys['list']:
try:
key = Key(kd['identifier'],
kd['level'],
b64decode(kd['data'][:-1]),
b64decode(kd['validation'][:-1]),
kd.get('iterations', Key.ITERATIONS))
self.keys.append(key)
except TypeError:
key = Key(kd['identifier'],
kd['level'],
b64decode(kd['data']),
b64decode(kd['validation']),
kd.get('iterations', Key.ITERATIONS))
self.keys.append(key)
finally:
keys_file.close()
except (IOError, KeyError, ValueError, TypeError):
e = sys.exc_info()[1]
sys.stderr.write('Error while opening the keychain, %s\n' % str(e))
return False
return True
def john_output(self):
sys.stdout.write("%s:$agilekeychain$%s" % (os.path.basename(self.path),
len(self.keys)))
for i in range(0, len(self.keys)):
sys.stdout.write("*%s*%s*%s*%s*%s" % (self.keys[i].iterations,
len(self.keys[i].salt),
binascii.hexlify(self.keys[i].salt).decode("ascii"),
len(self.keys[i].data),
binascii.hexlify(self.keys[i].data).decode("ascii")))
sys.stdout.write("\n")
def process_sqlite(filename):
db = sqlite3.connect(filename)
cursor = db.cursor()
rows = cursor.execute('SELECT master_key_data, salt, iterations FROM profiles')
found = False
for row in rows:
masterKey, salt, iterations = row
sys.stdout.write("$cloudkeychain$%s$%s$%s$%s$%s" % (
len(salt),
binascii.hexlify(salt).decode("ascii"),
iterations,
len(masterKey),
binascii.hexlify(masterKey).decode("ascii")))
found = True
return found
def process_file(keychain):
found = False
if "sqlite" in keychain: # XXX weak hack
found = process_sqlite(keychain)
if found:
return
keychainobj = CloudKeychain(keychain)
if not keychainobj.processed:
keychain = AgileKeychain(keychain)
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stderr.write("Usage: %s <1Password Agile Keychain(s) / Cloud
Keychain(s)> / OnePassword.sqlite\n" %
sys.argv[0])
sys.exit(1)