Author

Topic: recover keys from wallet.dat without using pywallet (Read 1651 times)

member
Activity: 351
Merit: 37
thing is fucked up. do you know there's EVP_CIPHER_CTX_free() on scene?
jr. member
Activity: 50
Merit: 30
Hi, since HCP wasnt online for 2 weeks now, does someone by chance has his coredecrypter?

I am unsure if I modified his code at all, however it is original or very close.
If I did edit it, it will print without verfication of the key pairs regadless of the data inputed.
HCP donation address has NOT BEEN CHANGED!


#!/usr/bin/env python

# Bitcoin Core wallet.dat masterkey/privkey decrypter
#
# Coded by HCP 2021
#
# Donations: BTC - 33o4MoDSFrfKSznHLBzwKigpTQvMiWWsHr
#
# Borrows heavily from joric's PoC code here: https://bitcointalksearch.org/topic/m.708668
# Bitcoin wallet keys AES encryption / decryption (see http://github.com/joric/pywallet)
# Uses pycrypto or libssl or libeay32.dll or aes.py from http://code.google.com/p/slowaes

import argparse
import base58
import binascii
import ecdsa
import getpass
import hashlib
import struct
import sys

crypter = None

debug = 0

try:
    from Crypto.Cipher import AES
    crypter = 'pycrypto'
except:
    pass

class Crypter_pycrypto( object ):
    def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
        data = vKeyData + vSalt
        for i in range(nDerivIterations):
            data = hashlib.sha512(data).digest()
        self.SetKey(data[0:32])
        self.SetIV(data[32:32+16])
        return len(data)

    def SetKey(self, key):
        self.chKey = key

    def SetIV(self, iv):
        self.chIV = iv[0:16]

    def Encrypt(self, data):
        return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32]
 
    def Decrypt(self, data):
        return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32]

try:
    if not crypter:
        import ctypes
        import ctypes.util
        ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32')
        crypter = 'ssl'
except:
    pass

class Crypter_ssl(object):
    def __init__(self):
        self.chKey = ctypes.create_string_buffer (32)
        self.chIV = ctypes.create_string_buffer (16)

    def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
        strKeyData = ctypes.create_string_buffer (vKeyData)
        chSalt = ctypes.create_string_buffer (vSalt)
        return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData,
            len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV))

    def SetKey(self, key):
        self.chKey = ctypes.create_string_buffer(key)

    def SetIV(self, iv):
        self.chIV = ctypes.create_string_buffer(iv)

    def Encrypt(self, data):
        buf = ctypes.create_string_buffer(len(data) + 16)
        written = ctypes.c_int(0)
        final = ctypes.c_int(0)
        ctx = ssl.EVP_CIPHER_CTX_new()
        ssl.EVP_CIPHER_CTX_init(ctx)
        ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
        ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
        output = buf.raw[:written.value]
        ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final))
        output += buf.raw[:final.value]
        return output

    def Decrypt(self, data):
        buf = ctypes.create_string_buffer(len(data) + 16)
        written = ctypes.c_int(0)
        final = ctypes.c_int(0)
        ctx = ssl.EVP_CIPHER_CTX_new()
        ssl.EVP_CIPHER_CTX_init(ctx)
        ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
        ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
        output = buf.raw[:written.value]
        ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final))
        output += buf.raw[:final.value]
        return output

try:
    if not crypter:
        from aes import *
        crypter = 'pure'
except:
    pass

class Crypter_pure(object):
    def __init__(self):
        self.m = AESModeOfOperation()
        self.cbc = self.m.modeOfOperation["CBC"]
        self.sz = self.m.aes.keySize["SIZE_256"]

    def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
        data = vKeyData + vSalt
        for i in range(nDerivIterations):
            data = hashlib.sha512(data).digest()
        self.SetKey(data[0:32])
        self.SetIV(data[32:32+16])
        return len(data)

    def SetKey(self, key):
        self.chKey = [ord(i) for i in key]

    def SetIV(self, iv):
        self.chIV = [ord(i) for i in iv]

    def Encrypt(self, data):
        mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV)
        return ''.join(map(chr, cypher))
 
    def Decrypt(self, data):
        chData = [ord(i) for i in data]
        return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV)

import hashlib

def Hash(data):
    return hashlib.sha256(hashlib.sha256(data).digest()).digest()

def main():

    parser = argparse.ArgumentParser(description="Bitcoin Core wallet.dat masterkey/privkey decrypter")
    
    parser.add_argument("--enc_mkey", action="store_true", default=False,
                        help="required if Master Key is Encrypted")
    parser.add_argument("mkey", action="store",
                        help="Master Key (can be encrypted or decrypted)")
    parser.add_argument("privkey", action="store",
                        help="Encrypted Private Key")
    parser.add_argument("pub", action="store",
                        help="Public Key of privkey (compressed or uncompressed)")

    
    results = parser.parse_args()
    
    if (results.enc_mkey):
        encrypted_master_key = binascii.unhexlify(results.mkey)
        encrypted_mkey, salt, method, iterations = struct.unpack_from("< 49p 9p I I", encrypted_master_key)
        
        if (debug):
            print("--------------------------------------------------------------")
            print "Successfully parsed encrypted master key\n"
            print "enc mkey: ", binascii.hexlify(encrypted_mkey)
            print "salt    : ", binascii.hexlify(salt)
            print "method  : ", method
            print "#iters  : ", iterations
            print("--------------------------------------------------------------")
        
        wallet_pass_phrase = getpass.getpass("\nEnter wallet passphrase: ")
    else:
        decrypted_master_key = binascii.unhexlify(results.mkey)
    
    enc_priv_key = binascii.unhexlify(results.privkey)
    pub_key = binascii.unhexlify(results.pub)
    
    global crypter

    if crypter == 'pycrypto':
        crypter = Crypter_pycrypto()
    elif crypter == 'ssl':
        crypter = Crypter_ssl()
        print "using ssl"
    elif crypter == 'pure':
        crypter = Crypter_pure()
        print "using slowaes"
    else:
        print("Need pycrypto of libssl or libeay32.dll or http://code.google.com/p/slowaes")
        exit(1)

    if (results.enc_mkey):
        crypter.SetKeyFromPassphrase(wallet_pass_phrase, salt, iterations, method)
        masterkey = crypter.Decrypt(encrypted_mkey)
        if (debug):
            print "Decrypted Master Key as:"
            print "dec mkey   : ", binascii.hexlify(masterkey)
            print("--------------------------------------------------------------")
    else:
        masterkey = binascii.unhexlify(results.mkey)
        
    crypter.SetKey(masterkey)
    crypter.SetIV(Hash(pub_key))
    
    d = crypter.Decrypt(enc_priv_key)
    e = crypter.Encrypt(d)

    if (debug):
        print 'dec privkey: ', binascii.hexlify(d)
        print("--------------------------------------------------------------")
    
    # Private key to public key (ecdsa transformation)
    private_key = ecdsa.SigningKey.from_string(d, curve = ecdsa.SECP256k1)
    verifying_key = private_key.get_verifying_key()
    uncomp_public_key = getPubKey(verifying_key.pubkey, False)
    if (debug):
        print 'calc pubkey: ', binascii.hexlify(uncomp_public_key)
        print 'orig pubkey: ', binascii.hexlify(pub_key)
        
    # hash sha 256 of pubkey
    sha256_1 = hashlib.sha256(uncomp_public_key)

    # hash ripemd of sha of pubkey
    ripemd160 = hashlib.new("ripemd160")
    ripemd160.update(sha256_1.digest())

    # checksum
    hashed_public_key = binascii.unhexlify("00") + ripemd160.digest()
    checksum_full = hashlib.sha256(hashlib.sha256(hashed_public_key).digest()).digest()
    checksum = checksum_full[:4]
    uncomp_bin_addr = hashed_public_key + checksum

    # encode address to base58 and print
    uncomp_result_address = base58.b58encode(uncomp_bin_addr)
    if (debug):
        print "\nuncomp addr: ", uncomp_result_address
    
    network_byte_key = binascii.unhexlify("80") + d
    priv_checksum_full = hashlib.sha256(hashlib.sha256(network_byte_key).digest()).digest()
    priv_checksum = priv_checksum_full[:4]
    uncomp_full_priv = network_byte_key + priv_checksum
    
    uncomp_wif = base58.b58encode(uncomp_full_priv)
    if (debug):
        print "uncomp WIF : ", uncomp_wif        
        print("--------------------------------------------------------------")
        
    # now do compressed
    comppub_key = getPubKey(verifying_key.pubkey, True)
    if (debug):
        print 'comp pubkey: ', binascii.hexlify(comppub_key)
        print 'orig pubkey: ', binascii.hexlify(pub_key)
        
    # hash sha 256 of pubkey
    sha256_1 = hashlib.sha256(comppub_key)

    # hash ripemd of sha of pubkey
    ripemd160 = hashlib.new("ripemd160")
    ripemd160.update(sha256_1.digest())

    # checksum
    hashed_public_key = binascii.unhexlify("00") + ripemd160.digest()
    checksum_full = hashlib.sha256(hashlib.sha256(hashed_public_key).digest()).digest()
    checksum = checksum_full[:4]
    comp_bin_addr = hashed_public_key + checksum

    # encode address to base58 and print
    comp_result_address = base58.b58encode(comp_bin_addr)
    if (debug):
        print "\ncomp addr  : ", comp_result_address

    network_byte_key = binascii.unhexlify("80") + d + binascii.unhexlify("01")
    priv_checksum_full = hashlib.sha256(hashlib.sha256(network_byte_key).digest()).digest()
    priv_checksum = priv_checksum_full[:4]
    comp_full_priv = network_byte_key + priv_checksum
    
    comp_wif = base58.b58encode(comp_full_priv)
    if (debug):
        print "comp WIF   : ", comp_wif
        print("--------------------------------------------------------------")
        
    if (pub_key != comppub_key) and (pub_key != uncomp_public_key):
        print "\n\nWARNING!!!"
        print "WARNING!!! - computed public keys DO NOT match, passphrase is probably incorrect or hex data is corrupt"
        print "WARNING!!!"
        exit()
    else:
        print "\nKeys successfully decrypted:\n"
        print "decrypted mkey: ", binascii.hexlify(masterkey)
        print("--------------------------------------------------------------")
        print "uncomp addr: ", uncomp_result_address
        print "uncomp WIF : ", uncomp_wif        
        print("--------------------------------------------------------------")
        print "comp addr  : ", comp_result_address
        print "comp WIF   : ", comp_wif
        print("--------------------------------------------------------------")
        

# pywallet openssl private key implementation

def getPubKey(pubkey, compressed=False):
    # public keys are 65 bytes long (520 bits)
    # 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
    # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
    # compressed keys: where is 0x02 if y is even and 0x03 if y is odd
    if compressed:
        if pubkey.point.y() & 1:
            key = '03' + '%064x' % pubkey.point.x()
        else:
            key = '02' + '%064x' % pubkey.point.x()
    else:
        key = '04' + \
              '%064x' % pubkey.point.x() + \
              '%064x' % pubkey.point.y()

    return key.decode('hex')

if __name__ == '__main__':
    main()
legendary
Activity: 1568
Merit: 6660
bitcoincleanup.com / bitmixlist.org
Hi, since HCP wasnt online for 2 weeks now, does someone by chance has his coredecrypter?

The coredecryptor python script is inaccessible from a web prowser because keybase.pub shut down almost a year ago. It was a separate service from Keybase itself IIRC.

However, according to this comment: https://news.ycombinator.com/item?id=34711449 it seems that you are still able to access the directory through the Keybase program itself. I am not sure how that would work though, because I haven't used Keybase since it was acquired by Zoom.
newbie
Activity: 2
Merit: 0
Hi, since HCP wasnt online for 2 weeks now, does someone by chance has his coredecrypter?
member
Activity: 119
Merit: 36


Quote
The passphrase is no problem as I already know it. I wonder if the salt and iteration are viewable within the wallet file? How do these various scripts find them?

I posted a script to explain what goes on. it is here
https://bitcointalksearch.org/topic/wallet-encryption-process-5331322

Please note this script does not match bitcoin decryption exactly (on purpose) it is for demonstration

If you want the script for bitcoin send me a pm.
newbie
Activity: 33
Merit: 0
I am try this method. It is useful for me.  You also try this . If you have a wallet.dat file and need to recover the keys without using pywallet, you can use a tool like Bitcoin Core to extract the keys. Bitcoin Core is a full Bitcoin client and will attempt to recover all keys stored in your wallet.dat file. It is a bit more complicated than using pywallet, but it is an effective way to recover your keys.
newbie
Activity: 3
Merit: 0
newbie
Activity: 22
Merit: 6
root@RémiM:~# python2.7 core_decrypter.py?dl=1.1
-snip-
root@RémiM:~# pip install base58
Requirement already satisfied: base58 in /usr/local/lib/python3.9/dist-packages (2.1.1)
can you help please? Thanks
everytime i try i get thiis
Pip is installing base58 to Python 3.9 while you're using Python 2.7 for core_decrypter.py.
Try to specify the python version when using pip.
thanks. I'll try it.
legendary
Activity: 2534
Merit: 6080
Self-proclaimed Genius
root@RémiM:~# python2.7 core_decrypter.py?dl=1.1
-snip-
root@RémiM:~# pip install base58
Requirement already satisfied: base58 in /usr/local/lib/python3.9/dist-packages (2.1.1)
can you help please? Thanks
everytime i try i get thiis
Pip is installing base58 to Python 3.9 while you're using Python 2.7 for core_decrypter.py.
Try to specify the python version when using pip.
newbie
Activity: 22
Merit: 6
newbie
Activity: 1
Merit: 0
I hope you will get it someday.
You just need to dig a little bit deeper pull it apart and reassemble it in a slightly altered way.
Are you being deliberately obtuse? or am I just missing something obvious? Huh I honestly have no idea what you're talking about when you say that "you can do it"... do what exactly? Huh

The script takes the raw encrypted "mkey" hex, breaks it down into the actual key, the salt, the iterations etc... then prompts for the wallet passphrase... then decrypts the master key... then uses that decrypted master key to decrypt a given "ckey" record (note that the pubkey part of the "ckey" is required as it is used to form the "IV" for the encrypted private key)


So why do I need to dig a little bit deeper? What needs pulling apart? Huh What does the script not currently do, that you think it should? Huh

YOU SAID THAT script takes the raw encrypted "mkey" hex, breaks it down into the actual key, so this actual key is AES key?
And if we crack this actual key do we get decrypted mkey?
(from above example decrypted mkey-84d1f2f2380eb3a089ea256b851553ebfbc95555eb11b28a9eaaa7eeb97db4d6)
member
Activity: 180
Merit: 38
Spit out the right key if you don't know the password.

 
HCP
legendary
Activity: 2086
Merit: 4361
I hope you will get it someday.
You just need to dig a little bit deeper pull it apart and reassemble it in a slightly altered way.
Are you being deliberately obtuse? or am I just missing something obvious? Huh I honestly have no idea what you're talking about when you say that "you can do it"... do what exactly? Huh

The script takes the raw encrypted "mkey" hex, breaks it down into the actual key, the salt, the iterations etc... then prompts for the wallet passphrase... then decrypts the master key... then uses that decrypted master key to decrypt a given "ckey" record (note that the pubkey part of the "ckey" is required as it is used to form the "IV" for the encrypted private key)


So why do I need to dig a little bit deeper? What needs pulling apart? Huh What does the script not currently do, that you think it should? Huh
member
Activity: 180
Merit: 38
Perhaps you can add this also then you can do it

Code:
from Crypto.Cipher import AES
I'm not sure what you mean by this? Huh

That is one of the first things the script attempts to do... it checks to see if pycrypto is installed, if so then it sets up the "crypter" to use that... otherwise it tries OpenSSL, if that fails, then it tries to use "slowaes" and if that fails it prints an error message and exits.

I hope you will get it someday.
You just need to dig a little bit deeper pull it apart and reassemble it in a slightly altered way.
full member
Activity: 217
Merit: 109
Thanks so much for doing this HCP, it gives me another avenue to pursue (probably the last). I don't have much spare time at the moment to try it out and it's very time consuming for me, being the computer thicko that I am Smiley. I might need some pointers if you don't mind, once I have a go at it. Thanks again, nice work. Smiley
HCP
legendary
Activity: 2086
Merit: 4361
Perhaps you can add this also then you can do it

Code:
from Crypto.Cipher import AES
I'm not sure what you mean by this? Huh

That is one of the first things the script attempts to do... it checks to see if pycrypto is installed, if so then it sets up the "crypter" to use that... otherwise it tries OpenSSL, if that fails, then it tries to use "slowaes" and if that fails it prints an error message and exits.
member
Activity: 180
Merit: 38
Perhaps you can add this also then you can do it

Code:
from Crypto.Cipher import AES

 
HCP
legendary
Activity: 2086
Merit: 4361
How hard would it be for jackjack or someone to create a standalone tool to decrypt private keys, if the correct hex strings and passphrase were inputted? Not that I'm implying he should, as he seems to be a very busy person. But If you can search out the required info with a hex editor, it would be a very flexible tool for fragmented wallet files.
Probably not very difficult... because it didn't take me too long to bang this out: https://keybase.pub/hcp/python/core_decrypter.py

It's pretty rough... but basically, if you can feed it an "encrypted master key" (or decrypted master key), "encrypted private key" and matching "public key"... it will prompt for wallet passphrase and then attempt to decrypt the master key... then use that to decrypt the private key before outputting the Address+WIF.

I've run some tests on some newly generated wallet.dat data... and the master key/priv key/pub key data from the code example in joric's post that I linked to earlier... and it seems to be working OK.

As per the comment at the top of the script, it borrows very heavily from the "Proof of Concept" code from joric's post.... full credit to them! Wink




NOTES:

- Python 2.7x compatible only for now... if there is enough interest, I'll see if I can make it Python3 compatible.
- requires a couple of libraries that you'll likely already have installed if you've been using PyWallet etc (ie. pycrypto, ecdsa etc)... if Python moans at you about modules not being found then install them with pip Tongue





- (For now) The encrypted master key needs to be the full 66/67 bytes (132/134 hex chars) of the mkey record in the wallet.dat... This should be of the form:

"30" - 48 byte data record indicator
"48 bytes worth of encrypted master key"
"08" - 8 byte data record indicator
"8 bytes worth of salt"
"4 bytes worth of method" - should be 0 (ie. 00000000)
"4 bytes worth of iterations" - in Little Endian (ie.  1fb80000 ==> 0x0000b8f1 ==> 47345)
"00" - 1 byte end of record indicator (optional)

for example:
Code:
3008adc5605413b38a04979bf465d0cff826a25c2c8812e582241477052c6d45c11b27690ba3bf2c1da144600789c2baaa08d8659791be653e15000000001760030000

Quote
3008adc5605413b38a04979bf465d0cff826a25c2c8812e582241477052c6d45c11b27690ba3bf2c1 da144600789c2baaa08d8659791be653e15000000001760030000

This modified version of the walletinfo.py script (original here) will output the "full mkey" from a wallet.dat: https://keybase.pub/hcp/python/walletinfo.py

Usage:
Code:
python walletinfo.py wallet.dat

otherwise, you'll have to do some hexediting of the wallet.dat file to find the data you need... have fun with that Tongue

I am also considering modifying the script so you can supply the "parsed" master key data (ie. encrypted key, salt, iterations etc) individually... I guess it depends on what is the most likely format of the hex data extracted from the wallet.dat file. Huh





On subsequent runs for private keys from the same wallet.dat (ie. encrypted with the same master key), you can just use the "decrypted master key" that is output instead of using the "encrypted" master key + walletpassphrase

Example:
Code:
C:\core_decrypter>C:\Python27\python.exe core_decrypter.py --enc_mkey 3008ADC5605413B38A04979BF465D0CFF826A25C2C8812E582241477052C6D45C11B27690BA3BF2C1DA144600789C2BAAA08D8659791BE653E150000000017600300 BF1356ABEEB7FD2C7CD6757BA4B459AF64D62A3855592B24F685F642D7143D7F6725230C8B8D9B65D42DA5634DDA9A73 020016BE1AFB579AB2EF6F57220E8946B0653FD5B883E09D70A7825F17C3B07F3D

Enter wallet passphrase:

Keys successfully decrypted:

decrypted mkey:  84d1f2f2380eb3a089ea256b851553ebfbc95555eb11b28a9eaaa7eeb97db4d6
--------------------------------------------------------------
uncomp addr:  13y8obG15gqVBmuRzD7HnokyVqGhQJZBfC
uncomp WIF :  5JXMD129XHcfpWF8hrzKG7eW4zDwZtuQ82gkwfMLz9aQRTxvsrh
--------------------------------------------------------------
comp addr  :  15V1kJedt9CJDsEYCofuvfWTyapqJPW4C9
comp WIF   :  KzLxBy64cgLSLg5P1MdybiLXJba7rC6H42SUxGGoDErSNtSKCJKP
--------------------------------------------------------------

C:\core_decrypter>

On the next run, you can just use the "decrypted mkey" that was displayed:
Code:
84d1f2f2380eb3a089ea256b851553ebfbc95555eb11b28a9eaaa7eeb97db4d6

... and remove the --enc_mkey flag from the commandline:
Code:
C:\core_decrypter>C:\Python27\python.exe core_decrypter.py 84d1f2f2380eb3a089ea256b851553ebfbc95555eb11b28a9eaaa7eeb97db4d6 ba946bb7db1a98e628d1a93104369d7cbb7a4cba9c9705223a2c7891bdc888a8b1019c27d2c17b28728513a51ba54f1e 0200cc635c13471b913e22bbe568711c19fd7bcb7449a9f09885f9ca53aff3cc6e

Keys successfully decrypted:

decrypted mkey:  84d1f2f2380eb3a089ea256b851553ebfbc95555eb11b28a9eaaa7eeb97db4d6
--------------------------------------------------------------
uncomp addr:  1K3p8CZCRZoKuXtzzawYs2LmKuN1uV2jvd
uncomp WIF :  5JsNBq7rwGPqp1jZLSgawvtusp3E47c2PjPex2QgK4Mhj4Bneuv
--------------------------------------------------------------
comp addr  :  1CVQVAeTCPhNUCcVsmSqrZoXWQCksbmgmc
comp WIF   :  L1sJWoPHQWwpSWEqipMiqSt8PGcpck9b9L6zVsDWWhpNkErSrJwf
--------------------------------------------------------------

C:\core_decrypter>
NOTE that there is no "wallet passphrase" prompt! Wink





If the walletpassphrase is not correct or the hex data is corrupt/incorrect, you will see a warning like this:
Code:
C:\core_decrypter>C:\Python27\python.exe core_decrypter.py --enc_mkey 3008ADC5605413B38A04979BF465D0CFF826A25C2C8812E582241477052C6D45C11B27690BA3BF2C1DA144600789C2BAAA08D8659791BE653E150000000017600300 BF1356ABEEB7FD2C7CD6757BA4B459AF64D62A3855592B24F685F642D7143D7F6725230C8B8D9B65D42DA5634DDA9A73 020016BE1AFB579AB2EF6F57220E8946B0653FD5B883E09D70A7825F17C3B07F3D

Enter wallet passphrase:


WARNING!!!
WARNING!!! - computed public keys DO NOT match, passphrase is probably incorrect or hex data is corrupt
WARNING!!!

C:\core_decrypter>
The script calculates the pubkey from the decrypted privkey, then compares to the user supplied "hex pub key". If both the calculated compressed and uncompressed pubkeys do not match the user provided pubkey, then either the wallet passphrase was incorrect (resulting in a bad master key decrypt) or the priv/pub key hex data could be corrupt/incorrect.





Wallet passphrase for the examples above: password123

Partial Pywallet dump (with encrypted privkeys):
Code:
keys": [
        {
            "addr": "15V1kJedt9CJDsEYCofuvfWTyapqJPW4C9",
            "compressed": true,
            "encrypted_privkey": "bf1356abeeb7fd2c7cd6757ba4b459af64d62a3855592b24f685f642d7143d7f6725230c8b8d9b65d42da5634dda9a73",
            "pubkey": "020016be1afb579ab2ef6f57220e8946b0653fd5b883e09d70a7825f17c3b07f3d",
            "reserve": 1
        },
        {
            "addr": "1CVQVAeTCPhNUCcVsmSqrZoXWQCksbmgmc",
            "compressed": true,
            "encrypted_privkey": "ba946bb7db1a98e628d1a93104369d7cbb7a4cba9c9705223a2c7891bdc888a8b1019c27d2c17b28728513a51ba54f1e",
            "pubkey": "0200cc635c13471b913e22bbe568711c19fd7bcb7449a9f09885f9ca53aff3cc6e",
            "reserve": 1
        },
        {
            "addr": "1JSo3zCQ8xaj9oGgfts8DMATtQ5ptnwigk",
            "compressed": true,
            "encrypted_privkey": "65207b28be9d6b142837e192797cc782e3c8f868fddb2fb901895dd4115c6bc1367c0787544f48bc631ec7e8fa0ebb51",
            "pubkey": "02020828662e18c691abd0ca216d655c576bbbac560a8852e7aafb42acb76ed9b9",
            "reserve": 1
        },
full member
Activity: 217
Merit: 109
How hard would it be for jackjack or someone to create a standalone tool to decrypt private keys, if the correct hex strings and passphrase were inputted? Not that I'm implying he should, as he seems to be a very busy person. But If you can search out the required info with a hex editor, it would be a very flexible tool for fragmented wallet files.
HCP
legendary
Activity: 2086
Merit: 4361
...
For the curious... Gavin Anderson explains some of this here: https://bitcointalksearch.org/topic/m.656114

And it seems that Joric showed some specific Python based code for dealing with the encryption/decryption here: https://bitcointalksearch.org/topic/m.708668

I will have to experiment further when I get some time... but it's Mother's Day here today Wink
member
Activity: 180
Merit: 38
Code:
Private key encryption is done based on a CMasterKey, which holds a salt and random encryption key.

CMasterKeys are encrypted using AES-256-CBC using a key derived using derivation method nDerivationMethod (0 == EVP_sha512()) and derivation iterations nDeriveIterations. vchOtherDerivationParameters is provided for alternative algorithms which may require more parameters (such as scrypt).

Wallet Private Keys are then encrypted using AES-256-CBC with the double-sha256 of the public key as the IV, and the master key's key as the encryption key (see keystore.[ch]). Master key for wallet encryption
full member
Activity: 217
Merit: 109
When you create a recovered wallet... you have to specify a passphrase that the "recovered wallet.dat" is going to use... you then specify the "possible" passphrases it should try when recovering keys/data etc...

The script attempts to decrypt any encrypted keys found using the "possible" passphrases... and then puts them into the recovered wallet.dat, encrypted with a master key derived from the "recovered wallet.dat" passphrase (NOT the passphrase of the original wallet)
Thought so. I presume that while dumping private keys it wouldn't be impossible to show any decrypted master keys also. It would be a handy feature for any other ckeys that may share the same master key but were not gathered into the recovery.
HCP
legendary
Activity: 2086
Merit: 4361
When you create a recovered wallet... you have to specify a passphrase that the "recovered wallet.dat" is going to use... you then specify the "possible" passphrases it should try when recovering keys/data etc...

The script attempts to decrypt any encrypted keys found using the "possible" passphrases... and then puts them into the recovered wallet.dat, encrypted with a master key derived from the "recovered wallet.dat" passphrase (NOT the passphrase of the original wallet)
full member
Activity: 217
Merit: 109
When a recovered wallet is dumped with pywallet, is the master key near the end the encrypted version and if so, is it a result of the passphrase given for the recovered wallet? It would be great if the original decrypted master key, or master keys were listed, as I presume that possibly more than one original lot of wallet keys are gathered into the one recovered wallet. This would be handy for trying fragmented wallets where other ckeys were not gathered and processed.
HCP
legendary
Activity: 2086
Merit: 4361
The passphrase is no problem as I already know it. I wonder if the salt and iteration are viewable within the wallet file? How do these various scripts find them?
The "mkey" data stored in the wallet.dat is unpacked as follows:
Code:
encrypted_master_key, salt, method, iter_count = struct.unpack_from("< 49p 9p I I", mkey)
So, it's actually a 48 byte record called the "encrypted_master_key", the 8 byte salt, the 'method' (should be a 4 byte unsigned Int value 0) and the iteration count (4 byte unsigned Int).

Then, the 48 byte "encrypted_master_key", is actually parsed as:
Code:
iv = binascii.hexlify(encrypted_master_key[16:32])
ct = binascii.hexlify(encrypted_master_key[-16:])

first 16 bytes are ignored?
2nd 16 bytes are the "iv" (initialisation vector)
last 16 bytes are the "ct" (cipher text)


The hex that the walletinfo.py script outputs is:
Code:
s = iv + ct + binascii.hexlify(salt) + iterations

return s
So it is actually iv (16 bytes) + cipher text (16 bytes) + salt (8 bytes) + iteration (8 bytes, it gets padded out from 4 bytes)

I assume this is because the OpenCL portion of that project is expecting the data in this format (to test passphrases) with.
member
Activity: 180
Merit: 38
It looks for a specific header.
full member
Activity: 217
Merit: 109
I don't think you will be able to use that site, as there are some other background things happening that I don't think that website supports, but assuming you can decrypt the master key, then it should theoretically be possible to then decrypt individual private keys.


As for decrypting the master key, I found this project which claims to be able to extract the encrypted master key, salt, IV etc from a wallet.dat: https://github.com/brichard19/core-decrypt

There is also an OpenCL based project included (and a precompiled .exe Huh) that claims to be able to test the master key decryption using a password dictionary. The idea being that you can identify what the wallet passphrase is... it doesn't decrypt individual keys, and it doesn't show the decrypted master key etc. It just seems to test the "encrypted master key" decryption using passwords you pass in.


If this information regarding wallet.dat encryption is still valid: https://en.bitcoin.it/wiki/Wallet_encryption

Then it looks like you would need to start playing with your wallet passphrase, SHA512 and OpenSSL "EVP_BytesToKey" functionality (along with the extracted IV, Salt, iteration count etc as extracted by the python script) to derive the "key" needed to decrypt the "encrypted master key"...

and then once you have the "unencrypted master key", you should be able to decrypt the individual private keys.
The passphrase is no problem as I already know it. I wonder if the salt and iteration are viewable within the wallet file? How do these various scripts find them?
HCP
legendary
Activity: 2086
Merit: 4361
I don't think you will be able to use that site, as there are some other background things happening that I don't think that website supports, but assuming you can decrypt the master key, then it should theoretically be possible to then decrypt individual private keys.


As for decrypting the master key, I found this project which claims to be able to extract the encrypted master key, salt, IV etc from a wallet.dat: https://github.com/brichard19/core-decrypt

There is also an OpenCL based project included (and a precompiled .exe Huh) that claims to be able to test the master key decryption using a password dictionary. The idea being that you can identify what the wallet passphrase is... it doesn't decrypt individual keys, and it doesn't show the decrypted master key etc. It just seems to test the "encrypted master key" decryption using passwords you pass in.


If this information regarding wallet.dat encryption is still valid: https://en.bitcoin.it/wiki/Wallet_encryption

Then it looks like you would need to start playing with your wallet passphrase, SHA512 and OpenSSL "EVP_BytesToKey" functionality (along with the extracted IV, Salt, iteration count etc as extracted by the python script) to derive the "key" needed to decrypt the "encrypted master key"...

and then once you have the "unencrypted master key", you should be able to decrypt the individual private keys.
full member
Activity: 217
Merit: 109
Would an offline version of this work if you had the mkey ckey and passphrase? https://www.devglan.com/online-tools/aes-encryption-decryption
member
Activity: 180
Merit: 38
I've searched the 0201010420 trailing bytes and checked hundreds of private keys manually but with no success as yet. I could see they were bitcoin core keys with the data and text surrounding them. I suspect that the private key I need is encrypted and there are hundreds of those as well, plus mkeys. I noticed that each ckey marker 000104636b65 79 is followed at a point later by the trailing byte of 30 as discovered and shown in HCP's example. I'm thinking that maybe the bytes following 30 is the encrypted key and the public key is between 79 and 30 maybe?

If you can grab the entire block of encrypted data from start to end then you can decrypt it using standard methods in which you start with your IV (Initialization Vector) so from the start and not like a partial/damaged or orphaned block in which you need to use the chain code from the previous block.
From what you wrote it seems that the data is still intact so there should be no problem to find it.
full member
Activity: 217
Merit: 109
I've searched the 0201010420 trailing bytes and checked hundreds of private keys manually but with no success as yet. I could see they were bitcoin core keys with the data and text surrounding them. I suspect that the private key I need is encrypted and there are hundreds of those as well, plus mkeys. I noticed that each ckey marker 000104636b65 79 is followed at a point later by the trailing byte of 30 as discovered and shown in HCP's example. I'm thinking that maybe the bytes following 30 is the encrypted key and the public key is between 79 and 30 maybe?
member
Activity: 180
Merit: 38
What would be the minimum data required to decrypt a private key? Would it be ckey! and mkey, what about salt, is that part of the mkey? All depends on having the correct passphrase of course. My corrupt wallets have been either fragmented with groups of ckeys that have been separated from the mkey, or other data in the wallet has been corrupted causing the wallet to be unusable with normal methods. I'm pretty sure that the hex values of the mkey and ckeys can be found quite easily by using Winhex or similar. If possible, it would be fantastic to have a tool for manually inputting the keys and passphrase that doesn't get tied up with other corrupt data in the wallet.dat.

1. The target address. ( maybe not see below)
2. The passphrase (and encryption method)
3. The exact 32 (consecutive) bytes of the private key somewhere in the file.

For un-encrypted keys:
The principle is very simple and assumes that the key is still somewhere in the (corrupted / partly overwritten etc.) file.
You slide over all of the remaining file data in 32 byte sized blocks and turn every block into a address (of the same target address method).
Then you compare the generated address to the target address.
If there is no match you move or slide the block to the next nibble or byte.
Then when your byte scanner moves above the correct block of bytes, that holds the correct byte sequence, the resulting address will match the target address.
You can then print out/save the found data/key and exit.
This will work without the presence of any btree structure and without (looking for) the original database index keys because it only looks for the exact 32 bytes of key itself and not the combined database index entry plus relevant data.
In this respect this method will outperform recovery tools that only look for the standard database index key entries and their following variable field.
So it's as close to a RAW method as it can possibly get, simply because one nibble or byte more, or one less and you will completely miss your target.
It will work most of the time but not in case of missing/corrupt bytes in the key itself and for fragmented keys because then it will obviously produce a mismatch in target address.
In that case your chances on a success are extremely limited and will depend on how hard you decide to push it further with the scrambled data that is left.
You could start and try to rotate individual bytes but it could take forever, this is not something I would advice in combination with this method.
In case you do not know the target address you will have to verify the actual balance of every address that rolls out of those scanned blocks on the blockchain.
So you could even do it with less.



For encrypted keys it will get a bit more difficult because your goal then is the decryption of partial encrypted data and then you (usually) also need the preceding 16 bytes (CBC Cypher Block Chain) as part of the decryption method to enable the decryption of the next block of data depending on the decryption method you use, there are several but it get's a bit technical here it is best imagined that you no longer search for the exact key but for a block of data that 'makes sense' by this i mean when your output will be mostly garbage and non base16 characters then you know that you are off target, and when a block arrives with a perfect string of only base16 hexadecimal bytes then you can be absolutely sure that you have something of real value.
I always compare it to turning a knob of those old analog radio's it's mostly noise until a crystal clear channel pops up.
full member
Activity: 217
Merit: 109
What would be the minimum data required to decrypt a private key? Would it be ckey! and mkey, what about salt, is that part of the mkey? All depends on having the correct passphrase of course. My corrupt wallets have been either fragmented with groups of ckeys that have been separated from the mkey, or other data in the wallet has been corrupted causing the wallet to be unusable with normal methods. I'm pretty sure that the hex values of the mkey and ckeys can be found quite easily by using Winhex or similar. If possible, it would be fantastic to have a tool for manually inputting the keys and passphrase that doesn't get tied up with other corrupt data in the wallet.dat.
member
Activity: 180
Merit: 38
I've tried it for a bit & looks like it works. However since it dump all data and looks like all dumped data use HEX format, there are few problem
1. We don't know the data is private key, public key or other data (maybe it's possible by check HEX length).
2. We don't know whether is encrypted or not (since the tools worked on both encrypted & non-encrypted wallet).
3. Some regex needed to select only needed data (Assuming the characteristic of data you're looking for).
That's just a basic syntax and using designated database tools/utility since the topic starter wrote he wanted something else then Python.

True, but OP mentioned he had hard time time getting pywallet works, so your solution isn't what OP looking for (but useful for more advance user who read this thread).
[/quote]

You don't know that.
It's only two lines of code.
Maybe it was exactly what he was looking for.
member
Activity: 180
Merit: 38
The easiest way to find the desired key is to extract all data and then use the address as identifier to search the file and then find the key that relates to that particular address.

I read somewhere on Github that the wallet.dat file format sometimes does not write keys and values contiguously but rather in arbitrary positions, so we may end up with a bunch of rows like HCP's output but with keys and values split along multiple rows.

For example, the output lists 84 entries. I highly doubt Bitcoin Core's code defines that many keys.

It won't because a generic database dumper is not designed to specifically read AES keys and decrypt them with a secret stored somewhere else in the file.

You can deal with that if you know what you are doing as is with most things.
These are regular database files they have keys as identifiers which relate to the target data, that is how they work.
Your doubts and beliefs are really irrelevant because these things work along exact protocols that are very well defined.
If you want you can always extend functionality, i do this all the time.
There can be many keys in a database file.



I made a wallet parser specifically for corrupt wallet files that works on the byte level and it produces ~ 200.000 keys from just the one wallet.
It does so because it is told to ignore the key index since these could have become corrupted and so instead of using those, it assumes the possible data locations.
This results in huge files with countless possibilities.
It leaves no byte unturned and if the key is still in there, then it's going to find it.
That is working on a much deeper level than what most programs and scripts do and it takes more effort and time but the results will be crystal clear.
If there is any doubt you can just pull up the oracle documentation and read the specifications because these things are well defined and used in many applications so not just for crypto currencies.
jr. member
Activity: 189
Merit: 1
I can help you, i have some command lines that can get the info. How much do you have in it? Its complicated but i can help you along the way or you can reach me by email:[email protected]
legendary
Activity: 1568
Merit: 6660
bitcoincleanup.com / bitmixlist.org
The easiest way to find the desired key is to extract all data and then use the address as identifier to search the file and then find the key that relates to that particular address.

I read somewhere on Github that the wallet.dat file format sometimes does not write keys and values contiguously but rather in arbitrary positions, so we may end up with a bunch of rows like HCP's output but with keys and values split along multiple rows.

For example, the output lists 84 entries. I highly doubt Bitcoin Core's code defines that many keys.

The real issue is extracting the encrypted data... I'm not sure if the db_dump "-P" parameter is actually able to decrypt the encrypted keys or if it is meant for some other purpose. I can't get it to produce the unencrypted keys in the db_dump output. Undecided

I guess no, Bitcoin Core wallet only encrypt some information (e.g. private key and master private key), while others (e.g. public key and address) isn't encrypted.

It won't because a generic database dumper is not designed to specifically read AES keys and decrypt them with a secret stored somewhere else in the file.
member
Activity: 180
Merit: 38
Code:
sudo apt-get install db-util

Code:
db_dump -d a wallet.dat > walletdump.txt

I've tried it for a bit & looks like it works. However since it dump all data and looks like all dumped data use HEX format, there are few problem
1. We don't know the data is private key, public key or other data (maybe it's possible by check HEX length).
2. We don't know whether is encrypted or not (since the tools worked on both encrypted & non-encrypted wallet).
3. Some regex needed to select only needed data (Assuming the characteristic of data you're looking for).

That's just a basic syntax and using designated database tools/utility since the topic starter wrote he wanted something else then Python.
Of course you can extend it into a script that will plot keys and addresses and the relevant info you desire since its a btree and you can use bytekeys.
Oracle can deal with encryption but from what i can see it is likely that if there is any encrypted data then it will be encrypted and decrypted by the bitcoin application itself, in such case the database file is just a structure that hold keys it does not know anything about encryption it just stores the data so in those cases a encryption switch parameter can not be used to work with encrypted data in the tree.
The easiest way to find the desired key is to extract all data and then use the address as identifier to search the file and then find the key that relates to that particular address.
HCP
legendary
Activity: 2086
Merit: 4361
1. We don't know the data is private key, public key or other data (maybe it's possible by check HEX length).
Should be easy enough given that the Database Schema is public knowledge...

Even some basic "trial and error" type testing using PyWallet output or other known data (private keys/public keys etc) should mean it would be relatively simple (if somewhat time consuming) to be able to determine which outputs in the db_dump are the private keys etc.

for instance:
Code:
page 4: btree leaf: LSN [0][1]: level 1
        prev:    0 next:   84 entries:   90 offset: 2432
        [000] 8148 len:  39 data: 04636b6579210201df7814f1bfdc65694230553d9fea229e34fc096b27611881e5924e045e955e
        [001] 8064 len:  81 data: 305048baa5553a6e650272560b11709abe027d96d01a6cdb67646632ebb3d39bafdeb5af63e8386e899c5f03194afd925e5a0dea80180867296864761c6d4a583d21dc7b6ac7a7cea38c2eea0825a25aef
        [002] 8020 len:  39 data: 04636b6579210201e1dadb18af47c2f457aeb9892e9bc9986101d2a5a32cb85fce217b191472f9
        [003] 7936 len:  81 data: 300f05de99d0a5bcaa86f15d9fc7d809b45a61f6826e559d0041f48f5fa4568b495cb973d7d8238a85e5afabce56e5fcfb0d7e0f66e7379f04fc181544273bfdde701587c745ce0d552d25546bcf01c69c

From PyWallet:
Code:
    "keys": [
        {
            "addr": "1NU7VwXy3Ugb1NPFYy9Y76Q6DvzMtrihuj",
            "compressed": true,
            "encrypted_privkey": "5048baa5553a6e650272560b11709abe027d96d01a6cdb67646632ebb3d39bafdeb5af63e8386e899c5f03194afd925e",
            "pubkey": "0201df7814f1bfdc65694230553d9fea229e34fc096b27611881e5924e045e955e",
            "reserve": 1
        },
        {
            "addr": "18NvdAH4yGytm6C9vpPAvrHHXLdKwaERFK",
            "compressed": true,
            "encrypted_privkey": "0f05de99d0a5bcaa86f15d9fc7d809b45a61f6826e559d0041f48f5fa4568b495cb973d7d8238a85e5afabce56e5fcfb",
            "pubkey": "0201e1dadb18af47c2f457aeb9892e9bc9986101d2a5a32cb85fce217b191472f9",
            "reserve": 1
        },

We can see that the pubkeys are in the "len 39" data with a prefix of "04636b657921":
Quote from: pywallet
"pubkey": "0201e1dadb18af47c2f457aeb9892e9bc9986101d2a5a32cb85fce217b191472f9"
Quote from: db_dump
[000] 8148 len:  39 data: 04636b6579210201df7814f1bfdc65694230553d9fea229e34fc096b27611881e5924e045e955e


We can also see that the encrypted privkey data is in the "len81" data with a prefix of "30" and a unique trailing sequence of bytes:
Quote from: pywallet
"encrypted_privkey": "5048baa5553a6e650272560b11709abe027d96d01a6cdb67646632ebb3d39bafdeb5af63e8386e8 99c5f03194afd925e",
Quote from: db_dump
[001] 8064 len:  81 data: 305048baa5553a6e650272560b11709abe027d96d01a6cdb67646632ebb3d39bafdeb5af63e8386e8 99c5f03194afd925e5a0dea80180867296864761c6d4a583d21dc7b6ac7a7cea38c2eea0825a25aef


The real issue is extracting the encrypted data... I'm not sure if the db_dump "-P" parameter is actually able to decrypt the encrypted keys or if it is meant for some other purpose. I can't get it to produce the unencrypted keys in the db_dump output. Undecided
member
Activity: 180
Merit: 38
Code:
sudo apt-get install db-util

Code:
db_dump -d a wallet.dat > walletdump.txt
legendary
Activity: 1568
Merit: 6660
bitcoincleanup.com / bitmixlist.org
While the answers have already been given above, I just want to make a point that you cannot open wallet.dat files in a text editor because they are binary files (Berkeley DB 4.8 files to be specific), which is why specialized tools like pywallet or bitcoin-wallet are required for reading the file contents.
legendary
Activity: 3374
Merit: 3095
Playbet.io - Crypto Casino and Sportsbook
I just want to add this from ETFbitcoin post above.
If you are going to test it with a different Electrum version I suggest you make a copy of the wallet.dat before you import it to any version of Electrum.

I think Electrum doesn't generate a wallet.dat file it usually generates a wallet file with no extension.

So it should be work on BitcoinQT just make sure to make an original copy of wallet.dat before you use any tool to decrypt and extract the private key.


Why not try to install Bitcoin core instead and replace the new wallet.dat with your old wallet.dat file and maybe your old addresses might show up on the addresses tab.
Or if you know the exact address of your wallet you can go to the console tab to dump the private key of a single address.

Here's the sample command

Code:
dumpprivkey "yourbitcoinaddress"

Remove double quote after you replace it with your BTC address and if the wallet.dat file is encrypted with a passphrase do this command first.

Code:
walletpassphrase mypassword 60

60 is the time limit in seconds. After that apply the code above with the exact address the result should be your private key in WIF format. You can import it to any wallet like Electrum.
newbie
Activity: 4
Merit: 12
I've been trying to install the right version of python and packages etc to run pywallet all morning, it's just error after error. even tried an "installer" and it just tries to grab packages and times out. all I want to do is get private keys from an old wallet.dat. can someone just make a simple gui or something? why cannot i just extract them using notepad? I just need to know which numbers are the private keys. someone let me know what to do. thanks

edit: i think this was actually made with an early version of electrum... somehow i have to convert it or find an old version of electrum that can read it
Jump to: