|
| 1 | +#!/usr/bin/env python |
| 2 | +# |
| 3 | +# BIP38 password scanner with tokens |
| 4 | +# |
| 5 | + |
| 6 | +import sys, time, multiprocessing |
| 7 | +import hashlib, base64, binascii, ecdsa |
| 8 | + |
| 9 | +alphabet="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" |
| 10 | + |
| 11 | +# secp256k1, http://www.oid-info.com/get/1.3.132.0.10 |
| 12 | +_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL |
| 13 | +_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L |
| 14 | +_b = 0x0000000000000000000000000000000000000000000000000000000000000007L |
| 15 | +_a = 0x0000000000000000000000000000000000000000000000000000000000000000L |
| 16 | +_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L |
| 17 | +_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L |
| 18 | +curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) |
| 19 | +generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) |
| 20 | +oid_secp256k1 = (1,3,132,0,10) |
| 21 | +SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) |
| 22 | + |
| 23 | +def b58encode(num, pad=''): |
| 24 | + out = '' |
| 25 | + while num >= 58: |
| 26 | + num,m = divmod(num, 58) |
| 27 | + out = alphabet[m] + out |
| 28 | + return pad + alphabet[num] + out |
| 29 | + |
| 30 | +def b58hex(s58): |
| 31 | + num = 0L |
| 32 | + for (i, c) in enumerate(s58[::-1]): |
| 33 | + num += alphabet.find(c) * (58**i) |
| 34 | + return hex(num)[2:-1].upper() |
| 35 | + |
| 36 | +def priv2pub(priv, compress=False): |
| 37 | + verkey = ecdsa.SigningKey.from_secret_exponent(long(priv, 16), curve=SECP256k1 ).get_verifying_key() |
| 38 | + prefix = '04' if not compress else ( '03' if verkey.pubkey.point.y() % 2 == 1 else '02' ) |
| 39 | + return prefix + ( '%032x' % verkey.pubkey.point.x() if compress else verkey.to_string() ) |
| 40 | + |
| 41 | +def mkaddr(pubkey): |
| 42 | + pad = "" |
| 43 | + rmd = hashlib.new('ripemd160') |
| 44 | + rmd.update(hashlib.sha256(pubkey).digest()) |
| 45 | + an = chr(0) + rmd.digest() |
| 46 | + for c in an: |
| 47 | + if c == '\0': pad += '1' |
| 48 | + else: break |
| 49 | + return b58encode(long(binascii.hexlify(an + hashlib.sha256(hashlib.sha256(an).digest()).digest()[0:4]), 16), pad) |
| 50 | + |
| 51 | +def bip38_decrypt(encrypted_privkey, passphrase): |
| 52 | + from Crypto.Cipher import AES |
| 53 | + import scrypt |
| 54 | + |
| 55 | + data = binascii.unhexlify("0"+b58hex(encrypted_privkey)) |
| 56 | + compress = '' if data[2] == '\xc0' else '01' |
| 57 | + key = scrypt.hash(passphrase, data[3:7], 16384, 8, 8) |
| 58 | + aes = AES.new(key[32:64]) |
| 59 | + priv = aes.decrypt(data[7:23]) + aes.decrypt(data[23:39]) |
| 60 | + priv = '%064x' % (long(binascii.hexlify(priv), 16) ^ long(binascii.hexlify(key[0:32]), 16)) |
| 61 | + pub = priv2pub(priv, data[2] != '\xc0') |
| 62 | + if hashlib.sha256(hashlib.sha256(mkaddr(pub)).digest()).digest()[0:4] != data[3:7]: |
| 63 | + return None |
| 64 | + chksum = binascii.hexlify(hashlib.sha256(hashlib.sha256(binascii.unhexlify('80'+priv)).digest()).digest()[:4]) |
| 65 | + return b58encode(long('80'+priv+chksum+compress, 16)) |
| 66 | + |
| 67 | +def scanproc(pwd): |
| 68 | + for token in tokens: |
| 69 | + privkey = bip38_decrypt( sys.argv[2], pwd+token ) |
| 70 | + if privkey == None: |
| 71 | + continue |
| 72 | + else: |
| 73 | + print "\nPWD:", pwd+token |
| 74 | + print "KEY:", privkey |
| 75 | + print "Took %.1f seconds" % (time.time() - started,) |
| 76 | + sys.exit(2) |
| 77 | + |
| 78 | +def pwd_scan(pwd, depth): |
| 79 | + global done, procs |
| 80 | + if len(procs) == max_procs: |
| 81 | + for p in procs: |
| 82 | + p.join() |
| 83 | + if p.exitcode == 2: |
| 84 | + done = True |
| 85 | + procs = [] |
| 86 | + if not done: |
| 87 | + if depth == 0: |
| 88 | + proc = multiprocessing.Process(target = scanproc, args = (pwd,)) |
| 89 | + procs.append(proc) |
| 90 | + proc.start() |
| 91 | + else: |
| 92 | + for token in tokens: |
| 93 | + pwd_scan(pwd+token, depth-1) |
| 94 | + |
| 95 | +if __name__ == '__main__': |
| 96 | + |
| 97 | + max_procs = multiprocessing.cpu_count() |
| 98 | + procs = [] |
| 99 | + done = False |
| 100 | + |
| 101 | + if len(sys.argv) < 3: |
| 102 | + print "BIP38 password scanner (multi-processing)" |
| 103 | + print "Reads tokens from file and tries every combination as password for BIP38 key" |
| 104 | + print "Default depth is 6. ie. scan up to 6 token combinations." |
| 105 | + print "Usage: %s <token file> <BIP38 encoded key> [depth]\n" % sys.argv[0] |
| 106 | + sys.exit(0) |
| 107 | + |
| 108 | + with open(sys.argv[1]) as f: |
| 109 | + tokens = f.read() |
| 110 | + tokens = tokens.split() |
| 111 | + |
| 112 | + started = time.time() |
| 113 | + depth = int(sys.argv[3]) if len(sys.argv) > 3 else 6 |
| 114 | + for n in range(depth): |
| 115 | + pwd_scan("", n) |
| 116 | + |
| 117 | + for p in procs: |
| 118 | + p.join() |
| 119 | + if p.exitcode == 2: |
| 120 | + done = True |
| 121 | + |
| 122 | + if not done: |
| 123 | + print "Password not found" |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | + |
| 130 | + |
0 commit comments