Skip to content

Instantly share code, notes, and snippets.

@ambroff
Created May 16, 2021 05:43
Show Gist options
  • Select an option

  • Save ambroff/09b6084c3ea2c5af2288b25c39f51552 to your computer and use it in GitHub Desktop.

Select an option

Save ambroff/09b6084c3ea2c5af2288b25c39f51552 to your computer and use it in GitHub Desktop.
Extract the dataset encryption keys from a truenas settings export file
#!/usr/bin/env python
import base64
import sys
import sqlite3
from Crypto.Cipher import AES
from Crypto.Util import Counter
def main(argv):
aes_key = read_aes_key()
keys = load_encrypted_keys()
for dataset_name, encrypted_key in keys.items():
decrypted = decrypt(encrypted_key, aes_key)
print(dataset_name)
print(decrypted)
print()
return 0
def read_aes_key():
with open('pwenc_secret', 'rb') as f:
return f.read()
def load_encrypted_keys():
d = {}
with sqlite3.connect('freenas-v1.db') as conn:
for row in conn.execute('SELECT name, encryption_key FROM storage_encrypteddataset'):
d[row[0]] = row[1]
return d
def decrypt(encrypted, key):
if not encrypted:
return ''
encrypted = base64.b64decode(encrypted)
nonce = encrypted[:8]
encrypted = encrypted[8:]
cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(64, prefix=nonce))
return cipher.decrypt(encrypted).rstrip(b'{').decode('utf8')
if __name__ == '__main__':
sys.exit(main(sys.argv))
@aviv926

aviv926 commented May 6, 2026

Copy link
Copy Markdown

thanks!
i have edit your script to add:

- Added extraction from `storage_replication.repl_encryption_key` (not just `storage_encrypteddataset`).
  This one is useful where set to store the encryption key in the TrueNAS database.

- Labeled output source so you can see where each key comes from.
- For replication keys, added task/format/location display.
- Show decrypted replication key plus raw encrypted value.
- Added clear message when no keys are found.
- Added end prompt to optionally export all results to `truenas_extracted_keys.json`.
#!/usr/bin/env python                                                                                                                                                                         

import base64
import json
import sys
import sqlite3

from Crypto.Cipher import AES
from Crypto.Util import Counter

def main(argv):
   aes_key = read_aes_key()

   dataset_keys = load_dataset_keys()
   replication_keys = load_replication_keys()
   export_rows = []

   for dataset_name, encrypted_key in dataset_keys.items():
       decrypted = decrypt(encrypted_key, aes_key)
       print('source: storage_encrypteddataset')
       print(dataset_name)
       print(decrypted)
       print()
       export_rows.append({
           'source': 'storage_encrypteddataset',
           'name': dataset_name,
           'decrypted_key': decrypted,
           'raw_encryption_key': encrypted_key,
       })

   for repl_name, encrypted_key, key_format, key_location in replication_keys:
       decrypted = decrypt(encrypted_key, aes_key)
       print('source: storage_replication (replication key)')
       print(f'task: {repl_name}')
       print(f'format: {key_format}')
       print(f'location: {key_location}')
       print(f'{decrypted} ({encrypted_key})')
       print()
       export_rows.append({
           'source': 'storage_replication',
           'task': repl_name,
           'format': key_format,
           'location': key_location,
           'decrypted_key': decrypted,
           'raw_repl_encryption_key': encrypted_key,
       })

   if not dataset_keys and not replication_keys:
       print('No encrypted keys found in storage_encrypteddataset or storage_replication.')
   else:
       export_json_prompt(export_rows)

   return 0

def export_json_prompt(rows):
   answer = input('Export extracted keys to JSON? [y/N]: ').strip().lower()
   if answer not in ('y', 'yes'):
       print('JSON export skipped.')
       return

   path = 'truenas_extracted_keys.json'
   with open(path, 'w', encoding='utf-8') as f:
       json.dump(rows, f, indent=2, ensure_ascii=False)
   print(f'Exported JSON to {path}')

def read_aes_key():
   with open('pwenc_secret', 'rb') as f:
       return f.read()

def load_dataset_keys():
   d = {}
   with sqlite3.connect('freenas-v1.db') as conn:
       for row in conn.execute('SELECT name, encryption_key FROM storage_encrypteddataset'):
           d[row[0]] = row[1]
   return d

def load_replication_keys():
   rows = []
   with sqlite3.connect('freenas-v1.db') as conn:
       query = '''
           SELECT repl_name, repl_encryption_key, repl_encryption_key_format, repl_encryption_key_location
           FROM storage_replication
           WHERE repl_encryption_key IS NOT NULL
       '''
       for row in conn.execute(query):
           rows.append((row[0], row[1], row[2], row[3]))
   return rows

def decrypt(encrypted, key):
   if not encrypted:
       return ''

   encrypted = base64.b64decode(encrypted)
   nonce = encrypted[:8]
   encrypted = encrypted[8:]
   cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(64, prefix=nonce))
   return cipher.decrypt(encrypted).rstrip(b'{').decode('utf8')


if __name__ == '__main__':
   sys.exit(main(sys.argv))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment