| skipped 213 lines |
214 | 214 | | |
215 | 215 | | # Load a Bitcoin Core BDB wallet file given the filename and extract the first encrypted master key |
216 | 216 | | def load_bitcoincore_wallet(wallet_filename): |
217 | | - | global wallet |
| 217 | + | global wallet, has_bsddb |
218 | 218 | | load_aes256_library() |
219 | | - | import bsddb.db |
220 | | - | db_env = bsddb.db.DBEnv() |
221 | | - | db_env.open(os.path.dirname(wallet_filename), bsddb.db.DB_CREATE | bsddb.db.DB_INIT_MPOOL) |
222 | | - | db = bsddb.db.DB(db_env) |
223 | | - | db.open(wallet_filename, "main", bsddb.db.DB_BTREE, bsddb.db.DB_RDONLY) |
224 | | - | mkey = db.get("\x04mkey\x01\x00\x00\x00") |
225 | | - | db.close() |
226 | | - | db_env.close() |
| 219 | + | |
| 220 | + | mkey = None |
| 221 | + | try: |
| 222 | + | import bsddb.db |
| 223 | + | has_bsddb = True |
| 224 | + | except: has_bsddb = False |
| 225 | + | if has_bsddb: |
| 226 | + | db_env = bsddb.db.DBEnv() |
| 227 | + | db_env.open(os.path.dirname(wallet_filename), bsddb.db.DB_CREATE | bsddb.db.DB_INIT_MPOOL) |
| 228 | + | db = bsddb.db.DB(db_env) |
| 229 | + | db.open(wallet_filename, "main", bsddb.db.DB_BTREE, bsddb.db.DB_RDONLY) |
| 230 | + | mkey = db.get("\x04mkey\x01\x00\x00\x00") |
| 231 | + | db.close() |
| 232 | + | db_env.close() |
| 233 | + | |
| 234 | + | else: |
| 235 | + | def align_32bits(i): |
| 236 | + | m = i % 4 |
| 237 | + | return i+4-m if m else i |
| 238 | + | with open(wallet_filename, "rb") as wallet_file: |
| 239 | + | wallet_file_size=os.path.getsize(wallet_filename) |
| 240 | + | |
| 241 | + | wallet_file.seek(12) |
| 242 | + | assert wallet_file.read(8) == "\x62\x31\x05\x00\x09\x00\x00\x00", "is a Btree v9 file" |
| 243 | + | wallet_file.seek(20) |
| 244 | + | (page_size,) = struct.unpack("<I", wallet_file.read(4)) |
| 245 | + | |
| 246 | + | # Don't actually try walking the btree, just look through every btree leaf page |
| 247 | + | # for the value/key pair (yes they are in that order...) we're searching for |
| 248 | + | for page_base in xrange(page_size, wallet_file_size, page_size): # skip the header page |
| 249 | + | wallet_file.seek(page_base+20) |
| 250 | + | (item_count, first_item_pos, btree_level, page_type) = struct.unpack("< H H B B", wallet_file.read(6)) |
| 251 | + | if page_type != 5 or btree_level != 1: continue # skip non-btree and non-leaf pages |
| 252 | + | pos = align_32bits(page_base + first_item_pos) |
| 253 | + | wallet_file.seek(pos) |
| 254 | + | for i in xrange(item_count): |
| 255 | + | (item_len, item_type) = struct.unpack("< H B", wallet_file.read(3)) |
| 256 | + | if item_type & ~0x80 == 1: # it's a variable-length key or value |
| 257 | + | if item_type == 1: # if it's not marked as deleted |
| 258 | + | if i % 2 == 0: # if it's a value, save it's position |
| 259 | + | value_pos = pos+3 |
| 260 | + | value_len = item_len |
| 261 | + | elif item_len == 9 and wallet_file.read(item_len) == "\x04mkey\x01\x00\x00\x00": |
| 262 | + | wallet_file.seek(value_pos) |
| 263 | + | mkey = wallet_file.read(value_len) # found it! |
| 264 | + | break |
| 265 | + | pos = align_32bits(pos + 3 + item_len) # calc the position of the next item |
| 266 | + | else: |
| 267 | + | pos += 12 # the two other item types have a fixed length |
| 268 | + | if i+1 < item_count: # don't need to seek if this is the last item in the page |
| 269 | + | assert pos < page_base + page_size, "next item is located in current page" |
| 270 | + | wallet_file.seek(pos) |
| 271 | + | else: continue # if not found on this page, continue to next page |
| 272 | + | break # if we broke out of inner loop, break out of this one |
| 273 | + | |
227 | 274 | | if not mkey: |
228 | 275 | | raise Exception("Encrypted master key #1 not found in the Bitcoin Core wallet file.\n"+ |
229 | 276 | | "(is this wallet encrypted? is this a standard Bitcoin Core wallet?)") |
230 | 277 | | # This is a little fragile because it assumes the encrypted key and salt sizes are |
231 | 278 | | # 48 and 8 bytes long respectively, which although currently true may not always be: |
232 | 279 | | (encrypted_master_key, salt, method, iter_count) = struct.unpack_from("< 49p 9p I I", mkey) |
233 | | - | if method != 0: raise NotImplementedError("Unsupported Bitcoin Core key derivation method " + method) |
| 280 | + | if method != 0: raise NotImplementedError("Unsupported Bitcoin Core key derivation method " + str(method)) |
234 | 281 | | wallet = (encrypted_master_key, salt, iter_count) |
235 | 282 | | |
236 | 283 | | # Estimate the time it takes to try a single password (on a single CPU) for Bitcoin Core |
| skipped 110 lines |
347 | 394 | | i += 16 |
348 | 395 | | return str(plaintext) |
349 | 396 | | |
| 397 | + | |
350 | 398 | | ############################## Argument Parsing ############################## |
351 | 399 | | |
352 | 400 | | |
| skipped 94 lines |
447 | 495 | | elif os.path.isfile("btcrecover-tokens-auto.txt"): tokenlist_file = open("btcrecover-tokens-auto.txt") |
448 | 496 | | if tokenlist_file: |
449 | 497 | | if tokenlist_file.read(3) == "#--": |
450 | | - | print(parser.prog+": warning: all options loaded from restore file; ignoring options in tokenlist file '"+tokenlist_file.name+"'") |
| 498 | + | print(parser.prog+": warning: all options loaded from restore file; ignoring options in tokenlist file '"+tokenlist_file.name+"'", file=sys.stderr) |
451 | 499 | | tokenlist_file.readline() |
452 | 500 | | else: |
453 | 501 | | tokenlist_file.seek(0) |
| skipped 147 lines |
601 | 649 | | elif not args.listpass: |
602 | 650 | | print(parser.prog+": error: argument --wallet (or --listpass) is required", file=sys.stderr) |
603 | 651 | | sys.exit(2) |
| 652 | + | |
| 653 | + | if not has_bsddb: |
| 654 | + | print(parser.prog+": warning: can't load bsddb, falling back to experimental Bitcoin Core wallet parsing mode", file=sys.stderr) |
604 | 655 | | |
605 | 656 | | # Open a new autosave file (if --restore was specified, the restore file |
606 | 657 | | # is still open and has already been assigned to autosave_file instead) |
| skipped 609 lines |