It occurred to me sometime after I’d finished my talk that I should have a single post that pulls all the elements together. So here’s a complete walkthrough from Master Password all the way to decrypted Vault Item.

If you’ve missed the first parts of the series, here’s a good starting point.

General Process

First, let’s review the overall sequence of events. It’s a little complicated at the beginning, depending on which client we’re using.

  1. Prompt the user for their Master Password
  2. Generate the Master Unlock Key (MUK). Depending on client OS and account configuration:
    1. If using local vaults:
      1. Get key data and salt from the “profiles” table in the OnePassword database. Generate the Master Keys
      2. Use Master Keys to decrypt enc_login from the appropriate entry in B5 database “accounts” table
      3. Retrieve the MUK from enc_login
    2. If Windows:
      1. Use the password to decrypt the EMK from the config table
      2. Use the decrypted Master Key to decrypt enc_login from “accounts” table
      3. Use the Master Password and Secret Key, along with email information from accounts table and salt, etc., from “keysets”, to generate MUK
    3. If macOS:
      1. Collect Secret Key from the user’s Keychain, email and other information from the accounts and keysets tables
      2. Use the 2SKD process to generate the MUK
  3. Using MUK, decrypt symmetric key from primary keyset
  4. Using symmetric key, decrypt private key from primary keyset
  5. Using private key, decrypt vault key from vaults table
  6. Using vault key, decrypt item from items table

For this discussion, we’ll assume a macOS client with only the single Teams account. I’ve generated a whole new set of data for this (which is different from the data we’ve been using in the rest of the series).

Unlocking

First, generate the MUK using the 2SKD process. To refresh your memory, this is basically:

  1. Normalize email address (lowercase, handle Unicode consistently, etc.)
  2. Retrieve salt from keysets table
  3. Use the HKDF function with the email address, the salt, and the phrase “PBES2g-HS256” to generate a password salt
  4. Retrieve the iterations count from keysets table
  5. Use PBKDF2 with the password, password salt, and iterations count, to generate a password-key
  6. Use HKDF with the Secret Key (split into Version, Account ID, and Secret) to generate another 256-bit value
  7. XOR the password-key and this last HKDF output to generate the MUK

Let’s start with steps 2 and 4. (Note that I’ll be replacing actual sqlite results with data I generated using the tools at my github repository, so they may not quite look exactly like real data) (I’ll also delete several fields of no interest here)

$ sqlite3 B5.sqlite
SQLite version 3.24.0 2018-06-04 14:10:15
Enter ".help" for usage hints.
sqlite> .mode line
sqlite> select * from keysets where encrypted_by = 'mp' and account_id=1;
          id = 1
  account_id = 1
  created_at = 1536781754
  updated_at = 1536781754
encrypted_by = mp
 enc_sym_key = {"enc": "A256GCM","p2c": 100000,"alg": "PBES2g-HS256","cty": "b5+jwk+json","iv": "pOP-OFLkUDtSPASqbVjLfA==","p2s": "MCWbcsy8bT2823bSBvEABg==","data": "3SaMuvyCbGD1wcTribvJrqoXrxnptjZCaZ01ptEytjRmmUWhaFuxR9hkuixkO1HYfI55GuhAs5rnFXPt4vqYgYhDhQqbzqDWlvhKRGs78mtnswm_qNa68hEuJJh9K_VrVyQu8ZzpYhxh4vw_RTshkwvw6cv-4qTXdITfLsr9BBAy_ierhdJcGBG2H9uNUcsV1lxAn49UkDLVS3HhUkbIHCwwnreFDRRpDeQdo5SJJKpLbBogTsguonTh","kid": "mp"}

The “encrypted_by = ‘mp’” means we’re getting a primary keyset entry, encrypted by the MUK using the Master Password, and the “account_id = 1” restricts the search to the primary account. (I’m also not showing the “pub_key” or “enc_pri_key” fields, which we’ll use in a little bit.)

So now we have the salt (p2s) and iterations (p2c), both found as parameters within the “enc_sym_key” field.

Now, let’s get data from the accounts table:

sqlite> select * from accounts where id=1;
                      id = 1
              created_at = 1536581150
              updated_at = 1536781755
            last_auth_at = 1535736037
               team_name = SpacelySprockets
         user_first_name = Tom
          user_last_name = Jones
              user_email = nobody@nowhere.com
             user_avatar = 
                  server = https://nowhere.1password.com
               enc_login = {"enc": "A256GCM","data": "Z0Ld_Mcl7KydyOZ-ScpIDqAU15Di5PEHnMsWreE75ItiqWxQUV8vEVd0_OJ4SKrOD5dS7e6pdsaPkMZYtIl4eS4MXA_fhDO2-t-nczvzCju22IqcYTEqahcupdlScRdXznHt3B3_M3nwf_CNebSGp1dh0lxmVYlgcW2D2TxtY-mLuCFY9fqp8YOdhF271nDc8NO_-2VcBi6A7zspu06vetEUfekGyV_NhvOQZI0RMtc60L7E3nk3VhPAhk2ONwst0Z5SJqx5AYJdettgxO-0M4C9ZMIMB-ivItSe_J60u8qfJalTmXCNwapMKWARpHTOp5PA0F4mCofv_SxJI132GMbDjegtgMtUxO9E0-RFJxuBBdw8HsbINWuUslh8Ow78YNmxhKKKZYLyeojRYpeuX6jmLqD9Im337xSuOg3mukgzs62R5G_cyoYhq5YMUt71JkW7_lBAgPx-uKA4iLJk46qp4DUUMpL7CA9zqqRfx7QXUZm2We_bi0j8NUHpc9mES08-A4G7MH4lBSXIK-RiK8Ru71VrqNlqswOx2j1NgLl4WvQpG7udIuBYa6_kygwS1fUMaCJbNsNI2JyimvRGykCXuNfqnkvLf1ZmobpXexNGp-qRY_G497c=","cty": "b5+jwk+json","kid": "343r9eb8z2z0zttjlae1p8c5ih","iv": "yQkMfBzWt4Hi4c5A7TgfPw=="}

We’ll assume we have the Secret Key (it’s kept in the Local Items keychain under “com.agilebits.onepassword.b5Credentials”), and we know the user’s password. So now we can generate the MUK.

$ python 2skd.py 

Enter the account's Secret Key (A3-xxx....): A3-5LFW85-HVLQ26-EZ656-AL7M6-EVM50-CQ9YB

Enter the master password: guess-magpie-homeland-quanta

Enter the email address: nobody@nowhere.com

Enter the 'p2s' parameter (salt, base-64 or hex encoded): MCWbcsy8bT2823bSBvEABg==

Enter the 'p2c' parameter (iterations count): 100000

Enter the 'alg' parameter
  (PBES2g-HS256 for MUK, SRPg-4096 for SRP-x) (note: case sensitive): PBES2g-HS256


** Computing 2SKD

Password             guess-magpie-homeland-quanta
Email                nobody@nowhere.com
Secret Key           A3-5LFW85-HVLQ26-EZ656-AL7M6-EVM50-CQ9YB
   Version           A3
   AcctID            5LFW85
   Secret            HVLQ26EZ656AL7M6EVM50CQ9YB
Algorithm            PBES2g-HS256
Iterations (p2c)     100000
Salt (p2s)           MCWbcsy8bT2823bSBvEABg==

Salt (decoded)       3025 9b72 ccbc 6d3d bcdb 76d2 06f1 0006   

HKDF(ikm=p2s, len=32, salt=email, hash=SHA256, count=1, info=algorithm)

HKDF out: pass salt  5894 58cc 0d69 dba1 f4d1 200b c249 4bbd   
                     05ee 3590 992e 00aa 2495 4cb8 5275 123c 

PBKDF2(sha256, password, salt=HKDF_salt, iterations=p2c, 32 bytes)

Derived password key 05f8 086c e0f3 4fca c7d9 019f caa7 f28f   
                     2bc2 318a 5a70 2838 f9e9 ee88 beef 274d 

HKDF(ikm=secret, len=32, salt=AcctID, hash=SHA256, count=1, info=version)

HKDF out: secret key 09ec 4b35 c27a 658f 180a ea7f 3d7c 9871   
                     ffd3 9aae 2c96 a2a9 f3f8 61ea 53b0 a6c1 

XOR PBKDF2 output and SecretKey HKDF output

Final 2SKD out       0c14 4359 2289 2a45 dfd3 ebe0 f7db 6afe   
                     d411 ab24 76e6 8a91 0a11 8f62 ed5f 818c 

Base-64 Encoded result: DBRDWSKJKkXf0-vg99tq_tQRqyR25oqRChGPYu1fgYw=

Quickly refreshing how this works:

  • Use the HKDF function to generate a password derivation salt:
    • The p2s parameter (“MCWbc…"), decoded from base-64 into a binary string is the “initial keying material”,
    • The email (converted to lowercase) is the salt, and
    • “PBES2g-HS256” is the “additional information” parameter
    • Use the SHA-256 hash
    • Generate a single 32-byte result
  • This generates the binary string (seen here in hex) of “5894 58cc … 123c”
  • Then use that result as the salt for a PBKDF2 function, with the user’s Master Password as the password, and 100,000 rounds. This derives the password key “05f8 086c … 274d”
  • Split the Secret Key into three chunks: Version (A3), Account ID (the first block, 5LFW85, and the rest) and provide those as inputs to another pass through HKDF. This produces “09ec 4b35 … a6c1”
  • XOR the HKDF(Secret Key) and PBKDF2 results together
  • The final output (0c14 4359 … 818c) is the Master Unlock Key
  • We’ll use the Base-64 encoding of this key for future operations (DBRDWS….gYw=)

Decrypting Keysets

With the MUK we can now decrypt the symmetric (AES) key in the keyset. That’s the “enc_sym_key” column, with “iv” (Initialization Vector) and “data” (encrypted data) sub-fields:

$ python gcm_decrypt.py 
Enter AES-256 key (hex or base-64 encoded)
 --> DBRDWSKJKkXf0-vg99tq_tQRqyR25oqRChGPYu1fgYw=
Enter IV (hex or base-64): pOP-OFLkUDtSPASqbVjLfA==
Enter ciphertext (hex or base-64): 3SaMuvyCbGD1wcTribvJrqoXrxnptjZCaZ01ptEytjRmmUWhaFuxR9hkuixkO1HYfI55GuhAs5rnFXPt4vqYgYhDhQqbzqDWlvhKRGs78mtnswm_qNa68hEuJJh9K_VrVyQu8ZzpYhxh4vw_RTshkwvw6cv-4qTXdITfLsr9BBAy_ierhdJcGBG2H9uNUcsV1lxAn49UkDLVS3HhUkbIHCwwnreFDRRpDeQdo5SJJKpLbBogTsguonTh

{"key_ops": ["decrypt", "encrypt"], "kty": "oct", "alg": "A256GCM", "k": "OsxnC2nkY_092YyzCNvsXAdkma-YnCJaSBoUmJToOKY=", "ext": true, "kid": "343r9eb8z2z0zttjlae1p8c5ih"}

This is the Master Key, generally referred to by the key id (just use the first four characters, in this case, “343r”). In some contexts, it’s called “mk”. It can decyrpt enc_login from the accounts table (which we fetched earlier):

$ python gcm_decrypt.py 
Enter AES-256 key (hex or base-64 encoded)
 --> OsxnC2nkY_092YyzCNvsXAdkma-YnCJaSBoUmJToOKY=
Enter IV (hex or base-64): yQkMfBzWt4Hi4c5A7TgfPw==
Enter ciphertext (hex or base-64): Z0Ld_Mcl7KydyOZ-ScpIDqAU15Di5PEHnMsWreE75ItiqWxQUV8vEVd0_OJ4SKrOD5dS7e6pdsaPkMZYtIl4eS4MXA_fhDO2-t-nczvzCju22IqcYTEqahcupdlScRdXznHt3B3_M3nwf_CNebSGp1dh0lxmVYlgcW2D2TxtY-mLuCFY9fqp8YOdhF271nDc8NO_-2VcBi6A7zspu06vetEUfekGyV_NhvOQZI0RMtc60L7E3nk3VhPAhk2ONwst0Z5SJqx5AYJdettgxO-0M4C9ZMIMB-ivItSe_J60u8qfJalTmXCNwapMKWARpHTOp5PA0F4mCofv_SxJI132GMbDjegtgMtUxO9E0-RFJxuBBdw8HsbINWuUslh8Ow78YNmxhKKKZYLyeojRYpeuX6jmLqD9Im337xSuOg3mukgzs62R5G_cyoYhq5YMUt71JkW7_lBAgPx-uKA4iLJk46qp4DUUMpL7CA9zqqRfx7QXUZm2We_bi0j8NUHpc9mES08-A4G7MH4lBSXIK-RiK8Ru71VrqNlqswOx2j1NgLl4WvQpG7udIuBYa6_kygwS1fUMaCJbNsNI2JyimvRGykCXuNfqnkvLf1ZmobpXexNGp-qRY_G497c=


{"SRPComputedXDictionary": {"hexX": "kJLSFWmPjByRXj7tzqWJj_iHPUmLTpJjX4qgE6Idfgw=", "params": {"alg": "PBES2g-HS256", "salt": "w6tFcZXIB0wCr-IGjc2psw==", "method": "SRPg-4096", "iterations": 100000}}, "masterUnlockKey": {"key_ops": ["encrypt", "decrypt"], "alg": "A256GCM", "k": "DBRDWSKJKkXf0-vg99tq_tQRqyR25oqRChGPYu1fgYw=", "ext": true, "key": "oct", "kid": "mp"}, "email": "nobody@nowhere.com", "personalKey": "A3-5LFW85-HVLQ26-EZ656-AL7M6-EVM50-CQ9YB"}

Now we have the SRP-X key as well, and can, if we want, connect to the server and pull down the latest copies of the vaults. We’ll skip that, since I haven’t gone into how that works. But we don’t strictly need that at this point.

We can also decrypt this first keyset’s private key. I didn’t show it in the query above, so here’s what we would’ve seen (formatted a little more cleanly):

enc_pri_key = {
    "enc": "A256GCM",
    "data": "VPS948h4WY6KRPMLv9AGFNEuV5SN5oSDpdgoZ3MlQnsfqsRVlkgmXpZhks8a2BQOPJsmcINVa-Vo7fNlf_RO6HC-RlTr6tyHDjxJ6LGhgG6ZZMIRVuIHwgAqzJMOxHe_6aIBwgjENTaKm9D3bR8n94iYohHAJ3Hr3ZNqrnLsMYpRiJZvms4ClzEMpr7khasZE0nzNczCX6xfeh_ZdmW2X42bJc_BGWVTprdzTlG8NAprgkiuvqvpn6RtaAl_-cgJ2Z3PRDrea4RK_Yuf5Nftsdb3DrvL1iMd58A5SQ5TWtfYGwYU96JXWQ48MTtGZ7fEo9XXwb9BuN2lsw_NzFuVODCf6MKNWBLIgAvDYrb0qhjYEqwkFu4Brv7Vd0G34ag0Q6_sKX92VAZUVV48QufA7aDILC0JAMsJ1V_A7GE22xavZGTV5tlJyav7cq7b_pX_lbZ4y7BkM4kfkx8iedc_8VKXdBGssGmKoqvB2n6CfsPmI9zqpw3rScNJ_m_9EHBLTqxBVqtbHTGKOCZgOj1qLuVEPZ7As9Z52mC35QaajYARbho-1NkrpG7RI-cm2tPF-F3CNDrWiT8AR5uwrDPUqINsXgvI2q2kdzQ3G6HEAXDe1e0vdoBPvd_8ih7aeP-1GFW8c_QBmRR_7ahAdsbqUOfeQnw2rz-Ng_DdCWjRxoA9K_KLwkQQRENw3q8820ihJjWAZgn9HbOGevC-dFu3NJp49535_53UEMdoSdFGdOoSxFvh6MNvTqz8Qg8C3Mnv534B24FezKskWp2y5ZmkquYF8VyIe9wVlEAXiDyW73BY9crruWVOjyCIpZ2sUtRtTm5vHRd_qDGDcCMWHLHspJmn34jCD1ZE9Gg2koH9barw0zF4nzFxX9o3zIRmzZ8SM5tqVMO8lstSYJvSSv9GozWo_IeERz977NC7AtO3QoxmwfbTfpAkdqbDdoQycAyNp7q0gL0Dt_NKy4g2y5OcwgCokwiJvpBuGFaLWhROqvqeO6FQXTgFFuN5O5XB9OplR7VDjRaYAneIXY-YNZyHqXrsK7ImW6yBsRYw4G9X1YKYRLG63ZLmCL7xATeIfP4mxknFCPpdet_vjbuNFYktaWeG8favCeLhLUWYwCW3Hnh4z-v4uS5x-FvybnGscuct6dy9_iXCaDW_qoctPt_AXx5EvCbABBFVXiBy8A0o38UgDZpk7LLeGaX-KT6c7akyzrfxSi7_Mbe7nOkjRmLVaGUOLT2F1flBQrwNUsoVN6GabIhEiW0IcApSWD_mlN-p-5ww28jSRkNYeB46gnKz3qlBLauQ4zNRFbB2QKxFsW-KtUyNa2YufzmdHwfbalm55w4hQo3R-xhW6vEgQYMlHzYTzi0odTlJvkXfPpAx8yCGlhI8pKRAqAEXwDft7V_lcFlR1GuLQMQSGuTtHNyO2pIrnquKDHMm8oGRCqq896Dr3iL2l6w6zeJMEC7rNnI-GaQqAX77jK5fbzGZPp0SuALXuzqKcS5b4gtNY0qu6E25fOfxRhO66BRtNr7qxDpZBUva-Wz4NgX2OC_LdLG0vGkEvcQFiCgb_5sAYSXpOOLx",
    "cty": "b5+jwk+json",
    "iv": "X7tHTudWEi-PpMFK741lwg==",
    "kid": "343r9eb8z2z0zttjlae1p8c5ih"
},

Use the Master Key 343r to decrypt this:

$ python gcm_decrypt.py 
Enter AES-256 key (hex or base-64 encoded)
 --> OsxnC2nkY_092YyzCNvsXAdkma-YnCJaSBoUmJToOKY=
Enter IV (hex or base-64): X7tHTudWEi-PpMFK741lwg==
Enter ciphertext (hex or base-64): VPS948h4WY6KRPMLv9AGFNEuV5SN5oSDpdgoZ3MlQnsfqsRVlkgmXpZhks8a2BQOPJsmcINVa-Vo7fNlf_RO6HC-RlTr6tyHDjxJ6LGhgG6ZZMIRVuIHwgAqzJMOxHe_6aIBwgjENTaKm9D3bR8n94iYohHAJ3Hr3ZNqrnLsMYpRiJZvms4ClzEMpr7khasZE0nzNczCX6xfeh_ZdmW2X42bJc_BGWVTprdzTlG8NAprgkiuvqvpn6RtaAl_-cgJ2Z3PRDrea4RK_Yuf5Nftsdb3DrvL1iMd58A5SQ5TWtfYGwYU96JXWQ48MTtGZ7fEo9XXwb9BuN2lsw_NzFuVODCf6MKNWBLIgAvDYrb0qhjYEqwkFu4Brv7Vd0G34ag0Q6_sKX92VAZUVV48QufA7aDILC0JAMsJ1V_A7GE22xavZGTV5tlJyav7cq7b_pX_lbZ4y7BkM4kfkx8iedc_8VKXdBGssGmKoqvB2n6CfsPmI9zqpw3rScNJ_m_9EHBLTqxBVqtbHTGKOCZgOj1qLuVEPZ7As9Z52mC35QaajYARbho-1NkrpG7RI-cm2tPF-F3CNDrWiT8AR5uwrDPUqINsXgvI2q2kdzQ3G6HEAXDe1e0vdoBPvd_8ih7aeP-1GFW8c_QBmRR_7ahAdsbqUOfeQnw2rz-Ng_DdCWjRxoA9K_KLwkQQRENw3q8820ihJjWAZgn9HbOGevC-dFu3NJp49535_53UEMdoSdFGdOoSxFvh6MNvTqz8Qg8C3Mnv534B24FezKskWp2y5ZmkquYF8VyIe9wVlEAXiDyW73BY9crruWVOjyCIpZ2sUtRtTm5vHRd_qDGDcCMWHLHspJmn34jCD1ZE9Gg2koH9barw0zF4nzFxX9o3zIRmzZ8SM5tqVMO8lstSYJvSSv9GozWo_IeERz977NC7AtO3QoxmwfbTfpAkdqbDdoQycAyNp7q0gL0Dt_NKy4g2y5OcwgCokwiJvpBuGFaLWhROqvqeO6FQXTgFFuN5O5XB9OplR7VDjRaYAneIXY-YNZyHqXrsK7ImW6yBsRYw4G9X1YKYRLG63ZLmCL7xATeIfP4mxknFCPpdet_vjbuNFYktaWeG8favCeLhLUWYwCW3Hnh4z-v4uS5x-FvybnGscuct6dy9_iXCaDW_qoctPt_AXx5EvCbABBFVXiBy8A0o38UgDZpk7LLeGaX-KT6c7akyzrfxSi7_Mbe7nOkjRmLVaGUOLT2F1flBQrwNUsoVN6GabIhEiW0IcApSWD_mlN-p-5ww28jSRkNYeB46gnKz3qlBLauQ4zNRFbB2QKxFsW-KtUyNa2YufzmdHwfbalm55w4hQo3R-xhW6vEgQYMlHzYTzi0odTlJvkXfPpAx8yCGlhI8pKRAqAEXwDft7V_lcFlR1GuLQMQSGuTtHNyO2pIrnquKDHMm8oGRCqq896Dr3iL2l6w6zeJMEC7rNnI-GaQqAX77jK5fbzGZPp0SuALXuzqKcS5b4gtNY0qu6E25fOfxRhO66BRtNr7qxDpZBUva-Wz4NgX2OC_LdLG0vGkEvcQFiCgb_5sAYSXpOOLx


{"key_ops": ["decrypt"], "e": "AQAB", "d": "IsMthqWEldQcNlsaSaASm-8D7lrTXNUBuhJJ07HalAGYGKwJ1jv4-HSODRJKnELEFazjFz-563HCsyPIQh4Xsi8KrJ5LRHdg7EFjZkTl_r7SQpHdLl79SXqiDMPp5_5sgfLiuGyC3aO8rZFFTBkMYUflK4BRLt9jiFBX1dLXIUrjdK0ZhaBfOFKRoLLRqY_JcVz8GVDIdnR3OBzegBOUdc8KxGfvGpWa9X2dz2V6Ul6Njmn5i60-u2kZyLVaNTnNIZG6I1xYoHbSZU8rKD-TaDs71b-WKya5NAnkanUCmKUt5w0URYK8iVieh5chacn9-GVqP8rndk56cEFquRM7YQ", "alg": "RSA-OAEP", "n": "lCX116GhkQIhqxaNC_3wjTHn6u_yixwcFcZVIh8RcaLpKh6bH205tMe9tG13ZxGeUmLlmZEmPcn3Oax84qPnohbJn-3AuFyDxdcK0KCMsiNgE8dlQH5yNRHfB1L7CaCnRt-doxiXuYalwsVZjRtov9iv2bx9kpq5tfOM54Gyfjmaa-y5p8eAFBzH9k6hwBM_CbjkO3WKR9_gX2B-BSgwtCyWlK15-dwvdD5Axe-KEJ8S8C9QBBtQw63ceb9hNrtKVCONWl76iOp0zkf2akRbzcbfsuCz1axYBkDqSc72wlQlQF4x3FaUu58oU7FLltLqvXz8580jXuGg3MNlbPIoQQ", "q": "zBbgASwBAgx7Km59zufmk2wOSw7dVMGczAgbmuhqrCme_NlQjeknR1pTrJdEDIDrAM_Kh_9igMgajfflyM8gOYmFJalADXeOROO_9GTsWtiK7sLV6nMiadNsxIe6HQTA6spTVls5OewJToOhOxPlohSuQSpWaiPQFLSTdWgqNYU", "p": "udSGPdkzc1StiRmapdL9YC9jdRQCOMzMF0thrTFqLFJLRMYjf2JMmmhIFLb9GXst9mjZlBXJ6z1mrCJPRoY8gPhzZMb0LSVYizT8EGnp57lcPEy1uJeg3UW4vRu4JEJKPPRPuKsukS3bCdpbOfKSmBfatWThteoZBrwENLJYVo0", "kid": "343r9eb8z2z0zttjlae1p8c5ih", "kty": "RSA"}

Decrypting Vault Key

Now we have all we need to begin decrypting the vaults. Let’s fetch the vault key for the first vault (again, selectively deleting some fields for simplicity):

sqlite> select * from vault_access where account_id = 1 limit 1;
           id = 1
   account_id = 1
     vault_id = 1
   created_at = 1532369304
   updated_at = 1532369304
      version = 3
lease_timeout = 0
          acl = 15730160
enc_vault_key = {"enc": "RSA-OAEP", "data": "erytOQ4IzBwiUAd1MM1VWdfBxAb63rUBhdeap0GVI2v2QGQdqxz8rq9K-75NamKGE-kWttBN6JghSN__e2BrsoCQr44L6h25vEIFCnvelTcFsZm-Nb5RWTZdAqdPz_A_j3Ht3MbfnFyuP1L6XoSdajsHGzDgm4Ku2DDeleEy2QYMRSln1GYYuVFWgedKvRDjECfqMX2K-2wEIKO3QdsV_zeEm3pT5rEzep2C6Fixz6pkESeTIlonliZ_fhIKrbOtGk-k723Yt8yblLEvKqLApF351C_TVXnbFnCypfsIdre7gxDUvsrt6R5W4-GwL0yIZA9mDErQgnyu875bUcg0lQ==", "cty": "b5+jwk+json", "kid": "343r9eb8z2z0zttjlae1p8c5ih"}

And decrypt this using the RSA Private key “343r” that we just extracted:

$ python rsa_decrypt.py 
Enter RSA private key (as json, decrypted from keyset):     {"key_ops": ["decrypt"], "e": "AQAB", "d": "IsMthqWEldQcNlsaSaASm-8D7lrTXNUBuhJJ07HalAGYGKwJ1jv4-HSODRJKnELEFazjFz-563HCsyPIQh4Xsi8KrJ5LRHdg7EFjZkTl_r7SQpHdLl79SXqiDMPp5_5sgfLiuGyC3aO8rZFFTBkMYUflK4BRLt9jiFBX1dLXIUrjdK0ZhaBfOFKRoLLRqY_JcVz8GVDIdnR3OBzegBOUdc8KxGfvGpWa9X2dz2V6Ul6Njmn5i60-u2kZyLVaNTnNIZG6I1xYoHbSZU8rKD-TaDs71b-WKya5NAnkanUCmKUt5w0URYK8iVieh5chacn9-GVqP8rndk56cEFquRM7YQ", "alg": "RSA-OAEP", "n": "lCX116GhkQIhqxaNC_3wjTHn6u_yixwcFcZVIh8RcaLpKh6bH205tMe9tG13ZxGeUmLlmZEmPcn3Oax84qPnohbJn-3AuFyDxdcK0KCMsiNgE8dlQH5yNRHfB1L7CaCnRt-doxiXuYalwsVZjRtov9iv2bx9kpq5tfOM54Gyfjmaa-y5p8eAFBzH9k6hwBM_CbjkO3WKR9_gX2B-BSgwtCyWlK15-dwvdD5Axe-KEJ8S8C9QBBtQw63ceb9hNrtKVCONWl76iOp0zkf2akRbzcbfsuCz1axYBkDqSc72wlQlQF4x3FaUu58oU7FLltLqvXz8580jXuGg3MNlbPIoQQ", "q": "zBbgASwBAgx7Km59zufmk2wOSw7dVMGczAgbmuhqrCme_NlQjeknR1pTrJdEDIDrAM_Kh_9igMgajfflyM8gOYmFJalADXeOROO_9GTsWtiK7sLV6nMiadNsxIe6HQTA6spTVls5OewJToOhOxPlohSuQSpWaiPQFLSTdWgqNYU", "p": "udSGPdkzc1StiRmapdL9YC9jdRQCOMzMF0thrTFqLFJLRMYjf2JMmmhIFLb9GXst9mjZlBXJ6z1mrCJPRoY8gPhzZMb0LSVYizT8EGnp57lcPEy1uJeg3UW4vRu4JEJKPPRPuKsukS3bCdpbOfKSmBfatWThteoZBrwENLJYVo0", "kid": "343r9eb8z2z0zttjlae1p8c5ih", "kty": "RSA"}

Enter ciphertext (base-64 or hex): erytOQ4IzBwiUAd1MM1VWdfBxAb63rUBhdeap0GVI2v2QGQdqxz8rq9K-75NamKGE-kWttBN6JghSN__e2BrsoCQr44L6h25vEIFCnvelTcFsZm-Nb5RWTZdAqdPz_A_j3Ht3MbfnFyuP1L6XoSdajsHGzDgm4Ku2DDeleEy2QYMRSln1GYYuVFWgedKvRDjECfqMX2K-2wEIKO3QdsV_zeEm3pT5rEzep2C6Fixz6pkESeTIlonliZ_fhIKrbOtGk-k723Yt8yblLEvKqLApF351C_TVXnbFnCypfsIdre7gxDUvsrt6R5W4-GwL0yIZA9mDErQgnyu875bUcg0lQ==

Decrypted data:

{"key_ops": ["decrypt", "encrypt"], "kty": "oct", "alg": "A256GCM", "k": "WwJG8d75Uc-kT11wGUMOYh-DssoeTOW3Qo5Y62Pj804=", "ext": true, "kid": "1mhogocqv0nyoyo4b4q0blrk7y"}

Now we have a new symmetric key, “1mho”. Use this key to decrypt the vault attributes:

sqlite> select * from vaults where account_id = 1 limit 1;
              id = 1
      account_id = 1
      created_at = 1532369304
      updated_at = 1532369304
       enc_attrs = {"enc": "A256GCM", "data": "CvGwDQFLDhqOeakFOhMJ5qmwnfWcJSK86UvKI8BIhKKutVlaCsCD1z2cUxfvV9KlrAs0qypffaHEOnWCCQnzR0d0PQwXmc-mMBPMJb9RDKUX_k6cZtdc9nMLxRKN6MMWIiJlsZC004f3QQ-L_MkuAFfKpw9UgzQ9csDm4AgQEidBAHIbmGUV", "cty": "b5+jwk+json", "iv": "CqambL5GJWdjV6-YK9SvBw==", "kid": "1mhogocqv0nyoyo4b4q0blrk7y"}


$ python gcm_decrypt.py 
Enter AES-256 key (hex or base-64 encoded)
 --> WwJG8d75Uc-kT11wGUMOYh-DssoeTOW3Qo5Y62Pj804=
Enter IV (hex or base-64): CqambL5GJWdjV6-YK9SvBw==
Enter ciphertext (hex or base-64): CvGwDQFLDhqOeakFOhMJ5qmwnfWcJSK86UvKI8BIhKKutVlaCsCD1z2cUxfvV9KlrAs0qypffaHEOnWCCQnzR0d0PQwXmc-mMBPMJb9RDKUX_k6cZtdc9nMLxRKN6MMWIiJlsZC004f3QQ-L_MkuAFfKpw9UgzQ9csDm4AgQEidBAHIbmGUV

{"desc": "unk-b64-blob", "type": "P", "uuid": "04ean7xv1mn9ucslj1b5szbhp7", "avatar": "", "name": "Test vault: vault1"}

And now we know the name of the vault: “Test vault: vault1”.

Decrypting a Vault Item

Finally, we can now decrypt a vault item. Let’s grab the first item for the first vault:

sqlite> select * from items where vault_id = 1 limit 1;
            id = 1
      vault_id = 1
    created_at = 1524765033
    updated_at = 1525892766
      overview = {"enc": "A256GCM", "kid": "1mhogocqv0nyoyo4b4q0blrk7y", "data": "hfKp8MWuLt1OtHFNgzRDK-GQ7tKK7pMUBL2_4aASafV6cdnJKgLjxHUHwHqUULZDRxshGTOgq37TIwfsMJ5LoG8Zh_fIQv3PwxJH0IsDSS2xMSEFjFv4fpiUmoI=", "cty": "b5+jwk+json", "iv": "CVbam841DVVyLpAO1ARBmw=="}
       details = {"enc": "A256GCM", "kid": "1mhogocqv0nyoyo4b4q0blrk7y", "data": "OUrUMEdOuwXB1YIOhD1QgSR3hkXCjqXgCnf1NTD1BBwh5eubmr9D9CBqOaapsWRtV94Zhr_z-o5v2SUgHwOOwadPEKac-qSZieejBDtl8Bfz_3a1XXi61HRHe4UaDeAhTZUSr1GUN0lGdSugrhN1_K45u5i9hHeoPwdWo2WXkC9P1MONsR-dnA==", "cty": "b5+jwk+json", "iv": "e5sT8Zx4Jh1jP57ebkXWqA=="}

First, decrypt the overview, using “1mho”:

$ python gcm_decrypt.py 
Enter AES-256 key (hex or base-64 encoded)
 --> WwJG8d75Uc-kT11wGUMOYh-DssoeTOW3Qo5Y62Pj804=
Enter IV (hex or base-64): CVbam841DVVyLpAO1ARBmw==
Enter ciphertext (hex or base-64): hfKp8MWuLt1OtHFNgzRDK-GQ7tKK7pMUBL2_4aASafV6cdnJKgLjxHUHwHqUULZDRxshGTOgq37TIwfsMJ5LoG8Zh_fIQv3PwxJH0IsDSS2xMSEFjFv4fpiUmoI=


{"ainfo": "account-name", "url": "https://example.com", "title": "My test!"}

And now the password itself:

$ python gcm_decrypt.py 
Enter AES-256 key (hex or base-64 encoded)
 --> WwJG8d75Uc-kT11wGUMOYh-DssoeTOW3Qo5Y62Pj804=
Enter IV (hex or base-64): e5sT8Zx4Jh1jP57ebkXWqA==
Enter ciphertext (hex or base-64): OUrUMEdOuwXB1YIOhD1QgSR3hkXCjqXgCnf1NTD1BBwh5eubmr9D9CBqOaapsWRtV94Zhr_z-o5v2SUgHwOOwadPEKac-qSZieejBDtl8Bfz_3a1XXi61HRHe4UaDeAhTZUSr1GUN0lGdSugrhN1_K45u5i9hHeoPwdWo2WXkC9P1MONsR-dnA==


{"fields": [{"type": "T", "name": "username", "value": "user"}, {"type": "P", "name": "password", "value": "password"}]}

That’s it!

So there you have it. We:

  • Collected the Master Password (from the keyboard), Secret Key (from the system Keychain), email address (from the accounts table), and salt and iterations (from the keysets table)
  • Generated the Master Unlock Key (MUK) using the Two-Secret Key Derivation Process (2SKD)
  • Used MUK to decrypt enc_sym_key in the account’s first keyset (the master key, key id beginning “343r”) (located in the keyset table)
  • Used 343r to decrypt the keyset RSA private key (also called 343r)
  • Used the 343r private key to decrypt the vault key “1mho” for vault id 1 (from vault_access table)
  • Used 1mho to decrypt:
    • The vault name and description (vaults table)
    • The item overview - name, URL (items table)
    • The item details - password, etc. (items table)

If we’d been using Local vaults, or on a Windows system, the initial unlock would’ve been a bit more complicated. If we’d wanted to retrieve an item from a local vault, we would also have had some additional steps. And finally, if we’d wanted to retrieve an item from a secondary account, we would have had to retrieve that account’s MUK from another entry in the accounts table, then use that to decrypt the primary keyset.

It all gets somewhat complex rather quickly.

But hopefully this helps to pull everything together, and to illustrate how it all works as a whole.