Skip to main content

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").

warning

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)