forked from openwall/john
-
Notifications
You must be signed in to change notification settings - Fork 0
/
electrum2john.py
executable file
·242 lines (214 loc) · 9.33 KB
/
electrum2john.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/env python
# This software is Copyright (c) 2017, Dhiru Kholia <dhiru.kholia at gmail.com>
# and it is hereby released under GPL v2 license.
#
# Major parts are borrowed from the "btcrecover" program which is,
# Copyright (C) 2014-2016 Christopher Gurnee and under GPL v2.
#
# See https://github.com/gurnec/btcrecover for details.
#
# References,
#
# https://github.com/gurnec/btcrecover/blob/master/btcrecover/btcrpass.py
# https://github.com/spesmilo/electrum (see 1.9.8 version)
import os
import sys
# import traceback
import base64
import binascii
import itertools
import optparse
try:
import json
assert json
except ImportError:
try:
sys.path.append(".")
import simplejson as json
except ImportError:
sys.stderr.write("Please install json / simplejson module which is currently not installed.\n")
sys.exit(1)
def process_electrum28_wallets(bname, data, options):
version = 4 # hack
MIN_LEN = 37 + 32 + 32 # header + ciphertext + trailer
if len(data) < MIN_LEN * 4 / 3:
sys.stderr.write("%s: Electrum 2.8+ wallet is too small to parse!\n" % bname)
return
data = base64.b64decode(data)
ephemeral_pubkey = data[4:37] # compressed representation
# ciphertext = data[37:-32]
mac = data[-32:]
all_but_mac = data[:-32]
if len(all_but_mac) > 16384 or options.truncate:
sys.stderr.write("Forcing generation of truncated hash, this is not tested well!\n")
all_but_mac = data[37:][:1024] # skip over the 4-byte magic & 33-byte pubkey
version = 5 # hack
ephemeral_pubkey = binascii.hexlify(ephemeral_pubkey).decode("ascii")
mac = binascii.hexlify(mac).decode("ascii")
all_but_mac = binascii.hexlify(all_but_mac).decode("ascii")
sys.stdout.write("%s:$electrum$%d*%s*%s*%s\n" % (bname, version, ephemeral_pubkey, all_but_mac, mac))
def process_file(filename, options):
bname = os.path.basename(filename)
try:
f = open(filename, "rb")
data = f.read()
except IOError:
e = sys.exc_info()[1]
sys.stderr.write("%s\n" % str(e))
return
# detect Electrum 2.7+ encrypted wallets
try:
if base64.b64decode(data).startswith(b'BIE1'):
# sys.stderr.write("%s: Encrypted Electrum 2.8+ wallets are not supported yet!\n" % bname)
process_electrum28_wallets(bname, data, options)
return
except:
# traceback.print_exc()
pass
try:
data = data.decode("utf-8")
except:
e = sys.exc_info()[1]
sys.stderr.write("%s\n" % str(e))
return -13
version = None
try:
wallet = json.loads(data)
except:
try:
from ast import literal_eval # hack for Electrum 1.x wallets
wallet = literal_eval(data)
version = 1
except:
sys.stderr.write("%s: Unable to parse the wallet file!\n" % bname)
# traceback.print_exc()
return
# This check applies for both Electrum 2.x and 1.x
if "use_encryption" in wallet and wallet.get("use_encryption") == False:
sys.stderr.write("%s: Electrum wallet is not encrypted!\n" % bname)
return
# Is this an upgraded wallet, from 1.x to 2.y (y<7)?
if "wallet_type" in wallet and wallet["wallet_type"] == "old":
sys.stderr.write("%s: Upgraded wallet found!\n" % bname)
version = 1 # hack
if version == 1:
try:
seed_version = wallet["seed_version"]
seed_data = base64.b64decode(wallet["seed"])
if len(seed_data) != 64:
sys.stderr.write("%s: Weird seed length value '%d' found!\n" % (bname, len(seed_data)))
return
if seed_version == 4:
iv = seed_data[:16]
encrypted_data = seed_data[16:32]
iv = binascii.hexlify(iv).decode("ascii")
encrypted_data = binascii.hexlify(encrypted_data).decode("ascii")
sys.stdout.write("%s:$electrum$1*%s*%s\n" % (bname, iv, encrypted_data))
return
else:
sys.stderr.write("%s: Unknown seed_version value '%d' found!\n" % (bname, seed_version))
return
except:
sys.stderr.write("%s: Problem in parsing seed value!\n" % (bname, seed_version))
return
# not version 1 wallet
wallet_type = wallet.get("wallet_type")
if not wallet_type:
sys.stderr.write("%s: Unrecognized wallet format!\n" % (bname))
return
if wallet.get("seed_version") < 11 and wallet_type != "imported": # all 2.x versions as of Oct 2016
sys.stderr.write("%s: Unsupported Electrum2 seed version '%d' found!\n" % (bname, wallet.get("seed_version")))
return
xprv = None
version = 2 # hack
while True: # "loops" exactly once; only here so we've something to break out of
# Electrum 2.7+ standard wallets have a keystore
keystore = wallet.get("keystore")
if keystore:
keystore_type = keystore.get("type", "(not found)")
# Wallets originally created by an Electrum 2.x version
if keystore_type == "bip32":
xprv = keystore.get("xprv")
if xprv:
break
# Former Electrum 1.x wallet after conversion to Electrum 2.7+ standard-wallet format
elif keystore_type == "old":
seed_data = keystore.get("seed")
if seed_data:
# Construct and return a WalletElectrum1 object
seed_data = base64.b64decode(seed_data)
if len(seed_data) != 64:
raise RuntimeError("Electrum1 encrypted seed plus iv is not 64 bytes long")
iv = seed_data[:16] # only need the 16-byte IV plus
encrypted_data = seed_data[16:32] # the first 16-byte encrypted block of the seed
version = 1 # hack
break
# Imported loose private keys
elif keystore_type == "imported":
for privkey in keystore["keypairs"].values():
if privkey:
privkey = base64.b64decode(privkey)
if len(privkey) != 80:
raise RuntimeError("Electrum2 private key plus iv is not 80 bytes long")
iv = privkey[-32:-16] # only need the 16-byte IV plus
encrypted_data = privkey[-16:] # the last 16-byte encrypted block of the key
version = 3 # dirty hack!
break
if version == 3: # another dirty hack, break out of outer loop
break
else:
sys.stderr.write("%s: found unsupported keystore type!\n" % (bname))
# Electrum 2.7+ multisig or 2fa wallet
for i in itertools.count(1):
x = wallet.get("x{}/".format(i))
if not x:
break
x_type = x.get("type", "(not found)")
if x_type == "bip32":
xprv = x.get("xprv")
if xprv:
break
else:
sys.stderr.write("%s: found unsupported keystore type!\n" % (bname))
if xprv:
break
# Electrum 2.0 - 2.6.4 wallet with imported loose private keys
if wallet_type == "imported":
for imported in wallet["accounts"]["/x"]["imported"].values():
privkey = imported[1] if len(imported) >= 2 else None
if privkey:
privkey = base64.b64decode(privkey)
if len(privkey) != 80:
raise RuntimeError("Electrum2 private key plus iv is not 80 bytes long")
iv = privkey[-32:-16] # only need the 16-byte IV plus
encrypted_data = privkey[-16:] # the last 16-byte encrypted block of the key
version = 3 # dirty hack
break
if version == 3: # another dirty hack, break out of outer loop
break
# Electrum 2.0 - 2.6.4 wallet (of any other wallet type)
else:
mpks = wallet.get("master_private_keys")
if mpks:
xprv = mpks.values()[0]
break
raise RuntimeError("No master private keys or seeds found in Electrum2 wallet")
if xprv:
xprv_data = base64.b64decode(xprv)
if len(xprv_data) != 128:
raise RuntimeError("Unexpected Electrum2 encrypted master private key length")
iv = xprv_data[:16] # only need the 16-byte IV plus
encrypted_data = xprv_data[16:32] # the first 16-byte encrypted block of a master privkey
iv = binascii.hexlify(iv).decode("ascii")
encrypted_data = binascii.hexlify(encrypted_data).decode("ascii")
sys.stdout.write("%s:$electrum$%d*%s*%s\n" % (bname, version, iv, encrypted_data))
f.close()
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.stderr.write("Usage: %s [Electrum Wallet files (default_wallet)]\n" % sys.argv[0])
sys.exit(1)
parser = optparse.OptionParser()
parser.add_option('-t', action="store_true", dest="truncate", help="force generation of truncated hashes")
options, remainder = parser.parse_args()
for j in range(0, len(remainder)):
process_file(remainder[j], options)