electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit c9bf1d4c800d8a07ae04de97f27cee51707f10de
parent f265acd2348697170ec7d23af69ebb7af91de0e7
Author: SomberNight <somber.night@protonmail.com>
Date:   Tue,  1 Sep 2020 18:15:37 +0200

scripts: add script to showcase bruteforcing wallet file password

Diffstat:
Aelectrum/scripts/bruteforce_pw.py | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+), 0 deletions(-)

diff --git a/electrum/scripts/bruteforce_pw.py b/electrum/scripts/bruteforce_pw.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# +# This script is just a demonstration how one could go about bruteforcing an +# Electrum wallet file password. As it is pure-python and runs in the CPU, +# it is horribly slow. It could be changed to utilise multiple threads +# but any serious attempt would need at least GPU acceleration. +# +# There are two main types of password encryption that need to be disambiguated +# for Electrum wallets: +# (1) keystore-encryption: The wallet file itself is mostly plaintext (json), +# only the Bitcoin private keys themselves are encrypted. +# (e.g. seed words, xprv are encrypted; addresses are not) +# Even in memory (at runtime), the private keys are typically +# stored encrypted, and only when needed the user is prompted +# for their password to decrypt the keys briefly. +# (2) storage-encryption: The file itself is encrypted. When opened in a text editor, +# it is base64 ascii text. Normally storage-encrypted wallets +# also have keystore-encryption (unless they don't have private keys). +# Storage-encryption was introduced in Electrum 2.8, keystore-encryption predates that. +# Newly created wallets in modern Electrum have storage-encryption enabled by default. +# +# Storage encryption uses a stronger KDF than keystore-encryption. +# As is, this script can test around ~1000 passwords per second for storage-encryption. + +import sys +from string import digits, ascii_uppercase, ascii_lowercase +from itertools import product +from typing import Callable +from functools import partial + +from electrum.wallet import Wallet, Abstract_Wallet +from electrum.storage import WalletStorage +from electrum.wallet_db import WalletDB +from electrum.simple_config import SimpleConfig +from electrum.util import InvalidPassword + + +ALLOWED_CHARS = digits + ascii_uppercase + ascii_lowercase +MAX_PASSWORD_LEN = 12 + + +def test_password_for_storage_encryption(storage: WalletStorage, password: str) -> bool: + try: + storage.decrypt(password) + except InvalidPassword: + return False + else: + return True + + +def test_password_for_keystore_encryption(wallet: Abstract_Wallet, password: str) -> bool: + try: + wallet.check_password(password) + except InvalidPassword: + return False + else: + return True + + +def bruteforce_loop(test_password: Callable[[str], bool]) -> str: + num_tested = 0 + for pw_len in range(1, MAX_PASSWORD_LEN + 1): + for pw_tuple in product(ALLOWED_CHARS, repeat=pw_len): + password = "".join(pw_tuple) + if test_password(password): + return password + num_tested += 1 + if num_tested % 5000 == 0: + print(f"> tested {num_tested} passwords so far... most recently tried: {password!r}") + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("ERROR. usage: bruteforce_pw.py <path_to_wallet_file>") + sys.exit(1) + path = sys.argv[1] + + config = SimpleConfig() + storage = WalletStorage(path) + if not storage.file_exists(): + print(f"ERROR. wallet file not found at path: {path}") + sys.exit(1) + if storage.is_encrypted(): + test_password = partial(test_password_for_storage_encryption, storage) + print(f"wallet found: with storage encryption.") + else: + db = WalletDB(storage.read(), manual_upgrades=True) + wallet = Wallet(db, storage, config=config) + if not wallet.has_password(): + print("wallet found but it is not encrypted.") + sys.exit(0) + test_password = partial(test_password_for_keystore_encryption, wallet) + print(f"wallet found: with keystore encryption.") + password = bruteforce_loop(test_password) + print(f"====================") + print(f"password found: {password}")