-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Expand file tree
/
Copy pathbitcoin2john.py
More file actions
executable file
·280 lines (220 loc) · 10.1 KB
/
Copy pathbitcoin2john.py
File metadata and controls
executable file
·280 lines (220 loc) · 10.1 KB
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/env python
# This software is
# Copyright (c) 2012-2018 Dhiru Kholia <dhiru at openwall.com>
# Copyright (c) 2019 Solar Designer
# Copyright (c) 2019 exploide
# Copyright (c) 2025 Maxim Kuleshov <mmcx@mail.ru>
# Redistribution and use in source and binary forms, with or without
# modification, are permitted. (This is a heavily cut-down "BSD license".)
#
# While the above applies to the stated copyright holders' contributions,
# this software is also dual-licensed under the MIT License, to be certain
# of license compatibility with that of the components listed below.
#
# This script (bitcoin2john.py) might still contain portions of jackjack's
# pywallet.py [1] which is forked from Joric's pywallet.py whose licensing
# information follows,
#
# [1] https://github.com/jackjack-jj/pywallet
#
# PyWallet 1.2.1 (Public Domain)
# http://github.com/joric/pywallet
# Most of the actual PyWallet code placed in the public domain.
# PyWallet includes portions of free software, listed below.
#
# BitcoinTools (wallet.dat handling code, MIT License)
# https://github.com/gavinandresen/bitcointools
# Copyright (c) 2010 Gavin Andresen
import binascii
import logging
import struct
import sys
import sqlite3
bsddb_db = None
def do_import_bsddb():
global bsddb_db
if bsddb_db is not None:
return
try:
import bsddb.db as bsddb_db
except:
try:
import bsddb3.db as bsddb_db
except:
sys.stderr.write("Error: This script needs bsddb3 to be installed!\n")
sys.exit(1)
json_db = {}
def hexstr(bytestr):
return binascii.hexlify(bytestr).decode('ascii')
# bitcointools wallet.dat handling code
class SerializationError(Exception):
""" Thrown when there's a problem deserializing or serializing """
class BCDataStream(object):
def __init__(self):
self.input = None
self.read_cursor = 0
def clear(self):
self.input = None
self.read_cursor = 0
def write(self, bytes): # Initialize with string of bytes
if self.input is None:
self.input = bytes
else:
self.input += bytes
def read_string(self):
# Strings are encoded depending on length:
# 0 to 252 : 1-byte-length followed by bytes (if any)
# 253 to 65,535 : byte'253' 2-byte-length followed by bytes
# 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
# ... and the Bitcoin client is coded to understand:
# greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
# ... but I don't think it actually handles any strings that big.
if self.input is None:
raise SerializationError("call write(bytes) before trying to deserialize")
try:
length = self.read_compact_size()
except IndexError:
raise SerializationError("attempt to read past end of buffer")
return self.read_bytes(length).decode('ascii')
def read_bytes(self, length):
try:
result = self.input[self.read_cursor:self.read_cursor + length]
self.read_cursor += length
return result
except IndexError:
raise SerializationError("attempt to read past end of buffer")
return ''
def read_uint32(self): return self._read_num('<I')
def read_compact_size(self):
size = self.input[self.read_cursor]
if isinstance(size, str):
size = ord(self.input[self.read_cursor])
self.read_cursor += 1
if size == 253:
size = self._read_num('<H')
elif size == 254:
size = self._read_num('<I')
elif size == 255:
size = self._read_num('<Q')
return size
def _read_num(self, format):
(i,) = struct.unpack_from(format, self.input, self.read_cursor)
self.read_cursor += struct.calcsize(format)
return i
class Sqlite3DB:
def __init__(self, walletfile):
self.cx = sqlite3.connect(walletfile)
def is_sqlite3(self):
try:
self.cx.execute("PRAGMA quick_check")
return True
except sqlite3.DatabaseError:
return False
def close(self):
self.cx.close()
def items(self):
return list(self.cx.execute('SELECT key,value FROM main'))
def open_wallet(walletfile):
db = Sqlite3DB(walletfile)
if db.is_sqlite3():
return db
do_import_bsddb()
db = bsddb_db.DB()
DB_TYPEOPEN = bsddb_db.DB_RDONLY
flags = bsddb_db.DB_THREAD | DB_TYPEOPEN
try:
r = db.open(walletfile, "main", bsddb_db.DB_BTREE, flags)
except bsddb_db.DBError as e:
logging.error(e)
r = True
if r is not None:
logging.error("Couldn't open wallet.dat/main. Try quitting Bitcoin and running this again.")
logging.error("See our doc/README.bitcoin for how to setup and use this script correctly.")
sys.exit(1)
return db
def parse_wallet(db, item_callback):
kds = BCDataStream()
vds = BCDataStream()
for (key, value) in db.items():
d = { }
kds.clear(); kds.write(key)
vds.clear(); vds.write(value)
type = kds.read_string()
d["__key__"] = key
d["__value__"] = value
d["__type__"] = type
try:
if type == "mkey":
#d['nID'] = kds.read_uint32()
d['encrypted_key'] = vds.read_bytes(vds.read_compact_size())
d['salt'] = vds.read_bytes(vds.read_compact_size())
d['nDerivationMethod'] = vds.read_uint32()
d['nDerivationIterations'] = vds.read_uint32()
#d['otherParams'] = vds.read_string()
item_callback(type, d)
except Exception:
sys.stderr.write("ERROR parsing wallet.dat, type %s\n" % type)
sys.stderr.write("key data in hex: %s\n" % hexstr(key))
sys.stderr.write("value data in hex: %s\n" % hexstr(value))
sys.exit(1)
# end of bitcointools wallet.dat handling code
# wallet.dat reader
def read_wallet(json_db, walletfile):
db = open_wallet(walletfile)
json_db['mkey'] = {}
def item_callback(type, d):
if type == "mkey":
#json_db['mkey']['nID'] = d['nID']
json_db['mkey']['encrypted_key'] = hexstr(d['encrypted_key'])
json_db['mkey']['salt'] = hexstr(d['salt'])
json_db['mkey']['nDerivationMethod'] = d['nDerivationMethod']
json_db['mkey']['nDerivationIterations'] = d['nDerivationIterations']
#json_db['mkey']['otherParams'] = d['otherParams']
parse_wallet(db, item_callback)
db.close()
crypted = 'salt' in json_db['mkey']
if not crypted:
sys.stderr.write("%s: this wallet is not encrypted\n" % walletfile)
return -1
return {'crypted':crypted}
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.stderr.write("Usage: %s [Bitcoin/Litecoin/PRiVCY wallet (.dat) files]\n" % sys.argv[0])
sys.exit(1)
for i in range(1, len(sys.argv)):
filename = sys.argv[i]
if read_wallet(json_db, filename) == -1:
continue
cry_master = binascii.unhexlify(json_db['mkey']['encrypted_key'])
cry_salt = binascii.unhexlify(json_db['mkey']['salt'])
cry_rounds = json_db['mkey']['nDerivationIterations']
cry_method = json_db['mkey']['nDerivationMethod']
crypted = 'salt' in json_db['mkey']
if not crypted:
sys.stderr.write("%s: this wallet is not encrypted\n" % filename)
continue
if cry_method != 0:
sys.stderr.write("%s: this wallet uses unknown key derivation method\n" % filename)
continue
cry_salt = json_db['mkey']['salt']
if len(cry_salt) == 16:
expected_mkey_len = 96 # 32 bytes padded to 3 AES blocks (last block is padding-only)
elif len(cry_salt) == 36: # Nexus legacy wallet
expected_mkey_len = 160 # 72 bytes padded to whole AES blocks
else:
sys.stderr.write("%s: this wallet uses unsupported salt size\n" % filename)
continue
# When cracking we only use the last two AES blocks, and thus we could support
# any encrypted master key size of 32 bytes (64 hex) or more. However, there's
# no reliable way for us to infer what the unencrypted key size was before it
# got padded to whole AES blocks, and thus no way for us to confidently detect
# correct guesses by checking the last block's padding. We rely on that check
# for expected encrypted master key sizes (assuming that 48 was 32, and 80 was
# 72, like specific known wallets use), but we don't dare to do that for
# unexpected sizes where we'd very likely end up with 100% (false) negatives.
if len(json_db['mkey']['encrypted_key']) != expected_mkey_len:
sys.stderr.write("%s: this wallet uses unsupported master key size\n" % filename)
continue
cry_master = json_db['mkey']['encrypted_key'][-64:] # last two AES blocks are enough
sys.stdout.write("$bitcoin$%s$%s$%s$%s$%s$2$00$2$00\n" %
(len(cry_master), cry_master, len(cry_salt), cry_salt, cry_rounds))