I had been wondering about the process of encrypting a wallet but could not find a simple enough explanation of how it works (I am a newbie!) So I thought I would do some investigating. The result was that I put together a little python script which explains it to me. I thought I would post it here in case it is of use to others. The base code came from
https://stackoverflow.com/questions/61378005/decrypting-aes-256-cbc-cipher-with-a-passcode-built-using-pbkdf2-in-python. The rest of the information I pieced together from various places on the internet. The explanation is in the code. Install the dependencies, run it, look at the results and then read through the heavily commented code to understand the process. If I have made any mistakes, please let me know.
from base64 import b64encode
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("5c5692da0f165d3d32e5c05a56dde9b2d0ebc05f100f8d0616941e9abe7e0fb0")
# 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.
password = b'password123'
# 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)
# 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 = 91005
# 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.
# 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 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 = hashlib.pbkdf2_hmac('sha512', password, salt, iterationcount)
# The wallet key is the first 32 bytes of the hash.
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.
# However, before we can spend any funds we need to decrypt the Ckeys which hold the private 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.
# Decrypting Ckeys will be the subject of another post.
# If you have found this useful please consider a small BTC tip to bc1qg82720gvdyvnc3jycnun348fhs3qam44nsmeqm
# Display results
def printData(text, data):
print(text + " (hex) :" + data.hex())
print(text + " (Base64):" + b64encode(data).decode('utf8') + "\n")
printData("SHA512 ", hash512)
printData("Wallet Key", wallet_key)
printData("16 bytes IV", iv)
printData("Master Key ", mkey)
printData("Ciphertext ", ciphertext)
# Decrypted data
printData("Decrypted ", decryptedData)