Slowly cracking 1Password8 iOS password w/ Python. How can I do better with Hashcat?
#1
Hi all,

I've had to finally start learning about password cracking to recover passwords stored in 1Password8 iOS vault. I think I figured out how to attempt different passwords and confirm when I find the correct one; however, being a beginner in the matter I know I'm blind to better methods to solve this.

The account only exists in iOS and was created only about 4 months ago, so I'm not sure if Hashcat supports their format. I tried passing the .sqlite database file to 1password2john but it wasn't able to work on it. I'm waiting for my code to finish running to uninstall the GPU driver and install the compatible one for Hashcat to see if it works.
If it doesn't work I'd like to know if I can run a similar process like the one on my script. I know some of the words I used when I created it so I think I could have a good start regarding the dictionary, and now that I have a better understanding of how rules and masks work I think I could crack it.

Brief introduction to how I got here:

So the problem is I forgot the master password/account password for a vault I created for my mom. I suggested moving all her passwords written in plain text to the app and even transcribed some of them for her. The ones I hadn't yet gotten to I locked up in the Notes app using a password generated by 1Password. I showed her how she can copy and paste the password for the (now encrypted) notes whenever she needed to.

So now we both can't remember what the master password and I'm trying to fix. I first reached out for tips on r/1Password thinking there was something else I could do, like a tip to make FaceID prompt again [1] .

I then continued researching on ways to get information from the iPhone device and I got an encrypted backup -- which at first I decrypted using the Mobile Verification Toolkit by Amnesty International Security Lab [2] and even ran the software to see if there were helpful logs that would show the password or hints to remember it.

I then decrypted the backup using a free backup browser to extract the 1Password files, and with the help of this series of posts [3] I went through the .sqlite database and found each of the pieces 1Password uses to create your AUK (Account Unlock Key, previously MUK) which is the central piece to then encrypt your private key that then is used to encrypt vaults.

I learned a lot from the series and 1Password's White Paper [4]. It all made me appreciate everything that has been developed by people to keep data secure, it makes me appreciate math in general; although, it's also scary how your physical device can compromise you in the wrong hands. Finally, it's all intimidating too since I feel like trying to go through armour with a stick.

Where I'm at:

I have now written a script in Python that goes through the same process 1Password goes through to generate the AUK. I do this for each password in a wordlist. I relied heavily on this repository from the same author of the 1Password series [5] .

So these things I know or have from a backup of the device:
  • Secret Key (version, account id, secret)
  • email
  • algorithm
  • iterations
  • salt
  • encrypted sqlite database (keysets, account, vaults, etc.)

With this I then get the HKDF salt:

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

Then the derived password key using the HKDF salt:

Code:
password_key = PBKDF2(sha256, password, salt=hkdf_salt, iterations=p2c, 32 bytes)

Then the HKDF secret key:

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

Then XOR the password key and the HKDF secret key:

Code:
auk = bytes(a ^ b for a, b in zip(password_key, hkdf_key))

Then I check if the resulting auk is valid by trying to decrypt and verify the data in the symmetric key:

Code:
C = AES.new(auk, AES.MODE_GCM, enc_sym_key_iv, mac_len=16)
    try:
        PT_enc_sym_key = C.decrypt_and_verify(enc_sym_key_data[:-16], enc_sym_key_data[-16:])
        if PT_enc_sym_key is not None:
            jwk_loaded = json.loads(jwk_json)
            decrypted_kid = jwk_loaded['kid']
            if(decrypted_kid == keyset_uuid):
                # Found password
            except ValueError:
            continue

My conclusion is that if the current password from the wordlist manages to decrypt the data and in the data the 'kid' matches the unencrypted 'uuid' from the keyset then the password is the correct one.

I divide the wordlist.txt file into chunks and do this to traverse through the words with simultaneous processes:

Code:
num_cores = 12
chunk_size = math.ceil(len(passwords) / num_cores)
pool = multiprocessing.Pool(processes=num_cores)

for i in range(num_cores):
    start = i * chunk_size
    end = start + chunk_size
    pool.apply_async(find_password, args=(passwords, start, end, hkdf_pass_salt, hkdf_key, enc_sym_key_iv, enc_sym_key_data, keyset_uuid))

pool.close()
pool.join()

After attempting this with a new 1Password account on another iOS device, getting the data needed from the backup and going through a wordlist that contained the correct password I managed to confirm that this works and it's very likely that it will work on the main device.

So, how can I do better?

I tried to optimize the code bit by bit and I understand there's plenty more to improve in it. However, I'd like to move to doing something like this on an environment that would make this a lot faster or more efficient. I know I won't be able to code more efficient solutions than the ones already out there by experts, even if I wanted to reinvent the wheel.

I started to feel the lack of optimization in my solution when John The Ripper output a wordlist in the almost hundreds of millions of lines from a set of rules I thought could contain the right password but no luck yet. I know I have to grow the list even more but optimize my approach even more. I would appreciate any help pointing me in the right direction.


Thank you for reading me!


Links

[1] https://www.reddit.com/r/1Password/comme...he_master/

[2] https://github.com/mvt-project/mvt

[3] https://darthnull.org/series/1password/

[4] https://1passwordstatic.com/files/securi...-paper.pdf (PDF)

[5] https://github.com/dschuetz/1password
Reply
#2
I just found the format for the 1password.agilekeychain and 1password.cloudkeychain. Though I'm not sure if or how the information I currently have can be converted to this format.

The cloudkeychain one seems to be more similar based on to the amount of data I have. But I'm not sure how each of the pieces translates. Would it be something like this?

Code:
hkdf_salt:hkdf_secret_key:num_iterations:data

I hope this doesn't sound too silly.

I also have a question regarding the data I have. As I mentioned in my original post, I did manage to decrypt the enc_sym_key. I also decrypted enc_pri_key and enc_sign_key. However, I couldn't find the actual AUK (or MUK). I read that this information is in the accounts but I can't seem to decrypt any further information outside of those first three.
So my question would be, are the agilekeychain or cloudkeychain hashes formed with the information I already have or do I have to further decrypt the information in my database to be able to do that?

First keyset
The "enc_sym_key" did reveal this:
Code:
{
    "alg": "A256GCM",
    "ext": true,
    "k": "",
    "key_ops": [
        "decrypt",
        "encrypt"
    ],
    "kty": "oct",
    "kid": "same-kid"
}

The "enc_pri_key" this:
Code:
{
    "alg": "RSA-OAEP",
    "d": "",
    "dp": "",
    "dq": "",
    "e": "AQAB",
    "ext": true,
    "key_ops": [
        "decrypt"
    ],
    "kty": "RSA",
    "n": "",
    "p": "",
    "q": "",
    "qi": "",
    "kid": "same-kid"
}

And "enc_sign_key" this:
Code:
{
    "crv": "P-256",
    "d": "",
    "ext": true,
    "key_ops": [
        "sign"
    ],
    "kty": "EC",
    "x": "",
    "y": "",
    "kid": "same-kid"
}

Accounts table
The still encrypted information in the accounts table has an "enc_srp_x":
Code:
"enc_srp_x": {
    "cty": "b5+jwk+json",
    "kid": "srpxkey",
    "enc": "A256GCM",
    "iv": "",
    "data": ""
}

And "enc_local_validation_key":
Code:
"enc_local_validation_key": {
    "cty": "b5+jwk+json",
    "kid": "core-setting-authkey-wrapper",
    "enc": "A256GCM",
    "iv": "",
    "data": ""
}

Which I don't know if I still need to do something with them to create a hash.

Thank you for reading me!

The hashes (file downloads):
https://hashcat.net/misc/example_hashes/...lekeychain
https://hashcat.net/misc/example_hashes/...udkeychain
Reply
#3
I was also going through the last AES in 1Password's process after getting the AUK to better understand what the hash needed really is and which “data” is needed actually needs. I’m now thinking that the “data” is not the one I found in the “enc_sym_key” part of the keyset by itself but rather a combination of the b’opdata01’ header, iv, data?

I see that the header is not present because the error presents itself inside the if statement. So maybe I could concatenate them like this? ciphertext = b’opdata01’ + iv + data? I was reading that I would need to append the Authentication Tag to the end of the ciphertext but the data I already have would already contain the Authentication
Tag, I would think.

I ran 1password2john using that as one of the fields that it gets from the sqlite table and and I finally got an output without errors. Based on the 1password2john.py source code it's structured like this (without the line breaks):
Code:
$cloudkeychain
$16 (SALT LENGTH)
$SALT
$650000 (ITERATIONS)
$193 (MASTER KEY LENGTH)
$MASTER_KEY
$2558457051679353615 (PLAIN TEXT LENGTH...)
$16
$IV
$129
$CRYPTEXT
$32
$EXPECTED_HMAC
$161
$HMAC_D_DATA

I was excited to get this output and tried running it with john but it wasn't working and it seems it doesn't recognize the hash using hashid.
So I'm going to finally try Hashcat now that I have something that looks like a real hash that just need some restructuring to match the example hash for agilekeychain or cloudkeychain. I've yet to download the right driver for my GPU but I hope it works.
Reply
#4
I got Hashcat to recognize the hash and it started but returned "Exhausted" so I'm lost again.

I was going through the cloudkeychain hash format and with the help of some other posts I understood that the format was:
[HASH]:[SALT]:[ITERATIONS]:[DATA]

I was confused by the first hash part because I couldn't identify it, but after going through this thread I think I see that the [HASH] is the last 32 bytes of the original data and the [DATA] part has those removed?
https://hashcat.net/forum/thread-6268-po...l#pid38322

Code:
SELECT lower (hex (substr (master_key_data, length (master_key_data) - 32 + 1, 32))) || ":" || lower (hex (salt)) || ":" || iterations || ":" || lower (hex (substr (master_key_data, 1, length (master_key_data) - 32))) FROM profiles;

This is the output with the hash I pieced together and a dictionary with the correct password on the last line:
Code:
hashcat -m 8200 ..\cloudkeychain-hash ..\dictionary.txt
hashcat (v6.2.6) starting

ADL2_Overdrive_Caps(): -5

ADL2_Overdrive_Caps(): -5

ADL2_Overdrive_Caps(): -5

ADL2_Overdrive_Caps(): -5

ADL2_Overdrive_Caps(): -5

ADL2_Overdrive_Caps(): -5

ADL2_Overdrive_Caps(): -5

ADL_Overdrive5_CurrentActivity_Get(): -1

ADL_Overdrive5_CurrentActivity_Get(): -1

ADL_Overdrive5_FanSpeed_Get(): -1

ADL_Overdrive5_CurrentActivity_Get(): -1

ADL_Overdrive5_Temperature_Get(): -1

ADL_Overdrive5_CurrentActivity_Get(): -1

HIP API (HIP 4.4)
=================
* Device #1: AMD Radeon RX 6700 XT, 12269/12272 MB, 20MCU

OpenCL API (OpenCL 2.1 AMD-APP (3380.6)) - Platform #1 [Advanced Micro Devices, Inc.]
=====================================================================================
* Device #2: AMD Radeon RX 6700 XT, skipped

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Slow-Hash-SIMD-LOOP
* Uses-64-Bit

Watchdog: Temperature abort trigger set to 90c

Host memory required for this attack: 175 MB

Dictionary cache built:
* Filename..: ..\dictionary.txt
* Passwords.: 1105
* Bytes.....: 19344
* Keyspace..: 1105
* Runtime...: 0 secs

The wordlist or mask that you are using is too small.
This means that hashcat cannot use the full parallel power of your device(s).
Unless you supply more work, your cracking speed will drop.
For tips on supplying more work, see: https://hashcat.net/faq/morework

Approaching final keyspace - workload adjusted.

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 8200 (1Password, cloudkeychain)
Hash.Target......: [hash]
Time.Started.....: Mon Mar 20 12:25:33 2023 (6 secs)
Time.Estimated...: Mon Mar 20 12:25:39 2023 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (..\dictionary.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:      196 H/s (0.55ms) @ Accel:128 Loops:64 Thr:32 Vec:1
Recovered........: 0/1 (0.00%) Digests (total), 0/1 (0.00%) Digests (new)
Progress.........: 1105/1105 (100.00%)
Rejected.........: 0/1105 (0.00%)
Restore.Point....: 1105/1105 (100.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:649984-649999
Candidate.Engine.: Device Generator
Candidates.#1....: asdfa2300janlsdk. -> [password]
Hardware.Mon.#1..: N/A
Reply
#5
It seems the available modes -m 6600 and -m 8200 are not compatible with the current format/algorithm 1Password uses. I opened an issue suggesting the new algorithm.
I am not yet familiar with how to develop a Hashcat plugin or developing in C at all for that matter. I can grasp some of what the Hashcat Plugin Development Guide explains but it's going to be a very long journey to being able to help develop this.

https://github.com/hashcat/hashcat/issues/3651
Reply
#6
Did you ever get anywhere with this? My gf’s in a real bind right now. The genius forgot her password, and even though I told her to write it down on a post it and hide it in our apartment a few months ago, what she wrote down isn’t working. I’m so frustrated (her not paying attention while doing things drives me up the wall). I’m not nearly as well versed in this as you; hoping you figured out the process so that I can just copy it :-)
Was just gonna use what she wrote on the post it to generate a ton of similar things. For example moving the words around, changing the word “locked” to “lock”, alternative capitalizations, changing the symbol from ! to . etc. 
I’m not even sure where to find her vault to begin. Extract it from a phone backup maybe?
Reply
#7
(10-22-2023, 12:54 AM)damnusernames Wrote: Did you ever get anywhere with this? My gf’s in a real bind right now. The genius forgot her password, and even though I told her to write it down on a post it and hide it in our apartment a few months ago, what she wrote down isn’t working. I’m so frustrated (her not paying attention while doing things drives me up the wall). I’m not nearly as well versed in this as you; hoping you figured out the process so that I can just copy it :-)
Was just gonna use what she wrote on the post it to generate a ton of similar things. For example moving the words around, changing the word “locked” to “lock”, alternative capitalizations, changing the symbol from ! to . etc. 
I’m not even sure where to find her vault to begin. Extract it from a phone backup maybe?

Couldn't crack the password. It was pretty long and complicated and would take too long it seems.

I got the vault by extracting a backup and looking at the app's directory where all its files were. I then used this to extract the hash from the information on the sqlite file: https://github.com/GonnZerg/extractor-de...ecret-key/

However, to use this extractor, it's important that you know or have the Secret Key because on iOS it is obfuscated and I just wrote the extractor to take the value from that field supposing it's already in plain text. There could be a way to retrieve the Secret Key but it's not something I researched since I did have it. So I just replaced the obfuscated Secret Key field with the one I already knew on the database file for the extractor to use.

After you get the hash you can use the module -m 31800 in Hashcat with whatever attack you choose. That part is whole other subject and would recommend going through the FAQs and Wiki first.
Reply