RpRelPyDBRelational Python

Documentation

13. Encryption

Encrypted columns, key handling, masked exports, decrypt=True and blind indexes.

Encryption goal

Encrypted columns are encrypted before storage. RelPyDB stores ciphertext in memory and in persistence files. Public exports mask encrypted values unless decrypt=True.

Key rule: the encryption key is never saved inside the RelPyDB JSON file.

Functions and flags

ItemUsageMeaning
RelPy.generate_encryption_key()key = RelPy.generate_encryption_key()Creates a key.
RelPy(encryption_key=key)constructorCreates DB with key loaded.
set_encryption_key(key)after loadAttach key later.
is_encrypted=Trueadd_column(...)Encrypt this column on insert/update.
decrypt=Trueexports/query resultsReturn plaintext if key is loaded.

Complete example

key = RelPy.generate_encryption_key()
db = RelPy(encryption_key=key)

db.create_table("users")
db.add_column("users", "id", AutoNumber, is_primary_key=True)
db.add_column("users", "email", str, nullable=False, is_encrypted=True)

db.insert("users", {"email": "alice@example.com"})

db.to_list("users")
# [{"id": 1, "email": "[ENCRYPTED]"}]

db.to_list("users", decrypt=True)
# [{"id": 1, "email": "alice@example.com"}]

Blind indexes

Encrypted equality search uses a blind index. RelPyDB stores a keyed HMAC-like lookup value next to the ciphertext. This allows equality lookup without indexing plaintext.

db.create_index("users", "email")

rows = (
    db.query("users")
      .where(col("email") == "alice@example.com")
      .to_list(decrypt=True)
)
OperationSupportReason
==Supported with blind indexStable keyed lookup.
in_Possible by equality matchingEach candidate can be checked.
LIKERequires decrypt scanBlind index does not preserve substrings.
BETWEENRequires decrypt scanBlind index does not preserve ordering.
ORDER BYRequires plaintext at runtimeEncrypted values are not order-preserving.

Save/load with encryption

db.save("users.relpy.json")

loaded = RelPy.load("users.relpy.json")
loaded.to_list("users")
# masked values

loaded.set_encryption_key(key)
loaded.to_list("users", decrypt=True)
# plaintext values

You can also pass the key during load:

loaded = RelPy.load("users.relpy.json", encryption_key=key)

Encryption and exports — behaviour summary

Situationto_list() defaultto_list(decrypt=True)
No key loaded[ENCRYPTED]Raises EncryptionError
Key loaded[ENCRYPTED]Plaintext value
Loaded from file, key passed[ENCRYPTED]Plaintext value
Equality query on encrypted colWorks via blind index — no key needed for reads