Number: SLIP-0011
Title: Symmetric encryption of key-value pairs using deterministic hierarchy
Type: Standard
Status: Draft
Authors: Pavol Rusnak <[email protected]>
Marek Palatinus <[email protected]>
Karel Bilek <[email protected]>
Created: 2014-06-12
This document is explaining symmetric encryption on hardware devices, using deterministic hierarchy.
We want to provide a symmetric encryption in the hardware wallet, where the key doesn't exit the device, and where the user might be forced to confirm the encryption/decryption on the display.
The following data are sent to the hardware wallet:
- BIP 32 path
- key (that is being shown on the device)
- value
- encrypt/decrypt direction
- should user confirm on encrypt?
- should user confirm on decrypt?
- optional IV
Value is what is actually being encrypted. The key for the encryption is constructed from the private key on the BIP address, the key displayed on the device, and the two informations about whether to ask for confirmation.
It is constructed in such a way, that different path, key or the confirm information will get a different encryption key and IV. So, you cannot "skip" the confirmation by using different input.
IV can be either manually set, or it is computed together with the key.
The value must be divisible into 16-byte blocks. The application has to pad the blocks itself and ensure safety; for example, by using PKCS7.
The details are best explained on a slightly simplified code from TREZOR Python emulator.
def _cipher_keyvalue(self, address_n, key, value, encrypt, ask_on_encrypt, ask_on_decrypt, iv):
if len(value) % 16 > 0:
return Failure(message="Input length must be a multiple of 16")
private_key = BIP32(self.storage.get_node()).get_private_node(list(address_n)).private_key
key += "E1" if ask_on_encrypt else "E0"
key += "D1" if ask_on_decrypt else "D0"
secret = hmac.HMAC(key=private_key, msg=key, digestmod=hashlib.sha512).digest()
aes_key = secret[0:32]
aes_iv = iv if iv else secret[32:48]
aes = pyaes.AESModeOfOperationCBC(key=aes_key, iv=aes_iv)
if encrypt:
res = ''.join([aes.encrypt(value[i:i+16]) for i in range(0, len(value), 16)])
else:
res = ''.join([aes.decrypt(value[i:i+16]) for i in range(0, len(value), 16)])
return CipheredKeyValue(value=res)
if len(value) % 16 > 0:
return Failure(message="Input length must be a multiple of 16")
First, the value is checked, if it is divisable into 16-byte blocks, since the symmetric cipher is block cipher. The application has to pad the blocks itself and ensure safety; for example, by using PKCS7.
private_key = BIP32(self.storage.get_node()).get_private_node(list(address_n)).private_key
Private key of the BIP32 node is derived.
key += "E1" if ask_on_encrypt else "E0"
key += "D1" if ask_on_decrypt else "D0"
The key, displayed on the device, is concatenated with either E1 or E0 and either D1 or D0, depending on whether the confirmation is enabled in a given direction.
secret = hmac.HMAC(key=private_key, msg=key, digestmod=hashlib.sha512).digest()
The key, with the concatenated E1/E0 or D1/D0, is HMACed, with the private key from HD Node, with SHA512 as a hash function.
aes_key = secret[0:32]
aes_iv = iv if iv else secret[32:48]
The AES key is the first 32 bytes of the HMAC; the input vector is the next 16 bytes.
aes = pyaes.AESModeOfOperationCBC(key=aes_key, iv=aes_iv)
The algorithm is AES, in CBC mode.
if encrypt:
res = ''.join([aes.encrypt(value[i:i+16]) for i in range(0, len(value), 16)])
else:
res = ''.join([aes.decrypt(value[i:i+16]) for i in range(0, len(value), 16)])
The result are the encrypted/decrypted blocks, concatenated together.
Check test_msg_cipherkeyvalue.py for the test vectors.
The algorithm is implemented in TREZOR firmware (function fsm_msgCipherKeyValue
) and its emulator (function _cipher_keyvalue
).
It is used in algorithms described in SLIP-0015 and SLIP-0016.