Bitcoin blog

The essence of Bitcoin is innovation that be related to remittance, asset holding rights, liberation, and freedom.

Encrypted Private Keys

Overview
BIP0038 propose standerd specification that encrypting private key by passphrase and encode in Base58Chack.Even in situation where the key is exposed,It will be able to save to safe backup media and transfer between wallets.When performing the encryption must know the passphrase.


f:id:adrenaline2017:20170505134751j:plain


Encryption steps
1. Calculate address(ASCII),and take the first 4 bytes of SHA256 double hash

2. Generate key from the passphrase using scrypt.passphrase encoded in UTF-8 and normalize using Unicode Normalization Form C(NFC)
※n=16384, r=8, p=8, length=64(n, r, p are provisional)

3. Split the key(64 bytes) in half,and call them derivedhalf1 and derivedhalf2

4. Do AES256Encrypt, call the 16-byte result "encryptedhalf1,2"

5. Encode concatenation of the following using Base58check
※0x01 0x42 + 0xC0(flagbyte) + salt + encryptedhalf1 + encryptedhalf2
※When case of non-EC-multiplied keys without compression


Encryption code

def encrypt(privkeyhex, addr, passphrase):
    a = bytes(addr, 'ascii')
    salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4]
    key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8)
    derived_half1, derived_half2 = key[:32], key[32:]
    aes = AES.new(derived_half2)
    encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes)
    encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes)
    payload = (b'\x01' + b'\x42' + b'\xc0' +
               salt + encrypted_half1 + encrypted_half2)
    checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
    privatkey = hexlify(payload + checksum).decode('ascii')
    return Base58(privatkey)

def encrypt(privkeyhex, addr, passphrase):
→When privkeyhex wif format,Please convert "wif" to "normal"
→The value of "addr" use to be generated from "privkeyhex"

a = bytes(addr, 'ascii')
→Calculate address(ASCII)

salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4]
→Take the first 4 bytes of SHA256 double hash

key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8)
→Generate key from the passphrase using scrypt

derived_half1, derived_half2 = key[:32], key[32:]
→Split the key in half,first half call to "derivedhalf1" and latter half "derivedhalf2"

aes = AES.new(derived_half2)
AES calculation useing python module(AES.new)

encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes)
→Do AES256Encrypt,use first half of privkeyhex(32byte) and 16byte of derived_half1.
※About"_encrypt_xor" see here

payload = (b'\x01' + b'\x42' + b'\xc0' + salt + encrypted_half1 + encrypted_half2)
→0x01 0x42 + flagbyte + salt + encryptedhalf1 + encryptedhalf2

Generate encrypted private key is the Base58Check-encode the above concatenation.


Decryption steps
1. Use encrypted private key and passphrase

2. Derive derivedhalf1 and derivedhalf2 by passing the passphrase and addresshash into scrypt function

3. Decrypt encryptedhalf1 and encryptedhalf2 using AES256Decrypt

4. Merge them to form the encrypted private key


Decryption code

def decrypt(encrypted_privkey, passphrase):
    d = unhexlify(base58decode(encrypted_privkey))
    d = d[2:]   
    flagbyte = d[0:1]
    d = d[1:]
    salt = d[0:4]
    d = d[4:-4]
    key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8)
    derivedhalf1 = key[0:32]
    derivedhalf2 = key[32:64]
    encryptedhalf1 = d[0:16]
    encryptedhalf2 = d[16:32]
    aes = AES.new(derivedhalf2)
    decryptedhalf2 = aes.decrypt(encryptedhalf2)
    decryptedhalf1 = aes.decrypt(encryptedhalf1)
    privraw = decryptedhalf1 + decryptedhalf2
    privraw = ('4x' % (int(hexlify(privraw), 16) ^
                          int(hexlify(derivedhalf1), 16)))
    return privraw

def decrypt(encrypted_privkey, passphrase):
→Use encrypted private key and passphrase

d = unhexlify(base58decode(encrypted_privkey))
→Decode encrypted private key by Base58Check
→Binary data representing a character string in hexadecimal notation

d = d[1:]
→get payload

d = d[4:-4]
→Encrypted_half1 + encrypted_half2(last 4 bytes is checksum,so cut out)

derivedhalf1 = key[0:32]
→Derive derivedhalf1 and derivedhalf2 by passing the passphrase and addresshash into scrypt function

decryptedhalf1 = aes.decrypt(encryptedhalf1)
→Decrypt encryptedhalf1 using AES256Decrypt

Merge them to form the encrypted private key


The code is used in this article,it have posted on GitHub.