-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request] Add $keychain$ support #2457
Comments
In 99,9% of the cases, this keychain-password is the same as the macOS login password, which is supported by Hashcat with It seems that macOS login is based on pbkdf2_sha512 and the keychain, like you mentioned, on PBKDF2-SHA1 3DES. It would be interesting to see if there is a speed gain. |
I am not aware of this statistic, but from my experience, keychain passwords are always different from the login one. |
First phrase: https://support.apple.com/guide/keychain-access/if-you-need-to-update-your-keychain-password-kyca2429/mac The user can change the keychain-password, but this is a deliberate action what not much people tend to do... I either way, I support you feature request :) |
I see three problems here: First, the password for the above example hash is wrong. This is very bad, because developers might waste a lot of time with wrong examples ! Try to avoid to do these types of mistakes/typos. The password would be "password" (without quotes). The example is taken from the john the ripper (JTR) examples: https://github.com/magnumripper/JohnTheRipper/blob/2778d2e9df4aa852d0bc4bfbb7b7f3dde2935b0c/src/keychain_common_plug.c#L11 Second, the tool from above (KeychainCracker) seems to be non-cross-platform, only working on macOS and also not have any code dedicated to the hashing. It's basically just a GUI that uses the operating system (macOS's) APIs, in particular it's this (SecKeychainUnlock): https://developer.apple.com/documentation/security/1400341-seckeychainunlock . Third, the verification step for this hash format is actually not bullet proof. As you can see from here: https://github.com/magnumripper/JohnTheRipper/blob/2778d2e9df4aa852d0bc4bfbb7b7f3dde2935b0c/src/keychain_fmt_plug.c#L111 , we only test for 0x04040404 at a very specific location of the decrypted data. These are only 4 bytes / 32 bits... which is very bad / low. It would be much better if there wasn't that high chance of false positives, wrong passwords reported. The good thing is that with 1000 iteration it's slowed down a little bit, but maybe not too much and therefore this hash type would need to be used with --keep-guessing :( So here I will try to explain a little bit the algorithm and attach some POC (proof of concept) perl hash generation code:
#!/usr/bin/env perl
# Author: philsmd
# Date: June 2020
# License: public domain, credits go to philsmd and hashcat
use strict;
use warnings;
use Crypt::PBKDF2;
use Crypt::CBC;
#
# Constants
#
my $ITERATIONS = 1000;
#
# Examples
#
# Example 1:
# $keychain$*10f7445c8510fa40d9ef6b4e0f8c772a9d37e449*f3d19b2a45cdcccb*8c3c3b1c7d48a24dad4ccbd4fd794ca9b0b3f1386a0a4527f3548bfe6e2f1001804b082076641bbedbc9f3a7c33c084b:password
my $pass = "password";
my $salt = pack ("H*", "10f7445c8510fa40d9ef6b4e0f8c772a9d37e449");
my $iv = pack ("H*", "f3d19b2a45cdcccb");
my $data = pack ("H*", "8c3c3b1c7d48a24dad4ccbd4fd794ca9b0b3f1386a0a4527f3548bfe6e2f1001804b082076641bbedbc9f3a7c33c084b");
# Example 2:
# $keychain$*a88cd6fbaaf40bc5437eee015a0f95ab8ab70545*b12372b1b7cb5c1f*1f5c596bcdd015afc126bc86f42dd092cb9d531d14a0aafaa89283f1bebace60562d497332afbd952fd329cc864144ec:password
# my $pass = "password";
# my $salt = pack ("H*", "a88cd6fbaaf40bc5437eee015a0f95ab8ab70545");
# my $iv = pack ("H*", "b12372b1b7cb5c1f");
# my $data = pack ("H*", "1f5c596bcdd015afc126bc86f42dd092cb9d531d14a0aafaa89283f1bebace60562d497332afbd952fd329cc864144ec");
# Example 3:
# $keychain$*23328e264557b93204dc825c46a25f7fb1e17d4a*19a9efde2ca98d30*6ac89184134758a95c61bd274087ae0cffcf49f433c7f91edea98bd4fd60094e2936d99e4d985dec98284379f23259c0:hhh
# my $pass = "hhh";
# my $salt = pack ("H*", "23328e264557b93204dc825c46a25f7fb1e17d4a");
# my $iv = pack ("H*", "19a9efde2ca98d30");
# my $data = pack ("H*", "6ac89184134758a95c61bd274087ae0cffcf49f433c7f91edea98bd4fd60094e2936d99e4d985dec98284379f23259c0");
# Example 4:
# $keychain$*927717d8509db73aa47c5e820e3a381928b5e048*eef33a4a1483ae45*a52691580f17e295b8c2320947968503c605b2784bfe4851077782139f0de46f71889835190c361870baa56e2f4e9e43:JtR-Jumbo
# my $pass = "JtR-Jumbo";
# my $salt = pack ("H*", "927717d8509db73aa47c5e820e3a381928b5e048");
# my $iv = pack ("H*", "eef33a4a1483ae45");
# my $data = pack ("H*", "a52691580f17e295b8c2320947968503c605b2784bfe4851077782139f0de46f71889835190c361870baa56e2f4e9e43");
# Example 5:
# $keychain$*1fab88d0b8ea1a3d303e0aef519796eb29e46299*3358b0e77d60892f*286f975dcd191024227514ed9939d0fa94034294ba1eca6d5c767559e75e944b5a2fcb54fd696be64c64f9d069ce628a:really long password -----------------------------
# my $pass = "really long password -----------------------------";
# my $salt = pack ("H*", "1fab88d0b8ea1a3d303e0aef519796eb29e46299");
# my $iv = pack ("H*", "3358b0e77d60892f");
# my $data = pack ("H*", "286f975dcd191024227514ed9939d0fa94034294ba1eca6d5c767559e75e944b5a2fcb54fd696be64c64f9d069ce628a");
# Example 6:
# $keychain$*3a473dd308b1713ddc76fc976758eb543779a228*570b762ec2b177d0*a1f491231412ff74344244db4d98b1dab6e40a8fc63a11f0d5cdabf97fce5c4fa8ae0a1f95d0398d37e3d45e9fa07aa7:El Capitan
# my $pass = "El Capitan";
# my $salt = pack ("H*", "3a473dd308b1713ddc76fc976758eb543779a228");
# my $iv = pack ("H*", "570b762ec2b177d0");
# my $data = pack ("H*", "a1f491231412ff74344244db4d98b1dab6e40a8fc63a11f0d5cdabf97fce5c4fa8ae0a1f95d0398d37e3d45e9fa07aa7");
#
# Start
#
my $pbkdf2 = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
iterations => $ITERATIONS,
output_len => 24
);
# slowest part of the algorithm:
my $key = $pbkdf2->PBKDF2 ($salt, $pass);
#
# Verification:
#
my $c1 = Crypt::CBC->new ({
key => substr ($key, 0, 8),
iv => "\x00" x 8,
cipher => "DES",
literal_key => 1,
header => "none",
padding => "null",
});
my $c2 = Crypt::CBC->new ({
key => substr ($key, 8, 8),
iv => "\x00" x 8,
cipher => "DES",
literal_key => 1,
header => "none",
padding => "null",
});
my $c3 = Crypt::CBC->new ({
key => substr ($key, 16, 8),
iv => "\x00" x 8,
cipher => "DES",
literal_key => 1,
header => "none",
padding => "null",
});
# fixed 48 byte output (but we only need last block):
# my $data_decrypted = "";
#
# for (my ($i, $j) = (0, 0); $i < 6; $i += 1, $j += 8)
# {
# my $d = substr ($data, $j, 8);
#
# my $t;
#
# $t = $c3->decrypt ($d);
# $t = $c2->encrypt ($t);
# $t = $c1->decrypt ($t);
#
# $t ^= $iv;
#
# $data_decrypted .= $t;
#
# $iv = $d
# }
# shortcut for padding check/attack:
my $d;
$iv = substr ($data, 32, 8);
$d = substr ($data, 40, 8);
my $t;
$t = $c3->decrypt ($d);
$t = $c2->encrypt ($t);
$t = $c1->decrypt ($t);
$t ^= $iv;
if (substr ($t, 4, 4) eq "\x04" x 4) # "\x04\x04\x04\x04"
{
print "password found: '$pass'\n";
exit (0);
}
exit (1); So my questions basically are:
Thanks |
@philsmd Thank you for your reaction. I tested successfully your script with 5 real-life login.keychains from different devices and macOS versions; they all give the expected output "password found" 2.&3. I do not know if this will always work, the risk to have a collision exists but is - I think - negligible. Let me explain something I noticed while testing.
This gives the following output :
a) the IV as mentioned in the extracted hash is not needed, instead we use 8 times So far, so good. One of the authors of the paper is n0fate who wrote the tool chainbreaker. This tool expects the login.keychain and a password as input. Afterwards, it decrypts all the records in the file. It would be interesting to see how his script does the password-check, right ?
This gives the following output:
a) the IV as mentioned in the extracted hash is now used, even as the whole data So, in the two cases, there is padding which is checked in order to validate the user password. I do not understand how it is possible that two completely different techniques give both an output with same padding. Maybe you know an explanation for this? |
@Banaanhangwagen yeah, these are good observations, but it's actually all about what I emphasized with my comment around At the end, it's all about the CBC mode of operation (see https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC) and https://upload.wikimedia.org/wikipedia/commons/2/2a/CBC_decryption.svg) For the first block you need the initialization vector (IV) from the hash generated by keychain2john.py... but if you use the Therefore, for the shortcut you use just the last block as data and the second to last block as an "IV", if you want to decrypt the whole data instead, you need to start from the beginning with the IV from the hash. Of course the shortcut is (in theory) much faster... we do not decrypt the whole data, but just the last 8 bytes.... in practice this change/speedup is negligible for 2 reasons:
I've discussed the problem of the likelihood of collisions for this hash format actually just a couple of days ago on a private chat with @jsteube on IRC and we actually did try to do the math correctly and see how high the rate of collisions could be:
So there you have it, at least every 3 minutes you will find a new password that could likely be a wrong password with the system above (especially if you try random passwords, i.e. use mask attack, brute-force it... with dictionary based attacks, it's a little bit lower, because of the less randomness in the input and the "slighlty" slower speed in -a 0/-a 1). I therefore wouldn't say collisions are rare with only 32 bits to validate and "only" 1000 iteration of PBKDF2-HMAC-SHA1 (and output length 24 !!! <- yeah, that's important for speed ;) ). We could still implement it, but we actually should really emphasize the importance of using --keep-guessing with this format (and of course it would be better if we find some further check we can perform, but the other problem is that we won't really use an incompatible format compared to JTR.... but our collaboration between JTR<->hc is actually quite good... therefore if we find a further check we can do, we could suggest JTR to update keychain2john.py and we both could be happier and need to worry less about collisions)... btw: even a slightly greater padding, e.g. if it would be instead of 4 bytes |
btw: I just noticed that I completely forgot to mention that there is actually a third (and actually the better way to decrypt the last block)... instead of setting the initialization vector to I only did that strange way of setting the IV to the zero IV because it's sometimes easier in perl to avoid setting That means in python it would also work like this:
here you see immediately that the "correct iv" is used straight away (the ciphertext of the second to last block). |
Okay, I've now decided to try to implement this new hash type with |
Thank you for your time and effort (and the small masterclass in encryption ;) ) I successfully cracked all my examples with Finally, when doing a benchmark, we see that |
wow, thank you very much for this quick responds and intensive testing. Yeah, I agree that the new keychain hash type could be a good/quick alternative to the "normal" macOS user hashes... I'm not sure if we can directly compare them, because the iteration count matters a lot. I'm actually curious how macOS chooses the default iteration count. Is this a fixed value (for -m 7100 iterations) ? does it depend on the hardware performance or specs ? does it depend on the macOS version (maybe newer macOS versions have higher iteration count) ? what is the current default ? I know we have some default iteration count for -m 7100 and example hashes with (quite low ?) iteration count.... but I'm not sure if (new and) real word examples also have low iterations count and if they are "always the same" for -m 7100 hashes nowadays. It would be actually great if we had a wiki page with this information, about how iteration counts are determined depending on the hash type, if they are fixed or user-configurable or hardware-dependent etc.... we actually have several supported hash types like this... most notable the password managers hashes (keepass etc)... they often depend on the initial computer the encrypted file that contains the passwords was generated with (but not always the case). The problem with such a wiki page would be, that it could very soon be outdated and there is nobody that can test each and every of those listed hash type regularily (and maybe even on different operating systems etc)... so it would be interesting to know these details, but hard to maintain an up-to-date and correct list. Thank you |
fixes #2457: added -m 23100 = Apple Keychain
Hi,
Look like hashcat does not support$keychain$ hash outputs from keychain2john.py .
This file is a keepass-like for Mac OS.
Could you please add this mode ? Based on PBKDF2-SHA1 3DES.
Thanks a lot.
Sample input file : https://openwall.info/wiki/_media/john/Keychain-Samples.zip
Sample hash:password :
$keychain$*10f7445c8510fa40d9ef6b4e0f8c772a9d37e449*f3d19b2a45cdcccb*8c3c3b1c7d48a24dad4ccbd4fd794ca9b0b3f1386a0a4527f3548bfe6e2f1001804b082076641bbedbc9f3a7c33c084b:passsord
$keychain$*salt*iv*ciphertext
The text was updated successfully, but these errors were encountered: