2025-11-19
Data-at-Rest-Verschlüsselung in DuckDB
Lotte Felius, Hannes Mühleisen
Wenn Sie Verschlüsselung in DuckDB nutzen möchten, empfehlen wir die neueste stabile Version, v1.4.2. Details stehen im Blogbeitrag zum neuesten Release.
Vor vielen Jahren haben wir das ausgezeichnete „Code Book“ von Simon Singh gelesen. Wussten Sie, dass Maria Stuart eine Verschlüsselungsmethode, die auf Julius Cäsar zurückgeht, nutzte, um ihre pikanten Briefe zu verschlüsseln? Aber ach: Die Chiffre wurde gebrochen, und der Inhalt der Briefe brachte sie auf das Schafott.
Diese Tage sind starke Verschlüsselungssoftware und -hardware eine Commodity. Moderne CPUs kommen mit spezialisierten Kryptographie-Instruktionen, und Betriebssysteme groß und klein enthalten weitgehend robuste Kryptographiesoftware wie OpenSSL.
Datenbanken speichern beliebige Informationen, und es ist klar, dass viele, wenn nicht die meisten Datensätze von Wert vielleicht nicht jedem im Klartext zur Verfügung stehen sollten. Selbst auf streng kontrollierter Hardware wie einer Cloud-VM gab es viele Fälle, in denen Dateien durch verschiedene Privilege Escalations verloren gingen. Wenig überraschend „empfehlen“ Compliance-Frameworks wie das gängige SOC 2 nachdrücklich, Daten zu verschlüsseln, wenn sie auf Speichermedien wie Festplatten liegen.
Datenbanksysteme und Verschlüsselung haben allerdings eine etwas problematische Erfolgsbilanz. Selbst PostgreSQL, das selbsternannte „The World’s Most Advanced Open Source Relational Database“, hat sehr begrenzte Optionen für Datenverschlüsselung. SQLite, die „Most Widely Deployed and Used Database Engine“ der Welt, unterstützt Datenverschlüsselung nicht out-of-the-box, die Encryption-Extension ist ein 2000-Dollar-Add-on.
DuckDB unterstützt Parquet Modular Encryption schon eine Weile. Dieses Feature erlaubt das Lesen und Schreiben von Parquet-Dateien mit verschlüsselten Spalten. Parquet-Dateien sind großartig und Berichte über ihren bevorstehenden Tod sind stark übertrieben – sie können aber zum Beispiel nicht in place aktualisiert werden, ein ziemlich grundlegendes Feature eines Datenbankmanagementsystems.
Ab DuckDB 1.4.0 unterstützt DuckDB transparente Datenverschlüsselung von Data-at-Rest mit industrieüblicher AES-Verschlüsselung.
DuckDBs Verschlüsselung erfüllt die offiziellen NIST-Anforderungen noch nicht. Folgen Sie Issue
#20162„Store and verify tag for canary encryption“, um unseren Fortschritt zur NIST-Compliance zu verfolgen.
Einige Grundlagen der Verschlüsselung
Es gibt viele verschiedene Wege, Daten zu verschlüsseln, einige sicherer als andere. In Datenbanksystemen und anderswo ist der Standard der Advanced Encryption Standard (AES), ein Blockchiffre-Algorithmus, standardisiert vom US NIST. AES ist ein symmetrischer Verschlüsselungsalgorithmus, das heißt, derselbe Schlüssel wird sowohl zum Verschlüsseln als auch zum Entschlüsseln genutzt.
Deshalb wählen die meisten Systeme, nur randomisierte Verschlüsselung zu unterstützen, das heißt, identische Plaintexts ergeben immer unterschiedliche Ciphertexts (wenn korrekt genutzt!). Der am häufigsten genutzte Industriestandard und empfohlene Verschlüsselungsalgorithmus ist AES – Galois Counter Mode (AES-GCM). Denn neben der Randomisierung authentifiziert er Daten auch, indem er ein Tag berechnet, um sicherzustellen, dass die Daten nicht manipuliert wurden.
DuckDB v1.4 unterstützt Verschlüsselung at rest mit den Chiffren AES-GCM-256 und AES-CTR-256 (Counter Mode). AES-CTR ist eine einfachere und schnellere Version von AES-GCM, aber weniger sicher, weil es keine Authentifizierung durch ein Tag bietet. Die 256 bezieht sich auf die Schlüsselgröße in Bits, das heißt, DuckDB unterstützt GCM jetzt nur mit 32-Byte-Schlüsseln.
GCM und CTR brauchen als Eingabe (1) einen Plaintext, (2) einen Initialization Vector (IV) und (3) einen Encryption Key. Plaintext ist der Text, den ein Nutzer verschlüsseln will. Ein IV ist ein eindeutiger Bytestream von üblicherweise 16 Bytes, der sicherstellt, dass identische Plaintexts in unterschiedliche Ciphertexts verschlüsselt werden. Ein Number Used Once (Nonce) ist ein Bytestream von üblicherweise 12 Bytes, der zusammen mit einem 4-Byte-Counter den IV konstruiert. Der IV muss für jeden verschlüsselten Block eindeutig sein, muss aber nicht unbedingt zufällig sein. Wiederverwendung desselben IV ist problematisch, weil ein Angreifer die zwei Ciphertexts XOR-en und beide Nachrichten extrahieren könnte. Das Tag in AES-GCM wird berechnet, nachdem alle Blöcke verschlüsselt sind, ziemlich wie eine Checksum, fügt aber eine Integritätsprüfung hinzu, die den gesamten Ciphertext sicher authentifiziert.
Implementierung in DuckDB
Bevor wir tiefer ins Wie der Verschlüsselung in DuckDB einsteigen, erklären wir ein paar Dinge zum DuckDB-Dateiformat.
DuckDB hat einen Main Database Header, der Daten speichert, mit denen eine DuckDB-Datenbank korrekt geladen und verifiziert werden kann. Am Anfang jedes DuckDB-Main-Database-Headers werden die Magic Bytes („DUCKDB“) gespeichert und beim Initialisieren gelesen, um zu prüfen, ob die Datei eine gültige DuckDB-Datenbankdatei ist. Auf die Magic Bytes folgen vier 8-Byte-Flags, die für verschiedene Zwecke gesetzt werden können.
Wenn eine Datenbank in DuckDB verschlüsselt ist, bleibt der Main Database Header jederzeit im Klartext, weil der Main Header keine sensiblen Daten über den Inhalt der Datenbankdatei enthält. Beim Initialisieren einer verschlüsselten Datenbank setzt DuckDB das erste Bit im ersten Flag, um anzuzeigen, dass die Datenbank verschlüsselt ist. Nach dem Setzen dieses Bits werden zusätzliche Metadaten gespeichert, die für die Verschlüsselung nötig sind. Diese Metadaten umfassen (1) den Database Identifier, (2) 8 Bytes zusätzliche Metadaten, z. B. für die genutzte Encryption Cipher, und (3) den verschlüsselten Canary.
Der Database Identifier wird als „Salt“ genutzt und besteht aus 16 zufällig erzeugten Bytes, die beim Initialisieren jeder Datenbank entstehen. Das Salt wird oft genutzt, um Eindeutigkeit sicherzustellen, d. h., es sorgt dafür, dass identische Input Keys oder Passwörter in unterschiedliche Derived Keys umgewandelt werden. Die 8 Bytes Metadaten umfassen die Key Derivation Function (erstes Byte), die Nutzung zusätzlicher authentifizierter Daten (zweites Byte), die Encryption Cipher (drittes Byte) und die Schlüssellänge (fünftes Byte). Nach den Metadaten nutzt der Main Header den verschlüsselten Canary, um zu prüfen, ob der Input Key korrekt ist.
Encryption-Key-Management
Um Daten in DuckDB zu verschlüsseln, können Sie praktisch jeden Plaintext- oder Base64-kodierten String nutzen, wir empfehlen aber einen sicheren 32-Byte-Base64-Schlüssel. Der Nutzer selbst ist für das Key-Management und damit für einen sicheren Schlüssel verantwortlich. Statt den vom Nutzer bereitgestellten Plain Key direkt zu nutzen, leitet DuckDB immer einen sichereren Schlüssel über eine Key Derivation Function (kdf) ab. Die kdf ist eine Funktion, die den Input Key auf einen sicheren 32-Byte-Schlüssel reduziert oder erweitert. Wird die Korrektheit des Input Keys geprüft, indem der Secure Key abgeleitet und der Canary entschlüsselt wird, wird der Derived Key in einem sicheren Encryption-Key-Cache verwaltet. Dieser Cache verwaltet Encryption Keys für den aktuellen DuckDB-Kontext und stellt sicher, dass die Derived Encryption Keys nie auf die Platte geswappt werden, indem er seinen Speicher lockt. Um die Sicherheit weiter zu stärken, werden die ursprünglichen Input Keys sofort aus dem Speicher gewischt, sobald sie in sichere Derived Keys umgewandelt sind.
DuckDB-Blockstruktur
Nach dem Main Database Header speichert DuckDB zwei 4-KB-Datenbankheader, die mehr Informationen enthalten, z. B. über die Block-(Header-)Größe und die genutzte Storage-Version. Nachdem der Main Database Header im Klartext bleibt, werden alle restlichen Header und Blöcke verschlüsselt, wenn Verschlüsselung genutzt wird.
Blöcke in DuckDB sind standardmäßig 256 KB, ihre Größe ist aber konfigurierbar. Am Anfang jedes Klartext-Blocks steht ein 8-Byte-Blockheader, der eine 8-Byte-Checksum speichert. Die Checksum ist eine einfache Berechnung, die in Datenbanksystemen oft genutzt wird, um korrupte Daten zu erkennen.
Bei verschlüsselten Blöcken besteht der Blockheader hingegen aus 40 Bytes statt 8 Bytes für die Checksum. Der Blockheader für verschlüsselte Blöcke enthält einen 16-Byte-Nonce/IV und optional ein 16-Byte-Tag, je nachdem, welche Encryption Cipher genutzt wird. Nonce und Tag werden im Klartext gespeichert, die Checksum wird aber für bessere Sicherheit verschlüsselt. Der Blockheader muss immer 8-Byte-aligned sein, um die Checksum zu berechnen.
Write-Ahead-Log-Verschlüsselung
Das Write Ahead Log (WAL) in Datenbanksystemen ist ein Crash-Recovery-Mechanismus, um Durability sicherzustellen. Es ist eine Append-only-Datei, die genutzt wird, wenn die Datenbank crasht oder abrupt geschlossen wird und noch nicht alle Änderungen in die Hauptdatenbankdatei geschrieben sind. Das WAL stellt sicher, dass diese Änderungen bis zum letzten Checkpoint wiederholbar sind; ein Checkpoint ist ein konsistenter Snapshot der Datenbank zu einem bestimmten Zeitpunkt. Das heißt, wenn ein Checkpoint erzwungen wird, was in DuckDB entweder (1) beim Schließen der Datenbank oder (2) beim Erreichen einer bestimmten Storage-Schwelle geschieht, wird das WAL in die Hauptdatenbankdatei geschrieben.
In DuckDB können Sie die Erzeugung eines WAL erzwingen, indem Sie setzen
PRAGMA disable_checkpoint_on_shutdown;PRAGMA wal_autocheckpoint = '1TB';So deaktivieren Sie Checkpointing beim Schließen der Datenbank, das heißt, das WAL wird nicht in die Hauptdatenbankdatei gemerged. Zusätzlich vermeidet das Setzen von wal_autocheckpoint auf eine hohe Schwelle Zwischen-Checkpoints, und das WAL bleibt bestehen. Zum Beispiel können wir eine persistente WAL-Datei erzeugen, indem wir zuerst die obigen PRAGMAs setzen, dann eine verschlüsselte Datenbank attachen und dann eine Tabelle erzeugen, in die wir 3 Werte einfügen.
ATTACH 'encrypted.db' AS enc ( ENCRYPTION_KEY 'asdf', ENCRYPTION_CIPHER 'GCM');CREATE TABLE enc.test (a INTEGER, b INTEGER);INSERT INTO enc.test VALUES (11, 22), (13, 22), (12, 21)Wenn wir den DuckDB-Prozess jetzt schließen, sehen wir eine .wal-Datei: encrypted.db.wal. Aber wie wird das WAL intern erzeugt?
Bevor neue Einträge (Inserts, Updates, Deletes) in die Datenbank geschrieben werden, werden diese Einträge im Wesentlichen geloggt und an das WAL angehängt. Erst nachdem geloggte Einträge auf die Platte geflusht sind, gilt eine Transaktion als committed. Ein Klartext-WAL-Eintrag hat die folgende Struktur:
Da das WAL append-only ist, verschlüsseln wir einen WAL-Eintrag pro Wert. Für AES-GCM heißt das, dass wir jedem Eintrag einen Nonce und ein Tag anhängen. Die Struktur, in der wir das tun, ist unten dargestellt. Wenn wir einen verschlüsselten Eintrag ins verschlüsselte WAL serialisieren, speichern wir zuerst die Länge im Klartext, weil wir wissen müssen, wie viele Bytes wir entschlüsseln sollen. Auf die Länge folgt ein Nonce, dem die verschlüsselte Checksum und der verschlüsselte Eintrag selbst folgen. Nach dem Eintrag wird ein 16-Byte-Tag zur Verifikation gespeichert.
Das Verschlüsseln des WAL wird standardmäßig ausgelöst, wenn ein Encryption Key für eine (un)verschlüsselte Datenbank angegeben wird.
Verschlüsselung temporärer Dateien
Temporäre Dateien speichern Zwischendaten, die oft für große Out-of-Core-Operationen wie Sortieren, große Joins und Window Functions nötig sind. Diese Daten können sensible Informationen enthalten und können im Crashfall auf der Platte bleiben. Um diese Überbleibsel zu schützen, verschlüsselt DuckDB temporäre Dateien automatisch mit.
Die Struktur temporärer Dateien
Es gibt drei verschiedene Typen temporärer Dateien in DuckDB: (1) temporäre Dateien mit demselben Layout wie ein regulärer 256-KB-Block, (2) komprimierte temporäre Dateien und (3) temporäre Dateien, die die Standard-256-KB-Blockgröße überschreiten. Die ersten beiden haben das Suffix .tmp, letztere unterscheidet sich durch das Suffix .block. Um die Größe von .block-Tempdateien zu tracken, werden sie immer mit ihrer Länge präfixiert. Im Gegensatz zu regulären Datenbankblöcken enthalten temporäre Dateien keine Checksum zur Prüfung auf Datenkorruption, weil die Berechnung einer Checksum etwas teuer ist.
Temporäre Dateien verschlüsseln
Temporäre Dateien werden (1) automatisch verschlüsselt, wenn Sie eine verschlüsselte Datenbank attachen, oder (2) wenn Sie die Einstellung SET temp_file_encryption = true nutzen. Im letzteren Fall ist die Hauptdatenbankdatei Klartext, die temporären Dateien werden aber verschlüsselt. Für die Verschlüsselung temporärer Dateien erzeugt DuckDB intern Temporary Keys. Das heißt, wenn die Datenbank crasht, gehen die Temporary Keys ebenfalls verloren. Temporäre Dateien können in diesem Fall nicht entschlüsselt werden und sind dann im Wesentlichen Müll.
Um DuckDB zu zwingen, temporäre Dateien zu erzeugen, können Sie einen einfachen Trick nutzen und einfach das Speicherlimit niedrig setzen. Das erzeugt temporäre Dateien, sobald das Speicherlimit überschritten ist. Zum Beispiel können wir eine neue verschlüsselte Datenbank erzeugen, sie mit TPC-H-Daten (SF 1) laden und dann das Speicherlimit auf 1 GB setzen. Führen wir dann einen großen Join aus, zwingen wir DuckDB, Zwischendaten auf die Platte zu spillen. Zum Beispiel:
SET memory_limit = '1GB';ATTACH 'tpch_encrypted.db' AS enc ( ENCRYPTION_KEY 'asdf', ENCRYPTION_CIPHER 'cipher');USE enc;CALL dbgen(sf = 1);
ALTER TABLE lineitem RENAME TO lineitem1;CREATE TABLE lineitem2 AS FROM lineitem1;CREATE OR REPLACE TABLE ans AS SELECT l1.* , l2.* FROM lineitem1 l1 JOIN lineitem2 l2 USING (l_orderkey , l_linenumber);Diese Befehlsfolge führt dazu, dass verschlüsselte temporäre Dateien auf die Platte geschrieben werden. Sobald die Query fertig ist oder die DuckDB-Shell beendet wird, werden die temporären Dateien automatisch aufgeräumt. Im Crashfall kann es aber vorkommen, dass temporäre Dateien auf der Platte bleiben und manuell aufgeräumt werden müssen.
Wie man Verschlüsselung in DuckDB nutzt
In DuckDB können Sie (1) eine bestehende Datenbank verschlüsseln, (2) eine neue, leere verschlüsselte Datenbank initialisieren oder (3) eine Datenbank neu verschlüsseln. Zum Beispiel erzeugen wir eine neue Datenbank, laden sie mit TPC-H-Daten von Scale Factor 1 und verschlüsseln sie dann.
INSTALL tpch;LOAD tpch;ATTACH 'encrypted.duckdb' AS encrypted (ENCRYPTION_KEY 'asdf');ATTACH 'unencrypted.duckdb' AS unencrypted;USE unencrypted;CALL dbgen(sf = 1);COPY FROM DATABASE unencrypted TO encrypted;Es gibt keinen trivialen Weg zu beweisen, dass eine Datenbank verschlüsselt ist, aber korrekt verschlüsselte Daten sollten wie zufälliges Rauschen aussehen und eine hohe Entropie haben. Um zu prüfen, ob eine Datenbank tatsächlich verschlüsselt ist, können wir Tools nutzen, die die Entropie berechnen oder das Binär visualisieren, etwa ent und binocle.
Wenn wir ent nach dem Ausführen des obigen SQL-Chunks nutzen, also ent encrypted.duckdb, ergibt das eine Entropie von 7,99999 Bits pro Byte. Dasselbe für die Klartext-(unverschlüsselte) Datenbank ergibt 7,65876 Bits pro Byte. Die Klartext-Datenbank hat ebenfalls eine hohe Entropie, das liegt aber an der Kompression.
Visualisieren wir jetzt sowohl die Klartext- als auch die verschlüsselten Daten mit binocle. Für die Visualisierung haben wir sowohl eine Klartext-DuckDB-Datenbank mit Scale Factor 0,001 von TPC-H-Daten erzeugt als auch eine verschlüsselte:
Klicken Sie hier, um die Entropie einer Klartext-Datenbank zu sehen
Klicken Sie hier, um die Entropie einer verschlüsselten Datenbank zu sehen
In diesen Abbildungen sieht man klar, dass die verschlüsselte Datenbankdatei vollständig zufällig wirkt, während die Klartext-Datenbankdatei klare Struktur in ihren Binärdaten zeigt.
Um eine verschlüsselte Datenbank zu entschlüsseln, können wir folgendes SQL nutzen:
ATTACH 'encrypted.duckdb' AS encrypted (ENCRYPTION_KEY 'asdf');ATTACH 'new_unencrypted.duckdb' AS unencrypted;COPY FROM DATABASE encrypted TO unencrypted;Und um eine bestehende Datenbank neu zu verschlüsseln, können wir einfach die alte verschlüsselte Datenbank in eine neue kopieren, so:
ATTACH 'encrypted.duckdb' AS encrypted (ENCRYPTION_KEY 'asdf');ATTACH 'new_encrypted.duckdb' AS new_encrypted (ENCRYPTION_KEY 'xxxx');COPY FROM DATABASE encrypted TO new_encrypted;Der Standard-Verschlüsselungsalgorithmus ist AES GCM. Das wird empfohlen, weil er Daten auch authentifiziert, indem er ein Tag berechnet. Je nach Use Case können Sie auch AES CTR nutzen. Das ist schneller als AES GCM, weil es das Berechnen eines Tags nach dem Verschlüsseln aller Daten überspringt. Die CTR-Chiffre können Sie so angeben:
ATTACH 'encrypted.duckdb' AS encrypted ( ENCRYPTION_KEY 'asdf', ENCRYPTION_CIPHER 'CTR');Um zu tracken, welche Datenbanken verschlüsselt sind, können Sie das so abfragen:
FROM duckdb_databases();Das zeigt, welche Datenbanken verschlüsselt sind und welche Chiffre genutzt wird:
| database_name | database_oid | path | … | encrypted | cipher |
|---|---|---|---|---|---|
| encrypted | 2103 | encrypted.duckdb | … | true | GCM |
| unencrypted | 2050 | unencrypted.duckdb | … | false | NULL |
| memory | 592 | NULL | … | false | NULL |
| system | 0 | NULL | … | false | NULL |
| temp | 1995 | NULL | … | false | NULL |
5 Zeilen — 10 Spalten (5 gezeigt)
Implementierung und Performance
Hier bei DuckDB streben wir eine gute Out-of-the-Box-Erfahrung mit null externen Abhängigkeiten und kleinem Footprint an. Verschlüsselung und Entschlüsselung werden aber üblicherweise von ziemlich schweren externen Bibliotheken wie OpenSSL ausgeführt. Wir würden viel lieber nicht auf externe Bibliotheken setzen oder riesige Codebasen statisch linken, nur damit Leute Verschlüsselung in DuckDB ohne Extra-Schritte nutzen können. Deshalb haben wir Verschlüsselung in DuckDB tatsächlich zweimal umgesetzt, einmal mit der (ausgezeichneten) Bibliothek Mbed TLS und einmal mit der allgegenwärtigen Bibliothek OpenSSL.
DuckDB hat schon Teile von Mbed TLS mitgeliefert, weil wir es nutzen, um RSA-Extension-Signaturen zu verifizieren. Für maximale Kompatibilität haben wir die Hardwarebeschleunigung von MbedTLS aber tatsächlich deaktiviert, was einen Performance-Einfluss hat. Außerdem ist Mbed TLS nicht besonders gegen Dinge wie fiese Timing Attacks gehärtet. OpenSSL hingegen enthält stark geprüften und hardwarebeschleunigten Code für AES-Operationen, weshalb wir es auch für Verschlüsselung nutzen können.
In DuckDB-Land ist OpenSSL Teil der httpfs-Extension. Sobald Sie diese Extension laden, wechselt die Verschlüsselung automatisch zu OpenSSL. Nachdem wir Verschlüsselung in DuckDB 1.4.0 ausgeliefert haben, haben Sicherheitsexperten tatsächlich Probleme mit dem Zufallszahlengenerator gefunden, den wir im Mbed-TLS-Modus genutzt haben. Auch wenn es schwer wäre, das tatsächlich auszunutzen, haben wir das Schreiben in Datenbanken im MbedTLS-Modus ab DuckDB 1.4.1 deaktiviert. Stattdessen versucht DuckDB jetzt (Version 1.4.2+), die httpfs-Extension automatisch zu installieren und zu laden, wann immer ein Write versucht wird. Wir könnten das in Zukunft nochmal anschauen, aber vorerst scheint das der sicherste Weg, der trotzdem hohe Kompatibilität fürs Lesen erlaubt. Im OpenSSL-Modus haben wir immer eine kryptographisch sichere Zufallszahlenerzeugung genutzt, dieser Modus ist also nicht betroffen.
Datenbankdateien zu ver- und entschlüsseln ist ein zusätzlicher Schritt beim Schreiben von Tabellen auf die Platte, also würden wir natürlich annehmen, dass es einen Performance-Einfluss gibt. Untersuchen wir den Performance-Einfluss von DuckDBs neuem Encryption-Feature mit einem sehr einfachen Experiment.
Wir erzeugen zuerst zwei DuckDB-Datenbankdateien, eine verschlüsselte und eine unverschlüsselte. Wir nutzen den TPC-H-Benchmark-Generator wieder, um die Tabellendaten zu erzeugen, insbesondere die (etwas strapazierte) Tabelle lineitem.
INSTALL httpfs;INSTALL tpch;LOAD tpch;
ATTACH 'unencrypted.duckdb' AS unencrypted;CALL dbgen(sf = 10, catalog = 'unencrypted');
ATTACH 'encrypted.duckdb' AS encrypted (ENCRYPTION_KEY 'asdf');CREATE TABLE encrypted.lineitem AS FROM unencrypted.lineitem;Jetzt nutzen wir DuckDBs praktischen SUMMARIZE-Befehl dreimal: einmal auf der unverschlüsselten Datenbank und einmal auf der verschlüsselten Datenbank mit MbedTLS und einmal auf der verschlüsselten Datenbank mit OpenSSL. Wir setzen ein sehr niedriges Speicherlimit, um mehr Lesen und Schreiben von der Platte zu erzwingen.
SET memory_limit = '200MB';.timer on
SUMMARIZE unencrypted.lineitem;SUMMARIZE encrypted.lineitem;
LOAD httpfs; -- use OpenSSLSUMMARIZE encrypted.lineitem;Hier die Ergebnisse auf einem recht aktuellen MacBook: SUMMARIZE auf der unverschlüsselten Tabelle dauerte ca. 5,4 Sekunden. Mit Mbed TLS stieg das auf etwa 6,2 s. Mit aktiviertem OpenSSL ging die End-to-End-Zeit aber direkt wieder auf 5,4 s. Wie ist das möglich? Ist Entschlüsselung nicht teuer? Nun, in der Query-Verarbeitung passiert viel mehr als das Lesen von Blöcken aus dem Storage. Der Einfluss der Entschlüsselung ist also gar nicht so groß, selbst mit einer langsamen Implementierung. Zweitens wird der Gesamt-Overhead von Ver- und Entschlüsselung bei Hardwarebeschleunigung in OpenSSL fast vernachlässigbar.
Aber nur Summarization auszuführen ist zu simpel. Richtige™ Datenbank-Workloads enthalten Änderungen an Daten, Einfügen neuer Zeilen, Updates von Zeilen, Löschen von Zeilen usw. Auch mehrere Clients werden gleichzeitig updaten und querien. Deshalb haben wir den vollen TPC-H-„Power“-Test aus unserem vorherigen Blogbeitrag „Changing Data with Confidence and ACID“ wiederbelebt. Wir haben das Benchmark-Skript leicht angepasst, um die neue Datenbankverschlüsselung zu aktivieren. Für dieses Experiment haben wir die OpenSSL-Encryption-Implementierung genutzt, wegen der oben skizzierten Probleme. Wir beobachten „Power@Size“ und „Throughput@Size“. Ersteres ist rohe sequenzielle Query-Performance, letzteres misst mehrere parallele Query-Streams in Gegenwart von Updates.
Auf demselben MacBook mit DuckDB 1.4.1 und einem „Scale Factor“ von 100 erhalten wir eine Power@Size-Metrik von 624.296 und eine Throughput@Size-Metrik von 450.409 ohne Verschlüsselung.
Wenn wir Verschlüsselung aktivieren, bleiben die Ergebnisse fast unverändert und bestätigen die Beobachtung des kleinen Microbenchmarks oben. Das Verhältnis zwischen verfügbarem Speicher und Benchmark-Größe bedeutet aber, dass wir die Verschlüsselung temporärer Dateien nicht belasten. Deshalb haben wir alles mit einem 8-GB-Speicherlimit erneut ausgeführt. Wir haben in dieser Konfiguration ständiges Lesen und Schreiben von und auf die Platte durch Beobachtung der Betriebssystemstatistiken bestätigt. Für den unverschlüsselten Fall ging die Power@Size-Metrik erwartbar auf 591.841 und Throughput@Size auf 153.690. Und schließlich konnten wir einen leichten Performance-Rückgang mit Power@Size von 571.985 und Throughput@Size von 145.353 beobachten. Der Unterschied ist aber auch nicht sehr groß und in realen Betriebsszenarien wahrscheinlich nicht relevant.
Fazit
Mit dem neuen Feature verschlüsselter Datenbanken können wir DuckDB-Datenbankdateien jetzt sicher herumreichen, mit allen Informationen darin vollständig undurchsichtig für neugierige Augen. Das erlaubt einige interessante neue Deployment-Modelle für DuckDB: Wir könnten zum Beispiel eine verschlüsselte DuckDB-Datenbankdatei auf ein Content Delivery Network (CDN) legen. Eine Flotte von DuckDB-Instanzen könnte diese Datei read-only mit dem Decryption Key attachen. Das erlaubt elegant die effiziente Verteilung privater Background-Daten ähnlich wie verschlüsselte Parquet-Dateien, natürlich mit vielen weiteren Features wie Multi-Table-Storage. Wenn wir DuckDB mit verschlüsseltem Storage nutzen, können wir auch das Threat Modeling vereinfachen, wenn wir DuckDB zum Beispiel bei Cloud-Anbietern nutzen. Während früher der Zugriff auf DuckDB-Storage gereicht hätte, um Daten zu leaken, können wir die Paranoia bezüglich Storage jetzt etwas lockern, besonders weil temporäre Dateien und WAL ebenfalls verschlüsselt sind. Und das Beste daran: Es gibt fast keinen Performance-Overhead bei der Nutzung von Verschlüsselung in DuckDB, besonders mit der OpenSSL-Implementierung.
Wir sind sehr gespannt, was Sie mit diesem Feature machen werden, und lassen Sie uns wissen, wenn Sie auf Probleme stoßen.