Der Linux-Kernel bietet seine Verschlüsselungsfunktionen auch zum asynchronen Zugriff an. Ineinander geschachtelte Datenstrukturen und undokumentierte Funktionen machen dem Programmierer allerdings das Leben schwer. Diese Kern-Technik schafft Abhilfe.
Wenn die Kernelentwickler von Datentransformation sprechen, meinen sie damit das Ver- und Entschlüsseln, Komprimieren oder das Bilden von Hashsummen. Geeignete Interfaces stellen den Zugriff auf die dafür implementierten Algorithmen sicher, die ihrerseits über definierte Schnittstellen an das Crypto-Subsystem des Linux-Kernels andocken.
Synchron – asynchron
Bisher finden die Transformationen vorwiegend synchron statt, wie es die vorige Ausgabe der Kern-Technik beschrieben hat [1]: Ein User, beispielsweise die Festplattenverschlüsselung, übergibt einen Verschlüsselungsauftrag an das Crypto-Subsystem und wartet bis zum Abschluss der Transformation. Erst dann erteilt er den nächsten Auftrag. Hardware-Verschlüsselung oder Parallelverarbeitung kommen dabei aber nicht zum Zug. Performanter geht es mit dem asynchronen Zugriff, wie ihn auch die Diplomarbeit von Sebastian Siewior [2] vorstellt.
Asynchron bedeutet, dass der User dem Crypto-Subsystem einen oder mehrere Aufträge zum Bearbeiten übergibt und die erfolgreiche oder vergebliche Auftragsbearbeitung zu einem späteren Zeitpunkt prüft (Abbildung 1). Der angeforderte Algorithmus wiederum muss die Aufträge in die Warteschlange einreihen und starten. Die Nachricht über Ge- oder Misslingen teilt das Crypto-Subsystem dem User später mit, indem es eine Callback-Funktion aufruft.

Abbildung 1: Der synchrone Zugriff (oben) auf das Crypto-API des Kernels bringt Wartezeiten mit sich. Bessere Performance verspricht die asynchrone Variante (unten).
In der Theorie mag das recht einfach erscheinen, in der Praxis erweisen sich ineinander geschachtelte Datenstrukturen und nicht dokumentierte Funktionen als Stolpersteine. Grundwissen und Quellcodestudium helfen weiter.
Dabei ist für jene, die performant verschlüsseln möchten, der erste Schritt noch einfach: Er spezifiziert den gewünschten Algorithmus und reserviert mit dieser Information das Transform-Objekt vom Typ »ablkcipher_tfm« . Existiert für den Algorithmus keine asynchrone Implementierung, gibt die Reservierungsfunktion einen Wert ungleich 0 zurück. Im anderen Fall wird der zu verwendende Schlüssel an das Transform-Objekt gebunden (Abbildung 2, (1)).
Der nachfolgende Schritt bereitet die Transformations-Aufträge und Übergabe an das Crypto-Subsystem vor. Dazu reserviert der User ein Request-Objekt und initialisiert es mit den Adressen der Quell- und Zielspeicherbereiche (als Scatter-Gather-Listen, siehe dazu [1]) und einer Callback-Funktion (Abbildung 2, (2)). Diese spielt eine zentrale Rolle für den dritten Block, der das Durchführen des Auftrags überwacht (3).
Hierbei fällt auf, dass das Crypto-Subsystem einen Auftrag manchmal auch synchron abarbeitet. Ob der Auftrag direkt abgearbeitet (synchron) oder zur Abarbeitung in die Queue gestellt wurde, ist am Rückgabewert der Funktion zur Auftragsvergabe ersichtlich. Im ersten Fall gibt die Funktion 0 zurück, sonst »-EINPROGRESS« oder »-EBUSY« .
Im Fall der asynchronen Abarbeitung ruft das Crypto-Subsystem nach dem Bearbeiten die Callback-Funktion auf und übergibt ihr die Adresse des Auftragsobjekts. Liegen die Ergebnisse sämtlicher Aufträge vor, beginnt im vierten Schritt das Aufräumen: die Freigabe aller Auftragsobjekte, aber auch des Transform-Objekts (Abbildung 2, (4)).
Entschlüsseln
Listing 1 zeigt den Einsatz der asynchronen Schnittstelle. Das Beispiel dekodiert eine Geheimnachricht (Ciphertext), die per XOR-Verfahren mit dem Key »Mein Schluessel.« verschlüsselt wurde. Um den Code möglichst übersichtlich zu halten, beschränkt sich die normalerweise aufwändige Verwaltung der Request-Objekte (»struct ablkcipher_request« ) auf die Reservierung und Freigabe eines einzelnen Objekts. Zum Entschlüsseln einer Nachricht, die kürzer als 16 Byte ist, reicht das aus.
Listing 1
Asynchrone Verschlüsselung (ablk_user.c, Teil 1)
001 #Include <linux/crypto.h>
002 #include "internal.h"
003 #include <linux/scatterlist.h>
004
005 #define CIPHERTEXT "\x01\x0c\x07\x1b\x58\x73\x2e\x09"\
006 "\x0b\x14\x1f\x1a\x1d\x65\x6c\x2e"
007
008 static char *key = "Mein Schluessel.";
009
010 struct crypt_result {
011 struct completion completion;
012 int err;
013 };
014
015 static void request_done(struct crypto_async_request *req, int err)
016 {
017 struct crypt_result *res = req->data;
018
019 printk("request_done()\n");
020 if (err == -EINPROGRESS)
021 return;
022 res->err = err;
023 complete(&res->completion);
024 }
025
026 static int __init ablkuser_init( void )
027 {
028 int ret;
029 struct scatterlist sg[2];
030 char *encrypted, *decrypted;
031 struct crypto_ablkcipher *tfm;
032 struct ablkcipher_request *req;
033 struct crypt_result result;
034
035 printk("ablkuser_init\n");
036 encrypted = kzalloc(16, GFP_KERNEL);
037 if (!encrypted)
038 goto alloc_encrypted_failed;
039 decrypted = kzalloc(16, GFP_KERNEL);
040 if (!decrypted)
041 goto free_encrypted;
042
043 init_completion(&result.completion);
044
045 memcpy(encrypted, CIPHERTEXT, 16);
046
047 sg_init_table( sg, ARRAY_SIZE(sg) );
048 sg_set_buf( &sg[0], encrypted, 16 );
049 sg_set_buf( &sg[1], decrypted, 16 );
050
051 tfm = crypto_alloc_ablkcipher("ablk", 0, 0);
052 if (IS_ERR(tfm)) {
053 printk("crypto_alloc_ablkcipher failed %ld\n", (long)tfm);
054 goto free_decrypted;
055 }
056 ret = crypto_ablkcipher_setkey(tfm, key, strlen(key));
057 if (ret<0) {
058 printk("crypto_ablkcipher_setkey failed\n");
059 goto free_cipher;
060 }
061 /* Auftragsvergabe */
062 req = ablkcipher_request_alloc(tfm, GFP_KERNEL);
063 if (!req) {
064 printk("ablkcipher_request_alloc failed\n");
065 goto free_cipher;
066 }
067 ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
068 request_done, &result);
069 crypto_ablkcipher_clear_flags(tfm, ~0);
070 ablkcipher_request_set_crypt(req, &sg[0], &sg[1], 16, NULL);
071 ret = crypto_ablkcipher_decrypt(req);
072 if (ret == 0) {
073 request_done( &req->base, 0 );
074 } else if (ret!=-EINPROGRESS && ret!=-EBUSY) {
075 printk("crypte_ablkcipher_decrypt failed %d\n", ret);
076 goto free_all;
077 }
078 ret = wait_for_completion_interruptible(&result.completion);
079 if (ret || (ret = result.err)) {
080 printk("decryption failed %d\n", ret);
081 goto free_all;
082 }
083
084 print_hex_dump(KERN_INFO,"encr> ",DUMP_PREFIX_NONE,16,1,encrypted,16,1);
085 printk("\n");
086 print_hex_dump(KERN_INFO,"decr> ",DUMP_PREFIX_NONE,16,1,decrypted,16,1);
087 printk("\n");
088
089 free_all:
090 ablkcipher_request_free(req);
091 free_cipher:
092 crypto_free_ablkcipher(tfm);
093 free_decrypted:
094 kfree(decrypted);
095 free_encrypted:
096 kfree(encrypted);
097 alloc_encrypted_failed:
098 return -EIO; /* macht das Testen einfacher */
099 }
100
101 static void __exit ablkuser_exit( void ) {
102 return;
103 }
104
105 module_init(ablkuser_init);
106 module_exit(ablkuser_exit);
107
108 MODULE_LICENSE("GPL");
Das Ende der Auftragsbearbeitung lässt sich am besten mit Hilfe des Completion-Objekts verfolgen. Nachdem er die Aufträge dem Crypto-Subsystem zum Bearbeiten übergeben hat, ruft der User für jeden Auftrag einmal die Funktion »wait_for-completion()« auf (Abbildung 1). Die legt die Instanz schlafen, bis das korrespondierende »complete()« erfolgt, bis also die Callback-Funktion zum Ende des Auftrags aktiviert wurde.
Da sein Code neben dem Completion-Objekt typischerweise auch noch den Fehlerstatus eines Auftrags verwalten muss, deklariert der Programmierer für all diese Daten eine eigene Datenstruktur (Listing 1, Zeile 10) und instanziert sie für jeden Auftrag. Der Beispielcode reserviert zur Vereinfachung Speicher für diese Datenstruktur auf dem Stack (Zeile 33).
Callback
Etwas komplexer gerät der Code dadurch, dass ein asynchroner Auftrag auch synchron, also direkt bearbeitet werden kann. In diesem Fall kommt es nie zum Aufruf der Callback-Funktion durch das Crypto-Subsystem; daher wird die Callback-Funktion stellvertretend aktiviert (Listing 1, Zeile 73). Innerhalb der Callback-Funktion selbst werden der Fehlercode gesetzt und das Completion-Objekt aktiviert. Die Callback-Funktion muss im Übrigen so programmiert sein, dass sie sich in jedem Kontext aufrufen lässt. Innerhalb dieser Funktion ist es daher beispielsweise nicht möglich, den gerade aktiven Rechenprozess in den Schlafzustand zu versetzen.
Listing 2 zeigt, wie der Programmierer eigene Algorithmen an das Crypto-Subsystem des Kernels andockt. Im Kern beschreibt es ein Objekt vom Typ »struct crypto_alg« . Dieses Objekt gibt es für jede Transformation (Cipher, Blockcipher, Asynchronous Blockcipher, AEAD) in einer eigenen Ausprägung. Welche vorliegt, spezifiziert ein Flag im Attribut »cra_flags« . Die je nach Ausprägung unterschiedlichen Attribute realisiert der Programmierer über das Konstrukt des Union »cra_u« .
Listing 2
Asynchroner Blockcipher (ablk.c)
01 #include <linux/module.h>
02 #include <linux/interrupt.h>
03 #include <linux/scatterlist.h>
04 #include <crypto/algapi.h>
05
06 static struct tasklet_struct ablk_tasklet;
07
08 struct ablk_ctx {
09 char key[16];
10 };
11
12 static void ablk_doit( unsigned long data )
13 {
14 int error = 0, i;
15 struct ablkcipher_request *req = (struct ablkcipher_request *) data;
16 char *src, *dst;
17 struct ablk_ctx *ctx;
18
19 printk("ablk_doit(%p)\n", req );
20 src = sg_virt( req->src );
21 dst = sg_virt( req->dst );
22 ctx = crypto_tfm_ctx(req->base.tfm);
23 for( i=0; i<req->nbytes; i++ )
24 dst[i] = src[i] ^ ctx->key[i];
25 req->base.complete(&req->base, error);
26 }
27
28 static int ablk_setkey(struct crypto_ablkcipher *tfm,
29 const u8 *key, unsigned int keylen)
30 {
31 struct ablk_ctx *ctx = crypto_tfm_ctx(&tfm->base);
32
33 printk("ablk_setkey( %p, %d )\n", key, keylen );
34 if (keylen == 16) {
35 memcpy(ctx->key, key, keylen);
36 return 0;
37 }
38 return -EINVAL;
39 }
40
41 int ablk_decrypt(struct ablkcipher_request *req )
42 {
43 printk("ablk_decrypt( %p )\n", req);
44 tasklet_init( &ablk_tasklet, ablk_doit, (unsigned long)req );
45 tasklet_schedule( &ablk_tasklet );
46 return -EINPROGRESS;
47 }
48
49 int ablk_encrypt(struct ablkcipher_request *req )
50 {
51 printk("ablk_encrypt( %p )\n", req);
52 tasklet_init( &ablk_tasklet, ablk_doit, (unsigned long)req );
53 tasklet_schedule( &ablk_tasklet );
54 return -EINPROGRESS;
55 }
56
57 static struct crypto_alg ablkcipher_ablk = {
58 .cra_list = LIST_HEAD_INIT(ablkcipher_ablk.cra_list),
59 .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER|CRYPTO_ALG_ASYNC,
60 .cra_blocksize = 16,
61 .cra_ctxsize = sizeof(struct ablk_ctx),
62 .cra_priority = 100,
63 .cra_name = "ablk",
64 .cra_type = &crypto_ablkcipher_type,
65 .cra_u = { .ablkcipher = {
66 .setkey = ablk_setkey,
67 .encrypt = ablk_encrypt,
68 .decrypt = ablk_decrypt,
69 .geniv = NULL,
70 .min_keysize = 0,
71 .max_keysize = 16,
72 .ivsize = 0,
73 },
74 }
75 };
76
77 static int __init crypto_ablk_mod_init(void)
78 {
79 printk("crypto_ablk_mod_init()\n");
80 return crypto_register_alg(&ablkcipher_ablk);
81 }
82
83 static void __exit crypto_ablk_mod_exit(void)
84 {
85 printk("crypto_ablk_mod_exit()\n");
86 tasklet_kill( &ablk_tasklet );
87 crypto_unregister_alg(&ablkcipher_ablk);
88 }
89
90 module_init(crypto_ablk_mod_init);
91 module_exit(crypto_ablk_mod_exit);
92 MODULE_LICENSE("GPL");
Obligatorisch ist es, für jeden Algorithmus einen Namen und eine Priorität zu vergeben. Namen dürfen im Übrigen mehrfach vorkommen, wenn sie auch real den gleichen Algorithmus umsetzen. Das Crypto-Subsystem ermöglicht nämlich die Koexistenz verschiedener Implementierungen. Ist ein Algorithmus mehrfach umgesetzt, wählt das Subsystem die Implementierung mit der höchsten Priorität. Normalerweise besitzen Algorithmen die Priorität 100, besonders optimierte Algorithmen – etwa durch eine Assembler-Kodierung – haben die Priorität 200, Hardware-unterstützte Implementierungen 300 oder 400 (Abbildung 3).

Abbildung 3: Das Crypto-API des Linux-Kernels gibt bereitwillig Auskunft über seine Fähigkeiten: Die virtuelle Datei »/proc/crypto« listet auf, welche Transformationen und Attribute das Betriebssystem bereitstellt.
Objekte und Parameter
Sobald ein User sich beim Crypto-Subsystem unter Angabe des Algorithmen-Namens anmeldet, wird vom Algorithmen-Objekt ein Transform-Objekt abgeleitet. Dieses Objekt enthält nicht nur die Adressen der eigentlichen Transformierungsfunktionen (wie beispielsweise Verschlüsseln), sondern auch die notwendigen instanzenspezifischen Parameter. Möchte ein User beispielsweise Daten ver- oder entschlüsseln, speichert das Objekt den zugehörigen Schlüssel als so genannten Kontext ab.
Weil der Kontext nicht nur algorithmenspezifisch, sondern auch implementierungsspezifisch ist, muss der Programmierer den Speicherbedarf hierfür dem Algorithmenobjekt in dem Attribut »cra_ctxsize« noch mitgeben. Der Speicher für das Transform-Objekt wird dann so groß bemessen, dass sich der Kontext im selben Speicherbereich ablegen lässt. Typischerweise deklariert der Programmierer für den Kontext eine eigene Datenstruktur, wie dies in Listing 2 (Zeile 8) erkennbar ist.
Bitte warten
Im Fall eines asynchronen Blockcipher bekommen die Methoden des Algorithmen-Objekts zum Ver- und zum Entschlüsseln jeweils die Adresse des Request-Objekts übergeben. Hier finden sie insbesondere die Speicheradressen für die Transformation und einen eventuell vorhandenen Initialisierungsvektor. Wird der Auftrag direkt behandelt und erfolgreich abgeschlossen, geben die Methoden 0 zurück, wird der Auftrag asynchron bearbeitet, »-EINPROGRESS« .
Listing 2 empfindet die asynchrone Verarbeitung durch Einsatz eines Tasklet nach. Ein Tasklet ist eine Routine, die das System nach dem nächsten Interrupt abarbeitet und die im Interruptkontext abläuft. Eine produktive Implementierung würde die Funktionalität wohl in einer Interrupt-Service-Routine oder in einem Kernelthread umsetzen. Die Funktion, die das Ende der Bearbeitung identifiziert – im Beispiel das Tasklet –, ruft dann die im Request abgelegte Callback-Funktion (Listing 2, Zeile 25) auf und übergibt ihr den Fehlerstatus, bei erfolgreichem Abschluss des Auftrags also eine 0.
Kompliziert wird die Implementierung eines Transform-Algorithmus durch die komplexen, ineinander geschachtelten Datenstrukturen (Abbildung 4). Diese sind zudem oft unterschiedlich ausgeprägt: Neben globalen Elementen finden sich dort instanzenspezifische Methoden und Attribute. Beispielsweise gibt es das Objekt »struct crypto_tfm« unter anderem in einer Ausprägung für den synchronen (»struct crypto_blkcipher« ) und einer für den asynchronen Zugriff (»struct crypto_ablkcipher« ).
Header-Studium
Um sich hier zum gewünschten Element durchzuhangeln, hilft nur, die zugehörigen Deklarationen in der Headerdatei »<linux/crypto.h>« gründlich zu studieren [3]. Diese Headerdatei stellt auch einige Inline-Funktionen wie beispielsweise »crypto_tfm_ctx()« (Listing 2, Zeile 31) zur Verfügung, die den Zugriff auf die Elemente abstrahieren. Abbildung 5 zeigt, wie »make« aus den beiden Codebeispielen zwei Kernelmodule generiert. Nacheinander geladen – erst der Verschlüsselungsalgorithmus, danach das zugreifende Testmodul – entschlüsseln sie den Cyphertext. An dieser Stelle noch einmal der Hinweis: Der vorgestellte Verschlüsselungsalgorithmus dient ausschließlich zu Demonstrationszwecken und gewährleistet in der vorgestellten Form keinen Schutz! Die implementierte XOR-Verschlüsselung ist nur dann sicher, wenn der Schlüssel mindestens ebenso lang wie die Nachricht ist und außerdem die Schlüsselwerte zufällig verteilt sind. Im Ernstfall greift der Programmierer also lieber zu etwas Bewährtem wie AES mit einem ausreichend langen Schlüssel.
Das Crypto-Subsystem des Linux-Kernels bietet noch weit mehr Funktionalitäten als diese und die vorige Kern-Technik beschrieben haben. Dazu gehören beispielsweise der Umgang mit Initialisierungsvektoren über so genannte Templates oder das automatische Laden eines Transformations-Moduls. Offene Fragen kann der Programmierer aber leider fast ausschließlich durch Quellcodestudium klären. (mhu)
Für Kommentare
Infos
- Jürgen Quade und Eva-Katharina Kunst, “Kern-Technik”, Folge 57: Linux-Magazin 06/11, S. 86
- Sebastian Siewior, “Acceleration of encrypted communication using co-processors”: http://diploma-thesis.siewior.net/html/diplomarbeitch4.html
- Headerdatei »linux/crypto.h« : http://lxr.free-electrons.com/source/include/linux/crypto.h









