bruteforce_pw.py (3913B)
1 #!/usr/bin/env python3 2 # 3 # This script is just a demonstration how one could go about bruteforcing an 4 # Electrum wallet file password. As it is pure-python and runs in the CPU, 5 # it is horribly slow. It could be changed to utilise multiple threads 6 # but any serious attempt would need at least GPU acceleration. 7 # 8 # There are two main types of password encryption that need to be disambiguated 9 # for Electrum wallets: 10 # (1) keystore-encryption: The wallet file itself is mostly plaintext (json), 11 # only the Bitcoin private keys themselves are encrypted. 12 # (e.g. seed words, xprv are encrypted; addresses are not) 13 # Even in memory (at runtime), the private keys are typically 14 # stored encrypted, and only when needed the user is prompted 15 # for their password to decrypt the keys briefly. 16 # (2) storage-encryption: The file itself is encrypted. When opened in a text editor, 17 # it is base64 ascii text. Normally storage-encrypted wallets 18 # also have keystore-encryption (unless they don't have private keys). 19 # Storage-encryption was introduced in Electrum 2.8, keystore-encryption predates that. 20 # Newly created wallets in modern Electrum have storage-encryption enabled by default. 21 # 22 # Storage encryption uses a stronger KDF than keystore-encryption. 23 # As is, this script can test around ~1000 passwords per second for storage-encryption. 24 25 import sys 26 from string import digits, ascii_uppercase, ascii_lowercase 27 from itertools import product 28 from typing import Callable 29 from functools import partial 30 31 from electrum.wallet import Wallet, Abstract_Wallet 32 from electrum.storage import WalletStorage 33 from electrum.wallet_db import WalletDB 34 from electrum.simple_config import SimpleConfig 35 from electrum.util import InvalidPassword 36 37 38 ALLOWED_CHARS = digits + ascii_uppercase + ascii_lowercase 39 MAX_PASSWORD_LEN = 12 40 41 42 def test_password_for_storage_encryption(storage: WalletStorage, password: str) -> bool: 43 try: 44 storage.decrypt(password) 45 except InvalidPassword: 46 return False 47 else: 48 return True 49 50 51 def test_password_for_keystore_encryption(wallet: Abstract_Wallet, password: str) -> bool: 52 try: 53 wallet.check_password(password) 54 except InvalidPassword: 55 return False 56 else: 57 return True 58 59 60 def bruteforce_loop(test_password: Callable[[str], bool]) -> str: 61 num_tested = 0 62 for pw_len in range(1, MAX_PASSWORD_LEN + 1): 63 for pw_tuple in product(ALLOWED_CHARS, repeat=pw_len): 64 password = "".join(pw_tuple) 65 if test_password(password): 66 return password 67 num_tested += 1 68 if num_tested % 5000 == 0: 69 print(f"> tested {num_tested} passwords so far... most recently tried: {password!r}") 70 71 72 if __name__ == '__main__': 73 if len(sys.argv) < 2: 74 print("ERROR. usage: bruteforce_pw.py <path_to_wallet_file>") 75 sys.exit(1) 76 path = sys.argv[1] 77 78 config = SimpleConfig() 79 storage = WalletStorage(path) 80 if not storage.file_exists(): 81 print(f"ERROR. wallet file not found at path: {path}") 82 sys.exit(1) 83 if storage.is_encrypted(): 84 test_password = partial(test_password_for_storage_encryption, storage) 85 print(f"wallet found: with storage encryption.") 86 else: 87 db = WalletDB(storage.read(), manual_upgrades=True) 88 wallet = Wallet(db, storage, config=config) 89 if not wallet.has_password(): 90 print("wallet found but it is not encrypted.") 91 sys.exit(0) 92 test_password = partial(test_password_for_keystore_encryption, wallet) 93 print(f"wallet found: with keystore encryption.") 94 password = bruteforce_loop(test_password) 95 print(f"====================") 96 print(f"password found: {password}")