@@ -10,10 +10,8 @@ import java.math.BigInteger
10
10
import java.nio.ByteBuffer
11
11
import java.nio.ByteOrder
12
12
import java.nio.channels.FileChannel
13
- import java.util.ArrayList
14
13
import java.util.Arrays
15
14
import java.util.HashMap
16
- import java.util.List
17
15
import java.util.Map
18
16
import org.slf4j.LoggerFactory
19
17
import org.spongycastle.crypto.InvalidCipherTextException
@@ -28,93 +26,21 @@ import prof7bit.bitcoin.wallettool.core.KeyObject
28
26
import prof7bit.bitcoin.wallettool.exceptions.FormatFoundNeedPasswordException
29
27
30
28
class WalletDatHandler extends AbstractImportExportHandler {
31
- val log = LoggerFactory . getLogger(WalletDatHandler )
29
+ val log = LoggerFactory . getLogger(this . class )
32
30
33
- /**
34
- * this list will contain all items from the bdb file,
35
- * its alternating key and value data. It has an even
36
- * count of entries, alternating key and their associated
37
- * value: { k1, v1, k2, v2, ... , kn, vn }
38
- */
39
- val List<ByteBuffer > bdbKeyValueItems = new ArrayList
40
-
41
- /**
42
- * this object will contain all bitcoin keys, it will
43
- * be populated when parseBitcoinData is going through
44
- * the bdbKeyValueItems list.
45
- */
46
- val rawKeyList = new WalletDatRawKeyDataList
47
-
48
- // all bitcoin private keys are encrypted with the mkey
49
- // and the mkey itself is encrypted with the password.
50
- var int mkey_nID
51
- var byte [] mkey_encrypted_key
52
- var byte [] mkey_salt
53
- var int mkey_nDerivationMethod
54
- var int mkey_nDerivationIterations
55
- var String mkey_other_params
56
31
57
32
/**
58
33
* Try to parse keys from a bitcoin-core wallet.dat file.
59
34
* Reverse engineered with inspiration from pywallet.py,
60
35
* db_page.h, db_dump and a hex editor.
61
36
*/
62
37
override load (File file , String password , String password2 ) throws Exception {
63
- var RandomAccessFile raf
64
- try {
65
- log. info(" opening file {}" , file)
66
- raf = new RandomAccessFile (file, " r" )
67
-
68
- parseBerkeleyFile(raf)
69
- parseBitcoinData
70
- decryptAndImport(password)
71
38
72
- } finally {
73
- raf. close
74
- }
75
- }
76
-
77
- //
78
- // ************* decrypt and import
79
- //
39
+ val wallet = new WalletDat (file)
40
+ wallet. decrypt(password)
80
41
81
- /**
82
- * Decrypt (if encrypted) the keys that we have parsed
83
- * from the wallet and import them into walletKeyTool
84
- */
85
- private def decryptAndImport (String password ) throws Exception {
86
- if (mkey_encrypted_key != null ){
87
- if (password == null ){
88
- throw new FormatFoundNeedPasswordException
89
- }
90
-
91
- val crypter = new WalletDatCrypter
92
-
93
- // first we decrypt the encrypted master key...
94
- val mkey = crypter. decrypt(
95
- mkey_encrypted_key,
96
- password,
97
- mkey_salt,
98
- mkey_nDerivationIterations,
99
- mkey_nDerivationMethod
100
- )
101
-
102
- // ...then we use that to decrypt all the individual keys
103
- for (key : rawKeyList. keyData. values){
104
- if (key. encrypted_private_key != null ){
105
- key. private_key = crypter. decrypt(
106
- key. encrypted_private_key,
107
- mkey,
108
- key. public_key
109
- )
110
- }
111
- }
112
- }
113
-
114
- // At this point we have all keys unencrypted in rawKeyList.
115
- // import them into the current walletKeyTool instance
116
42
var count = 0
117
- for (rawKey : rawKeyList . keyData . values ){
43
+ for (rawKey : wallet . keys ){
118
44
119
45
var ECKey ecKey
120
46
if (rawKey. private_key. length > 32 ){
@@ -140,14 +66,14 @@ class WalletDatHandler extends AbstractImportExportHandler {
140
66
if (rawKey. pool){
141
67
wktKey. label = " (reserve)"
142
68
} else {
143
- val label = rawKeyList . getName (addr)
69
+ val label = wallet . getAddrLabel (addr)
144
70
wktKey. label = label
145
71
if (label == " " ){
146
72
wktKey. label = " (change)"
147
73
}
148
74
}
149
75
walletKeyTool. add(wktKey)
150
- walletKeyTool. reportProgress(100 * count / rawKeyList . keyData . keySet . length, addr)
76
+ walletKeyTool. reportProgress(100 * count / wallet . keys . length, addr)
151
77
count = count + 1
152
78
153
79
} else {
@@ -159,35 +85,58 @@ class WalletDatHandler extends AbstractImportExportHandler {
159
85
log. info(" import complete :-)" )
160
86
}
161
87
162
- def getParams (){
88
+ private def getParams (){
163
89
if (walletKeyTool. params == null ){
164
90
walletKeyTool. params = new MainNetParams
165
91
}
166
92
return walletKeyTool. params
167
93
}
168
94
169
- //
170
- // ************* parsing the wallet contents from the key/value list
171
- //
95
+ /**
96
+ * Save the keys to the wallet.dat file. This will always
97
+ * throw an exception because it will not be implemented.
98
+ */
99
+ override save (File file , String password , String password2 ) throws Exception {
100
+ throw new UnsupportedOperationException (" Writing wallet.dat is not supported" )
101
+ }
102
+ }
103
+
104
+ class WalletDat {
105
+ val log = LoggerFactory . getLogger(this . class)
106
+
107
+ var RandomAccessFile raf
108
+ var ByteBuffer currentKey = null
172
109
173
110
/**
174
- * Parse the key/value pairs. After the bdb parsing is done we
175
- * have a collection of all key/value pairs from the database.
176
- * This method will go through all of them to parse relevant
177
- * data from it. While it is running it will populate the
178
- * rawKeyList with all keys and their meta data.
111
+ * this object will contain all bitcoin keys, it will
112
+ * be populated while going through the bdb key/value
113
+ * pairs in the file.
179
114
*/
180
- private def parseBitcoinData (){
181
- rawKeyList. clear
182
- var i = 0
183
- while (i < bdbKeyValueItems. length - 1 ) {
184
- val key = bdbKeyValueItems. get(i)
185
- val value = bdbKeyValueItems. get(i + 1 )
186
- parseKeyValuePair(key, value)
187
- i = i + 2
115
+ val rawKeyList = new WalletDatRawKeyDataList
116
+
117
+ // all bitcoin private keys are encrypted with the mkey
118
+ // and the mkey itself is encrypted with the password.
119
+ var int mkey_nID
120
+ var byte [] mkey_encrypted_key
121
+ var byte [] mkey_salt
122
+ var int mkey_nDerivationMethod
123
+ var int mkey_nDerivationIterations
124
+ var String mkey_other_params
125
+
126
+ new (File file) throws Exception {
127
+ try {
128
+ log. info(" opening file {}" , file)
129
+ raf = new RandomAccessFile (file, " r" )
130
+ parseBerkeleyFile
131
+ } finally {
132
+ raf. close
188
133
}
189
134
}
190
135
136
+ //
137
+ // Bitcoin stuff
138
+ //
139
+
191
140
/**
192
141
* parse an individual key/value pair and see if it contains
193
142
* relevant information, extract this information (we only
@@ -279,11 +228,6 @@ class WalletDatHandler extends AbstractImportExportHandler {
279
228
log. trace(" found: type 'pool' n={}" , n)
280
229
}
281
230
282
-
283
- //
284
- // ************* reading stuff from a ByteBuffer in the way bitcoin likes to encode it
285
- //
286
-
287
231
private def readString (ByteBuffer buf ) {
288
232
return new String (buf. readSizePrefixedByteArray)
289
233
}
@@ -318,11 +262,53 @@ class WalletDatHandler extends AbstractImportExportHandler {
318
262
}
319
263
}
320
264
265
+
266
+ /**
267
+ * Decrypt (if encrypted) the keys in the raw key list,
268
+ * do nothing if they are not encrypted
269
+ */
270
+ def decrypt (String password ) throws Exception {
271
+ if (mkey_encrypted_key != null ){
272
+ if (password == null ){
273
+ throw new FormatFoundNeedPasswordException
274
+ }
275
+
276
+ val crypter = new WalletDatCrypter
277
+
278
+ // first we decrypt the encrypted master key...
279
+ val mkey = crypter. decrypt(
280
+ mkey_encrypted_key,
281
+ password,
282
+ mkey_salt,
283
+ mkey_nDerivationIterations,
284
+ mkey_nDerivationMethod
285
+ )
286
+
287
+ // ...then we use that to decrypt all the individual keys
288
+ for (key : rawKeyList. keyData. values){
289
+ if (key. encrypted_private_key != null ){
290
+ key. private_key = crypter. decrypt(
291
+ key. encrypted_private_key,
292
+ mkey,
293
+ key. public_key
294
+ )
295
+ }
296
+ }
297
+ }
298
+ }
299
+
300
+ def getKeys () {
301
+ rawKeyList. keyData. values
302
+ }
303
+
304
+ def getAddrLabel (String addr ){
305
+ rawKeyList. getName(addr)
306
+ }
307
+
321
308
//
322
- // ************* reading Berkeley-specific structures from the file
309
+ // Berkeley-db stuff
323
310
//
324
311
325
-
326
312
/**
327
313
* Parse the wallet.dat file, find all b-tree leaf
328
314
* pages in the file and put all their items into
@@ -331,7 +317,7 @@ class WalletDatHandler extends AbstractImportExportHandler {
331
317
* no next page. When this function has returned we
332
318
* have all key/value items in the bdbKeyValueItems list.
333
319
*/
334
- private def parseBerkeleyFile (RandomAccessFile raf ) throws Exception {
320
+ private def parseBerkeleyFile () throws Exception {
335
321
// these are the only ones we support
336
322
val MAGIC = 0x53162
337
323
val VERSION = 9
@@ -345,15 +331,13 @@ class WalletDatHandler extends AbstractImportExportHandler {
345
331
val pagesize = head. pageSize
346
332
val last_pgno = head. lastPgno
347
333
348
- bdbKeyValueItems. clear
349
334
for (pgno : 0 .. last_pgno) {
350
335
// find a root leaf
351
336
val page = new BerkeleyDBLeafPage (raf, pgno, pagesize)
352
337
if (page. isLBTREE && page. isRootPage){
353
338
readAllLeafPages(page)
354
339
}
355
340
}
356
- log. debug(" parsing done, found {} key/value pairs in db file" , bdbKeyValueItems. length / 2 )
357
341
}
358
342
359
343
/**
@@ -379,19 +363,19 @@ class WalletDatHandler extends AbstractImportExportHandler {
379
363
log. debug(" page {} contains {} entries" , page. pgno , count)
380
364
for (i : 0 .. < count){
381
365
if (page. getItemType(i) == 1 ) {
382
- val data = page. getItemData(i)
383
- bdbKeyValueItems. add(data)
366
+ val item = page. getItemData(i)
367
+ if (currentKey == null ){
368
+ // even number, item is a key
369
+ currentKey = item
370
+ } else {
371
+ // odd number, item is a value,
372
+ // previous item was its key
373
+ parseKeyValuePair(currentKey, item)
374
+ currentKey = null
375
+ }
384
376
}
385
377
}
386
378
}
387
-
388
- //
389
- // ************* saving (won't be implemented)
390
- //
391
-
392
- override save (File file , String password , String password2 ) throws Exception {
393
- throw new UnsupportedOperationException (" Writing wallet.dat is not supported" )
394
- }
395
379
}
396
380
397
381
abstract class BerkeleyDBPage {
0 commit comments