Encryption at Rest
When EAR is configured, all data is encrypted at rest using AES-128-GCM-SIV. This is an authenticated cipher, meaning it will prove if the decryption is correct.
Manually decrypting
Normally you can use our native tooling (cord ear
) to manage encrypting or decrypting.
With the use of some scripting, you can manually decrypt to see another proof of encryption-at-rest.
Setup
Install sledtool, or similar tool for viewing a "sled" embedded database.
git clone https://github.com/vi/sledtool.git
cd sledtool
cargo install --path .
Create a key in treasury with ID MY_ENCRYPTED_KEY
. Note this is 4d595f454e435259505445445f4b4559
in hex.
treasury keys create MY_ENCRYPTED_KEY --threshold 1 --algorithm k256-sha2
Alternatively you can picked a key you already have, take note of it's ID (e.g. keys/100
, the 100
is the ID).
Dump signer.db
Using sledtool
, we can manually export all of the signer key state.
cd $TREASURY_HOME/signer.db/
sledtool . export > dump.json
Open signer.json and search for 4d595f454e435259505445445f4b4559, or by your hex-encoded chosen key ID. It will be nested under 6b65792d736861726573 ("key-shares").
Any internal database format is not part of our API, and you should not rely on this being stable.
Decrypt
Run the following reference script to decrypt your specific key.
python3 decrypt.py path/to/dump.json MY_ENCRYPTED_KEY
#! /usr/bin/env python3
import cryptography
from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV
from mnemonic import Mnemonic
from binascii import hexlify, unhexlify
import os,json,sys
if len(sys.argv) != 3:
print(sys.argv)
print("usage: ./decrypt.py <encrypted-export.json> <key-id>" % sys.argv[0])
sys.exit(1)
exportFile = sys.argv[1]
keyId = sys.argv[2]
# open the export
data = json.loads(open(exportFile).read())
phrase = (os.environ.get('SIGNER_EAR_PHRASE', ""))
if phrase == "":
print("must set SIGNER_EAR_PHRASE env")
sys.exit(1)
# derive the decryption key from bip39 phrase
mnem = Mnemonic("english")
entropy = mnem.to_entropy(phrase)
if len(entropy) != 16:
# 12 words is 16 bytes of entropy
print(f"expected 12 words")
sys.exit(1)
prefix1 = hexlify(b"keys/").decode("utf8")
prefix2 = hexlify(b"key-shares/").decode("utf8")
keyIdAsHex = hexlify(keyId.encode("utf8")).decode("utf8")
# scan over the "key-shares" tree of the export
keyShares = data[hexlify(b"key-shares").decode("utf8")]
for name, value in keyShares.items():
if prefix1+keyIdAsHex == name or prefix2+keyIdAsHex == name:
binValue = unhexlify(value)
nonce = binValue[:12]
ciphertext = binValue[12:]
aes_siv = AESGCMSIV(entropy)
# this is authenticated encryption, it will throw exception if it's invalid.
plaintext = aes_siv.decrypt(nonce, ciphertext, b"")
print(f"decrypted {keyId} = {hexlify(plaintext)}")
sys.exit(0)
print(f"{keyId} not found")
sys.exit(1)