For reference, here is the whole thing. It works for me and I will not be changing anything but bugs.
There are more elegant and slick ways of doing this, but this is the simplest way I could break it down
I Suggest you make your own wallet with a known password to experiment with.
import hashlib
import binascii
import pyaes
import os
import random
# Wallet software generates a random 32 byte master key. (How this is done is well documented and is not
# explained here). The master key needs to be encrypted to protect your funds. The one below is made up.
# You can substitute your own key below to try the software but DO NOT publish your own key anywhere.
mkey = binascii.unhexlify("5c5692daff165d3d32e5c05a56dde3b2d0ebc05f133f8d0616941e9abe7e0fb0")
# Wallet software will ask you for a password to encrypt the master key.
# You can change the password below for your own, but don't lose the b in front.
pw_txt = "alberto" #Thank you AlbertoBSD for Keyhunt!
password = b'alberto'
# Also needed is some 'randomness' in the form of a salt and an iteration count. These are generated by the wallet.
# Use the # to comment out the salt and iteration count below if testing your own values.
# create a random 8 byte salt and iteration count.
# salt = os.urandom(8)
# iterationcount = random.randint(2500, 50000)
# Now if you run the program several times, you will see you get different wallet keys for the same password.
# The random salt and iterations help prevent duplicate wallet keys for the same password.
# These two values are saved in wallet.dat so that the wallet key can be recreated (not decrypted) if you know the password.
# here you can substitute your own salt and iteration count from a wallet. Remove the # in front
salt = binascii.unhexlify('a49e804e25740714')
iterationcount = 91705
# Hashing with SHA512. This process generates the wallet key and IV. It is one-way. You cannot go back from
# here to recover the password, which is why it is so important you remember it.
# It is also essential that the password is strong because this is where dictionary attacks can take place.
# Remember the salt & iterations are recoverable from the wallet.dat files!
hash512 = password + salt
i = 0
while i < iterationcount:
hash512 = hashlib.sha512(hash512).digest()
i = i + 1
# The wallet key is the first 32 bytes of the hash result.
wallet_key = hash512[0:32]
# The IV (initialization vector) is the next 16 bytes of the hash
iv = hash512[32:48]
# We now have all we need to encrypt the master key
# Encryption with AES-256-CBC
encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(wallet_key, iv))
ciphertext = encrypter.feed(mkey)
ciphertext += encrypter.feed()
# The cipher text is what you see in the wallet.dat files as Mkey followed by 48 bytes
# There are also Ckey's which hold the private spend keys for each address.
#######################################################################################
# Your master private key is now encrypted!!
#######################################################################################
# Decryption is the reverse process using the same wallet key and IV to process the ciphertext (Mkey)
# Decryption with AES-256-CBC
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(wallet_key, iv))
decryptedData = decrypter.feed(ciphertext)
decryptedData += decrypter.feed()
# The master key is now decrypted for all the world to see.
# Before we can spend any funds we need to decrypt the Ckeys which hold the private spend keys to the addresses.
# As you can see with no Ckey's, wallet recovery services cannot access your funds. It is safe to send Mkey ciphertext
# iteration count and salt but not the wallet itself as this has ALL the keys.
# ======================================================================================================================
# Now to decrypt a Ckey which holds the private spend key to an address. Here's one I made earlier.
# public address: 18sGovZjm83NhxXQb1bsxogCqXiYE5nXn7
ckey = binascii.unhexlify("316787cf83a3c9caeca2a2b20f0879edc2a8c60ed127453c00330d1ac009402d78eff292b03e588a3b858410ea2628ed")
public_key = binascii.unhexlify("027a098dbada15a831c66491cb20dee174d4971fdd9766a3b6c3dc64562cbb6524")
# Hash the public key twice
hash1 = hashlib.sha256(public_key).digest()
hash2 = hashlib.sha256(hash1).digest()
# The IV to the private key is the first 16 bytes of the hash result.
pk_iv = hash2[0:16]
# Decrypt the ciphertext Ckey with the master key and IV
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(decryptedData, pk_iv))
decryptedckey = decrypter.feed(ckey)
# The plain text 32 byte raw key is now visible to the world!
# You can put the key into one of the online services and see the result is the compressed public address above.
# Display results
def printData(text, data):
print(text + "(hex) :" + data.hex())
print("Password ", pw_txt)
printData("SHA512 ", hash512)
printData("Wallet Key ", wallet_key)
printData("16 bytes IV ", iv)
printData("Encrypting ", mkey)
printData("Ciphertext ", ciphertext)
# Decrypted data
printData("Master key ", decryptedData)
# Ckey output
printData("\nCkey :", ckey)
printData("Public Double hash :", hash2)
printData("PK IV :", pk_iv)
printData("Private Key :", decryptedckey)
printData("to public Key :", public_key)
# If you have found this useful please consider a small BTC tip to bc1qg82720gvdyvnc3jycnun348fhs3qam44nsmeqm