Posts: 9
Threads: 1
Joined: Jan 2020
Hello everyone,
i want to use hashcat to brute-force a password created by multibit classic bitcoin wallet. The wallet key backup uses the following openssl method to generate the backup:
openssl enc -p -aes-256-cbc -a -in \<plaintext file\> -out \<ciphertext file\> -pass pass:\<password\>
From my little knowledge, this is base64 decoded, salted with MD5 hash, so it could run really fast on GPUs.
In the multibit wiki, the followin is stated:
The whole private key file is encrypted. This uses AES256 with a common standardized Password-Based Key Derivation Function. For maximum compatibility I have used the same encryption methodology as in OpenSSL so you can also encrypt and decrypt the files using the command line utility 'openssl'.
Is it possible to use hashcat with it?
Posts: 2,267
Threads: 16
Joined: Feb 2013
are you talking about this https://github.com/hashcat/hashcat/issues/1538 ?
this has nothing todo with the "openssl" tool at all. very different algorithm.
Posts: 9
Threads: 1
Joined: Jan 2020
(01-28-2020, 02:02 PM)philsmd Wrote: are you talking about this https://github.com/hashcat/hashcat/issues/1538 ?
this has nothing todo with the "openssl" tool at all. very different algorithm.
Not really. There seems to be some multibit version which uses scrypt, but my key backup is made from the openssl command mentioned above.
See here:
https://github.com/Multibit-Legacy/multi...ate%20keys
I can use the multibit2john python script on that, and john also is saying, that this is a MD5 based KDF.
Thank you for your help.
Posts: 9
Threads: 1
Joined: Jan 2020
01-28-2020, 02:43 PM
(This post was last modified: 01-28-2020, 02:44 PM by derlange2k.)
Examples:
encrypted key backup file from multibit:
Code: U2FsdGVkX1/Gt5+4m/DQUaahjZ1bZvpbehbiJ8RlZgScHycsuhU6vxfLMpWR1LSHoTJma6igo6eG
CnMqbPYXw9drUjK3BZ2Qo1ZVvWD8pLcaIPM3rcTLAouZjurxZE32
hash generated by multibit2john.py:
Code: hashcat-20200128133332.key:$multibit$1*c6b79fb89bf0d051*a6a18d9d5b66fa5b7a16e227c46566049c1f272cba153abf17cb329591d4b487
Decrypting with openssl: (i've had to add -md md5, otherwise my openssl would use sha1):
Code: openssl enc -d -p -aes-256-cbc -a -in hashcat.key -md md5 -out hashcat-decrypt.key -pass pass:test
Code: *** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
salt=C6B79FB89BF0D051
key=1D7E307669F6D7224294C3A69BAFD9B94771F74805E97EB8D61CE78878780444
iv =480A54B1CA593A0BDBA73B3072492B1F
out file from openssl:
Code: KzWpNf4JJC8StHZE9nYtQZpXbhDyxWypxKBUaZMcBEJSZ2oYTiZd 2020-01-21T21:58:35Z
unencrypted key backup from multibit:
Code: # KEEP YOUR PRIVATE KEYS SAFE !
# Anyone who can read this file can spend your bitcoin.
#
# Format:
# <Base58 encoded private key>[<whitespace>[<key createdAt>]]
#
# The Base58 encoded private keys are the same format as
# produced by the Satoshi client/ sipa dumpprivkey utility.
#
# Key createdAt is in UTC format as specified by ISO 8601
# e.g: 2011-12-31T16:42:00Z . The century, 'T' and 'Z' are mandatory
#
KzWpNf4JJC8StHZE9nYtQZpXbhDyxWypxKBUaZMcBEJSZ2oYTiZd 2020-01-28T12:36:13Z
# End of private keys
So it is for sure MD5 used. I simply don't know how to use it with hashcat. Any help would be really cool.
Posts: 9
Threads: 1
Joined: Jan 2020
01-28-2020, 02:59 PM
(This post was last modified: 01-28-2020, 03:36 PM by derlange2k.)
Ok, from my understanding it works that way:
The salt is:
hashcat should generate MD5 hashes with the given attack mode, salt it with the above salt and compare it with the first two AES blocks:
Code: a6a18d9d5b66fa5b7a16e227c46566049c1f272cba153abf17cb329591d4b487
Am i right?
EDIT: i've also found out, that iteration count for this is 3, if this helps.
Posts: 9
Threads: 1
Joined: Jan 2020
Maybe this can help:
Code: ############### MultiBit ###############
# - MultiBit .key backup files
# - MultiDoge .key backup files
# - Bitcoin Wallet for Android/BlackBerry v3.47+ wallet backup files
# - Bitcoin Wallet for Android/BlackBerry v2.24 and older key backup files
# - Bitcoin Wallet for Android/BlackBerry v2.3 - v3.46 key backup files
# - KnC for Android key backup files (same as the above)
@register_wallet_class
class WalletMultiBit(object):
class __metaclass__(type):
@property
def data_extract_id(cls): return b"mb"
# MultiBit private key backup file (not the wallet file)
@staticmethod
def is_wallet_file(wallet_file):
wallet_file.seek(0)
try: data = base64.b64decode(wallet_file.read(20).lstrip()[:12])
except TypeError: return False
return data.startswith(b"Salted__")
def __init__(self, loading = False):
assert loading, 'use load_from_* to create a ' + self.__class__.__name__
aes_library_name = load_aes256_library().__name__
self._passwords_per_second = 100000 if aes_library_name == "Crypto" else 5000
def __setstate__(self, state):
# (re-)load the required libraries after being unpickled
load_aes256_library(warnings=False)
self.__dict__ = state
def passwords_per_seconds(self, seconds):
return max(int(round(self._passwords_per_second * seconds)), 1)
# Load a Multibit private key backup file (the part of it we need)
@classmethod
def load_from_filename(cls, privkey_filename):
with open(privkey_filename) as privkey_file:
# Multibit privkey files contain base64 text split into multiple lines;
# we need the first 48 bytes after decoding, which translates to 64 before.
data = b"".join(privkey_file.read(70).split()) # join multiple lines into one
if len(data) < 64: raise EOFError("Expected at least 64 bytes of text in the MultiBit private key file")
data = base64.b64decode(data[:64])
assert data.startswith(b"Salted__"), "WalletBitcoinCore.load_from_filename: file starts with base64 'Salted__'"
if len(data) < 48: raise EOFError("Expected at least 48 bytes of decoded data in the MultiBit private key file")
self = cls(loading=True)
self._encrypted_block = data[16:48] # the first two 16-byte AES blocks
self._salt = data[8:16]
return self
# Import a MultiBit private key that was extracted by extract-multibit-privkey.py
@classmethod
def load_from_data_extract(cls, privkey_data):
assert len(privkey_data) == 24
print(prog + ": WARNING: read the Usage for MultiBit Classic section of Extract_Scripts.md before proceeding", file=sys.stderr)
self = cls(loading=True)
self._encrypted_block = privkey_data[8:] # a single 16-byte AES block
self._salt = privkey_data[:8]
return self
def difficulty_info(self):
return "3 MD5 iterations"
# This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password
# is correct return it, else return False for item 0; return a count of passwords checked for item 1
assert b"1" < b"9" < b"A" < b"Z" < b"a" < b"z" # the b58 check below assumes ASCII ordering in the interest of speed
def return_verified_password_or_false(self, orig_passwords):
# Copy a few globals into local for a small speed boost
l_md5 = hashlib.md5
l_aes256_cbc_decrypt = aes256_cbc_decrypt
encrypted_block = self._encrypted_block
salt = self._salt
# Convert Unicode strings (lazily) to UTF-16 bytestrings, truncating each code unit to 8 bits
if tstr == unicode:
passwords = itertools.imap(lambda p: p.encode("utf_16_le", "ignore")[::2], orig_passwords)
else:
passwords = orig_passwords
for count, password in enumerate(passwords, 1):
salted = password + salt
key1 = l_md5(salted).digest()
key2 = l_md5(key1 + salted).digest()
iv = l_md5(key2 + salted).digest()
b58_privkey = l_aes256_cbc_decrypt(key1 + key2, iv, encrypted_block[:16])
# (all this may be fragile, e.g. what if comments or whitespace precede what's expected in future versions?)
if b58_privkey[0] in b"LK5Q\x0a#":
#
# Does it look like a base58 private key (MultiBit, MultiDoge, or oldest-format Android key backup)?
if b58_privkey[0] in b"LK5Q": # private keys always start with L, K, or 5, or for MultiDoge Q
for c in b58_privkey[1:]:
# If it's outside of the base58 set [1-9A-HJ-NP-Za-km-z], break
if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl":
break
# If the loop above doesn't break, it's base58-looking so far
else:
# If another AES block is available, decrypt and check it as well to avoid false positives
if len(encrypted_block) >= 32:
b58_privkey = l_aes256_cbc_decrypt(key1 + key2, encrypted_block[:16], encrypted_block[16:32])
for c in b58_privkey:
if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl":
break # not base58
# If the loop above doesn't break, it's base58; we've found it
else:
return orig_passwords[count-1], count
else:
# (when no second block is available, there's a 1 in 300 billion false positive rate here)
return orig_passwords[count - 1], count
#
# Does it look like a bitcoinj protobuf (newest Bitcoin for Android backup)
elif b58_privkey[2:6] == b"org." and b58_privkey[0] == b"\x0a" and ord(b58_privkey[1]) < 128:
for c in b58_privkey[6:14]:
# If it doesn't look like a lower alpha domain name of len >= 8 (e.g. 'bitcoin.'), break
if c > b"z" or (c < b"a" and c != b"."):
break
# If the loop above doesn't break, it looks like a domain name; we've found it
else:
return orig_passwords[count - 1], count
#
# Does it look like a KnC for Android key backup?
elif b58_privkey == b"# KEEP YOUR PRIV":
return orig_passwords[count-1], count
return False, count
From: https://github.com/gurnec/btcrecover/blo...tcrpass.py
Posts: 2,267
Threads: 16
Joined: Feb 2013
01-29-2020, 10:26 AM
(This post was last modified: 01-29-2020, 10:33 AM by philsmd.)
I don't know about the details, but also the source code you posted says "Multibit Classic" (as the title of the github issue), so maybe there is a lot of confusion because users try to hijack github issues and nobody explains the differences between the formats.
I'm also NO python expert, but your code has 2 very strange "problems" and therefore contributes to even further confusion:
1. there is NO good verification that matches your decrypted text (the code only checks for "LK5Q", but your "plaintext" is "KzWp" as far as I understand)
2. the following code also makes NO logical sense to me (maybe I'm a python noob, but this seems like a bug in the code posted):
Code: if b58_privkey[0] in b"LK5Q\x0a#":
if b58_privkey[0] in b"LK5Q"
what is the sense to check for a substring "LK5Q", if already a longer string "LK5Q\x0a#" matches ? This is very weird code to me... maybe other guys in here (like @undeath) can make more sense of this code... but it seems to be flawed and not really working with your example "KzWp"... Did you try to use this btcrpass.py tool to crack your file ? Does it even work ?
update: I guess it's not a substring test, but it matches every character... i.e.
the char b58_privkey[0] could be "L", "K" "5", "Q" "\n" and "#"
very weird verification code indeed and there might be room for a lot of false positives if there are no stricter checks
Posts: 2,267
Threads: 16
Joined: Feb 2013
so it seems the only check is that the 32 decrypted bytes are base58 chars and the first needs to start with either L, K, 5 or Q.
I think that is approximately a chance of (58^32) / (256^32) .... it's not too bad, but could still result in rare false positives (see https://github.com/hashcat/hashcat/commi...9d8e38b69c , here we have "only" MD5, so it should be very fast... which is bad if you try very hard to reduce false positives)
Isn't there also a checksum involved with those keys or are they just random bytes converted to base58 ? Maybe not the full data needed for a checksum is within the output of multibit2john or btcrecover.
Posts: 9
Threads: 1
Joined: Jan 2020
01-29-2020, 03:32 PM
(This post was last modified: 01-29-2020, 03:43 PM by derlange2k.)
(01-29-2020, 03:12 PM)philsmd Wrote: so it seems the only check is that the 32 decrypted bytes are base58 chars and the first needs to start with either L, K, 5 or Q.
I think that is approximately a chance of (58^32) / (256^32) .... it's not too bad, but could still result in rare false positives (see https://github.com/hashcat/hashcat/commi...9d8e38b69c , here we have "only" MD5, so it should be very fast... which is bad if you try very hard to reduce false positives)
Isn't there also a checksum involved with those keys or are they just random bytes converted to base58 ? Maybe not the full data needed for a checksum is within the output of multibit2john or btcrecover.
I really appreciate your answers, philsmd, thank you.
Well, i'm not too deep into this, but as far as i know, there is no checksum involved, it is random bytes.
btcreover reports the false positive also in the README:
Code: Warning: Using the extract-multibit-privkey.py script on a MultiBit Classic key file, as described below, can lead to false positives. A false positive occurs when btcrecover reports that it has found the password, but is mistaken—the password which it displays may not be correct.
Would it be possible to integrate it in hashcat? Running it with GPU power would be very faster, i guess.
Posts: 9
Threads: 1
Joined: Jan 2020
btcrecover can operate in 2 modes:
1. Mode:
It operates on the full key backup file. This way, btcrecover can compare a second AES block:
Code: # If another AES block is available, decrypt and check it as well to avoid false positives
if len(encrypted_block) >= 32:
b58_privkey = l_aes256_cbc_decrypt(key1 + key2, encrypted_block[:16], encrypted_block[16:32])
for c in b58_privkey:
if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl":
break # not base58
# If the loop above doesn't break, it's base58; we've found it
2. Mode:
You use the extract script from btcrecover and btcreover operates on the output from the extract script. the script only extracts the first AES block. This mode is for the purpose, if you want to brute-force from a third-party, which you don't want to send the whole backup. Because in case, he is able to brute-force it, he has the wallet and is the owner of the BTC.
So in my case, i have the key backup file, so we could check the second AES block, which should avoide false positivs.
Maybe i'm wrong? With my limit coding skills i assume it works this way.
|