Linux Crypto APIの拡張と使用

[0]イントロ



カーネルバージョン2.5.45から導入されたLinux暗号化API 。 それ以来、 Crypto APIはすべての一般的な(だけでなく)国際標準を超えて成長しました。









この暗号化は、 IPsecdm-cryptなど、さまざまなカーネルサブシステム( カーネルスペース内 )で主に使用されます。 ユーザー空間からのCrypto API関数の使用は、バージョンから始まるNetlinkインターフェースでも可能です。 2.6.38カーネルは、_AF ALGファミリーを導入し、 ユーザー空間コードからカーネル暗号化へのアクセスを提供します。 ただし、既存の機能では十分でない場合があるため、新しいアルゴリズムを使用してCrypto APIを拡張する必要があります。







そのため、約2年前、私はLinuxカーネル (バージョン3.13)のIPsec実装に(ベラルーシ共和国の)国家暗号化を組み込むという課題に直面しました。 当時、私はカーネル空間でプログラミングを行ったことがなく、これがカーネルモジュールを書いた初めての経験であり、 Robert Loveの著書 『 The Linux Kernel。Development Processの説明』は私を大いに助けてくれました。 Crypto API自体に対処することははるかに困難でした-言及された本では、この問題は原則としてカバーされておらず、 公式ドキュメントでは、アーキテクチャのかなり詳細な説明が提供されていますが、新しいアルゴリズムの実装に関する情報は実質的にありません。 私の検索では、Hackerマガジンで、Crypto APIの使用方法について一般的に語る素晴らしい記事に出会いましたが、その拡張についての言葉はありません。 最後に、必要な知識を得るために、ソース(カーネルコード)に目を向け、問題を解決しました。







それからしばらく経ちましたが、今になってようやく知識を更新し、このテキストを書き始めました。 Crypto APIについてです。 具体的な例として、任意のアルゴリズムをCrypto APIに埋め込む方法、およびカーネル空間ユーザー空間の両方からそれらにアクセスする方法を確認します。 この資料は、同様の問題に直面している開発者にとって非常に役立つと確信していますが、カジュアルな読者がここで自分にとって興味深いものを見つけられることを願っています。 猫へようこそ!







PS私のプログラムのコンパイルされたソースは、その断片は以下にありますが、 githubで入手できます。 この記事のソースがあります。 すべてのコードは、カーネルバージョン4.13.0-32-genericを使用したデスクトップUbuntu 16.04 LTSで作成およびテストされています。 モジュールで使用したインターフェイスのいくつかは4つのカーネルバージョンで導入されたため、3。カーネル*間違いなくコンパイルされません










[1] Crypto APIアーキテクチャ



コードを書き始める前に、 Crypto APIですべてがどのように機能するかについて少し話しましょう。 しかし、それらは私の前のドキュメントで詳細に説明されていましたが、私は順番に、材料を理解するのに十分なはずである圧搾を与えるだけです。 すぐに予約します。その後、主に対称暗号化、他のタイプの暗号変換を主に処理しますが、一般的には同様に機能しますが、多くのニュアンスがすべてを非常に複雑にします。







したがって、導入から、カーネルの暗号化サブシステムには暗号化アルゴリズムの多くの実装が含まれており、ユーザーがアクセスできるインターフェースを提供していることがすでにわかっています(以下、ユーザーと開発者はそれぞれAPIユーザーAPI開発 を意味します )。 Crypto APIに関しては、アルゴリズムは「変換」と呼ばれるため、さまざまなタイプの変換には独自のサブAPIがあり、変換記述子(ハンドル)には通常「 tfm 」という名前が付けられます。







struct crypto_cipher *tfm = crypto_alloc_cipher("aes", 0, 0);
      
      





ここで、 tfmは何らかの変換、この場合はAESアルゴリズムを使用したデータブロックの暗号化のハンドルです。 Crypto APIハンドルのライフサイクルは3段階に短縮されます









使用されるアルゴリズムに関係なく、 1つのデータブロックの基本的な暗号化機能の実装は、 crypto_cipher



タイプのハンドルを介して利用できます。対応するAPIシングルブロック暗号API )は、キーを設定し、ブロックを暗号化/復号化するためのメソッドを提供します。 独立したAPIであるため、ユーザーにとって大きな関心はありませんが、開発者の観点からは、 Crypto APIの重要な概念、いわゆる「テンプレート」を使用するため、重要です。 テンプレートは基本機能と組み合わせて使用​​され、以下を実装します。









ハンドルを作成するとき(上記の例を参照)、必要なアルゴリズムは行で指定されます-アルゴリズムの名前。この行には次のセマンティクスがあります。







 template(single block cipher/message digest)
      
      





さらに、必要に応じて、テンプレートを他のテンプレートに「ラップ」できます。







 template_a(template_b(single block cipher/message digest))
      
      





ただし、基本的なアルゴリズムなしでテンプレートを使用することはできません。 いくつか例を挙げます。









任意のモードでの対称ブロック暗号の使用は、別個のAPIを介して実行されます 。 カーネルにはこのようなAPIが3つありますが、そのうち2つは非推奨であり、 Symmetric Key Cipher APIが関連していることに注意してください。 このAPIは、任意の長さのキー/同期および暗号化/復号化のデータを設定するメソッドをユーザーに提供します。また、 非ブロッキング (非同期)です。暗号化メソッドは、通知に使用される暗号操作とコールバック関数のリクエストを呼び出し元にできるだけ早く制御を返します操作の完了に関するユーザーは、システムスケジューラに送信されます。 Symmetric Key Cipher APIは、組み込みアルゴリズムをテストするカーネルモジュールの作成に進むときに詳細に検討されます。







2つの名前に加えて、 Crypto APIには次のサブAPIがあります









開発者にとって、 Crypto APIにテンプレートが存在するということは、新しい対称暗号化アルゴリズムを組み込むことで、基本的なブロック暗号化機能のみを実装すれば十分であり、対応するテンプレートにカプセル化して必要なモードを取得できることを意味します。 しかし、これは完全に真実ではありません。 実際、カーネルに含まれるアルゴリズムのソースコード( crypto / aes_generic.ccrypto / blowfish_generic.c 、...)を見ると、基本的な機能のみが実装されていることがわかります。 ただし、同じ方法で従来のGOST 28147-89ブロックの暗号化機能を実装し、それをゲーミングモード( CTRテンプレート)で「ラップ」し、テストシーケンスで結果のアルゴリズムをチェックすると、間違った結果が得られます。 問題は、 GOSTで説明されているガンマモードが、 CTRテンプレートに実装されているガンマアルゴリズムと異なることです。 私が扱った他の国家アルゴリズムについても同じことが言えます。 そのような場合、たとえばAESアルゴリズムの最適化された実装( AES-NI - arch / x86 / crypto / aesni-intel_glue.c )で行われているように、必要なモードで本格的なブロック暗号を埋め込む必要があります。 後で、埋め込みの両方のオプションを検討します。







おそらくこれが私が建築について言いたかったことのすべてです。 より詳細な説明に興味がある人は、 ドキュメントを参照する必要があり、私のプレゼンテーションは私たちが先に進むことができるように十分でなければなりません。










[2]土壌の準備



したがって、 Crpyto APIにいくつかの新しいアルゴリズムを組み込む準備が整いました。統合できるアルゴリズムはまだないという警告があります。 この記事では、「実際の」暗号化アルゴリズムを実装することを気にしませんでした。定性的に行うことはほとんどできなかったからです(暗号作成者に任せてください)。 一方、 ヌル暗号を作成することは、特にカーネルにすでにあるため、それほど興味深いものではありません。 したがって、基本暗号化アルゴリズムの実装を妥協して記述します。



















どこで:









このアルゴリズムのブロックとキーのサイズは、128ビット(16バイト)に等しいと想定されています。







実装を開始できます。 アルゴリズムの暗号コンテキストと、コンテキストを作成/破棄する機能を定義します。







 #define XOR_CIPHER_KEY_SIZE 16 typedef struct xor_cipher_ctx xor_cipher_ctx; struct xor_cipher_ctx { uint8_t key[XOR_CIPHER_KEY_SIZE]; }; xor_cipher_ctx* xor_cipher_allocate() { xor_cipher_ctx *cipher = calloc(1, sizeof(xor_cipher_ctx)); return cipher; } void xor_cipher_free(xor_cipher_ctx *ctx) { memset(ctx->key, 0xFF, XOR_CIPHER_KEY_SIZE); free(ctx); }
      
      





キーを設定し、ブロックを暗号化/復号化するメソッドを追加します。







 #define XOR_CIPHER_BLOCK_SIZE 16 void xor_cipher_set_key(xor_cipher_ctx *ctx, uint8_t *key) { memmove(ctx->m_key, key, XOR_CIPHER_KEY_SIZE); } void xor_cipher_crypt_block(xor_cipher_ctx *ctx, uint8_t *dst, uint8_t *src) { for (int i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { dst[i] = src[i] ^ ctx->key[i]; } }
      
      





XOR操作の可逆性を考えると、 xor_cipher_crypt_block



メソッドxor_cipher_crypt_block



使用して暗号化と復号化を同時に行います。







ブロック暗号化は優れていますが、ブロック暗号化モードの一部、たとえば暗号ブロックブロック結合モード( CBCを実装するとさらに良くなります

























どこで:









引き続き動作します。暗号のブロックを結合するモードで暗号化および復号化のメソッドを実装します。







暗号化
 void xor_cipher_encrypt_cbc(xor_cipher_ctx *ctx, uint8_t *_dst, uint32_t len, uint8_t *_src, uint8_t *_iv) { uint32_t blocks = len / XOR_CIPHER_BLOCK_SIZE; uint32_t leftover = len - (blocks * XOR_CIPHER_BLOCK_SIZE); uint8_t *dst = _dst, *src = _src, *iv = _iv; for (uint32_t i = 0; i < blocks; i++) { memmove(dst, src, XOR_CIPHER_BLOCK_SIZE); for (int j = 0; j < XOR_CIPHER_BLOCK_SIZE; j++) { dst[j] ^= iv[j]; } xor_cipher_crypt_block(ctx, dst, dst); iv = dst; dst += XOR_CIPHER_BLOCK_SIZE; src += XOR_CIPHER_BLOCK_SIZE; } if (leftover) { memmove(dst, src, leftover); for (uint32_t i = 0; i < leftover; i++) { dst[i] ^= iv[i]; dst[i] ^= ctx->key[i]; } } }
      
      





解読
 void xor_cipher_decrypt_cbc(xor_cipher_ctx *ctx, uint8_t *_dst, uint32_t len, uint8_t *_src, uint8_t *_iv) { uint32_t blocks = len / XOR_CIPHER_BLOCK_SIZE; uint32_t leftover = len - (blocks * XOR_CIPHER_BLOCK_SIZE); uint8_t u[XOR_CIPHER_BLOCK_SIZE], iv[XOR_CIPHER_IV_SIZE]; uint8_t *dst = _dst, *src = _src; memmove(iv, _iv, XOR_CIPHER_IV_SIZE); for (uint32_t i = 0; i < blocks; i++) { memmove(u, src, XOR_CIPHER_BLOCK_SIZE); xor_cipher_crypt_block(ctx, dst, src); for (int j = 0; j < XOR_CIPHER_BLOCK_SIZE; j++) { dst[j] ^= iv[j]; } memmove(iv, u, XOR_CIPHER_IV_SIZE); dst += XOR_CIPHER_BLOCK_SIZE; src += XOR_CIPHER_BLOCK_SIZE; } if (leftover) { for (uint32_t i = 0; i < leftover; i++) { dst[i] = src[i] ^ ctx->key[i]; dst[i] ^= iv[i]; } } }
      
      





これはすでにもっと面白いです! これで、この実装を使用して、テストシーケンスを準備できます。テストシーケンスでは、将来、カーネル内の同じアルゴリズムの実装を確認します。 ネタバレの下に使用した値があります。







テストシーケンス
ブロック暗号化
キー 2f 1b 1a c6 d1 be cb a2 f8 45 66 0d d2 97 5c a3



テスト番号1
入力データ cc 6b 79 0c db 55 4f e5 a0 69 05 96 11 be 8c 15



インプリント e3 70 63 ca 0a eb 84 47 58 2c 63 9b c3 29 d0 b6



テスト番号2
入力データ 53 f5 f1 ef 67 a5 ba 6c 68 09 b5 7a 24 de 82 5f



インプリント 7c ee eb 29 b6 1b 71 ce 90 4c d3 77 f6 49 de fc





CBC暗号化
キー ec 8d 93 30 69 7e f8 63 0b f5 58 ec de 78 24 f2



同期パッケージ db 02 1f a8 5a 22 15 cf 49 f7 80 8b 7c 24 a1 f3



平文 6e 96 50 42 84 d2 7e e8 44 9b 75 1d e0 ac 0a 58 ee 40 24 cc 32 fc 6e c4 e2 fc d1 f5 76 6a 45 9a e4 88 ba d6 12 07 28 86



暗号文 59 19 dc da b7 8e 93 44 06 99 ad 7a 42 f0 8f 59 5b d4 6b 26 ec 0c 05 e3 ef 90 24 63 ea e2 ee 31 53 d1 42 c0 97 75 d5 06





CBC復号化
キー ec 8d 93 30 69 7e f8 63 0b f5 58 ec de 78 24 f2



同期パッケージ db 02 1f a8 5a 22 15 cf 49 f7 80 8b 7c 24 a1 f3



暗号文 db e9 1d c6 1f 13 1a 5a 34 2b 90 1e c3 b1 6f e9 52 1b 91 7f 8d 8f 6d b4 42 87 ad 85 5f 2d 89 7d



平文 ec 66 91 5e 2c 4f f7 f6 76 29 48 79 61 ed ea e8 65 7f 1f 89 fb e2 8f 8d 7d 59 65 77 42 e4 c2 66





アルゴリズムとテストプログラムの実装のソースコードはここから入手できますカーネルスペースに行く準備はほぼ整っていますが 、その前に重要なポイントに注意を向けたいと思います。 実際、多くの有名な暗号化アルゴリズムは、入力データの最後の不完全なブロックの処理を必要としません。 したがって、たとえば、入力データのサイズがブロックサイズの倍数でない場合、 GOST 28147-89アルゴリズムの実装はエラーフラグを返さなければなりませんがベルトベラルーシ語はそのような処理を提供します。 私のアルゴリズムもそれを提供します(クロックパッケージのキーと現在の値は、不完全なブロックのサイズに切り捨てられます)。 この事実は少し後で役割を果たしますが、あなたはこれを覚えておく必要があります。










[3]コアへの浸漬



アクションはカーネル空間に転送されます。 カーネルでのプログラミングは、ユーザー空間でのプログラミングとは多少異なり、デバッグの複雑さのために、かなり時間がかかるプロセスです。 ただし、この資料に取り組んでいる間、私は読者にカーネルモジュールのプログラミングの基本を理解するというタスクを設定しませんでした。これは、 Habrémore ; and more )など、私なしでこれに関する多くの情報があるためです。 したがって、読者がモジュールの作成に完全に慣れていない場合は、最初に上記で引用した資料を確認するか、自分で確認することをお勧めします(これには時間がかかりません)。 そして、さらに先へ進む準備ができている人たちと一緒に、 Crypto APIを深く研究し始めます







それで、アルゴリズムがあり、それをカーネルに埋め込みたいのですが、どこから始めればいいのでしょうか? もちろん、ドキュメント付き。 残念なことに、 アルゴリズムの開発/埋め込みに関するセクションでは、このプロセスに関する非常に一般的な知識しか得られませんが、少なくとも正しい方向に進むには役立ちます。 具体的には、カーネル内のアルゴリズムの登録および登録解除に関与する関数の存在について学習します。 理解しましょう:







 /* include/linux/crypto.h */ int crypto_register_alg(struct crypto_alg *alg); int crypto_register_algs(struct crypto_alg *algs, int count); int crypto_unregister_alg(struct crypto_alg *alg); int crypto_unregister_algs(struct crypto_alg *algs, int count);
      
      





これらの関数は、エラーの場合は負の値を返し、成功した場合は0を返します。登録されたアルゴリズムは、 crypto_alg



構造体によって記述されます(その定義( include / linux / crypto.h )を参照 )。







 /* include/linux/crypto.h */ struct crypto_alg { struct list_head cra_list; struct list_head cra_users; u32 cra_flags; unsigned int cra_blocksize; unsigned int cra_ctxsize; unsigned int cra_alignmask; int cra_priority; atomic_t cra_refcnt; char cra_name[CRYPTO_MAX_ALG_NAME]; char cra_driver_name[CRYPTO_MAX_ALG_NAME]; const struct crypto_type *cra_type; union { struct ablkcipher_alg ablkcipher; struct blkcipher_alg blkcipher; struct cipher_alg cipher; struct compress_alg compress; } cra_u; int (*cra_init)(struct crypto_tfm *tfm); void (*cra_exit)(struct crypto_tfm *tfm); void (*cra_destroy)(struct crypto_alg *alg); struct module *cra_module; } CRYPTO_MINALIGN_ATTR;
      
      





幸いなことに、この構造は非常によく文書化されており、このフィールドまたはそのフィールドの値を推測する必要はありません。









文書化されていない残りのフィールドは内部使用のためのものであり、入力しないでください。 複雑なことは何もないようです。 まず、ブロック暗号化アルゴリズムの実装を組み込みたいので、 cra_u



ユニオンのcipher_alg



構造を見てみましょう。







 /* include/linux/crypto.h */ struct cipher_alg { unsigned int cia_min_keysize; unsigned int cia_max_keysize; int (*cia_setkey)(struct crypto_tfm *tfm, const u8 *key, unsigned int keylen); void (*cia_encrypt)(struct crypto_tfm *tfm, u8 *dst, const u8 *src); void (*cia_decrypt)(struct crypto_tfm *tfm, u8 *dst, const u8 *src); };
      
      





ここではまだ簡単で、私には思えるので説明は不要です。 これで、これらすべてが実際にどのように機能するかを確認する準備ができました。 ちなみに、 cipher_alg



構造体の関数のシグネチャ 、2パートアルゴリズムのAPIの関数のシグネチャに似ていることに注意してください。







カーネルモジュールを作成します。 署名cipher_alg.cia_setkey



に従って、 cipher_alg.cia_setkey



とキーインストール関数を決定します。







 #define XOR_CIPHER_KEY_SIZE 16 struct xor_cipher_ctx { u8 key[XOR_CIPHER_KEY_SIZE]; }; static int xor_cipher_setkey(struct crypto_tfm *tfm, const u8 *key, unsigned int len) { struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); u32 *flags = &tfm->crt_flags; if (len != XOR_CIPHER_KEY_SIZE) { *flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; return -EINVAL; } memmove(ctx->key, key, XOR_CIPHER_KEY_SIZE); return 0; }
      
      





アルゴリズムのAPIでは、cryptocontextが関数に直接渡されました。ここで、関数はtfm



アルゴリズムのハンドルをtfm



ますtfm



アルゴリズムから、 crypto_tfm_ctx



関数を使用してコンテキストが抽出されます。 また、ここでは、送信されたキーの長さを確認します。 長さが正しくない場合、対応するフラグ( CRYPTO_TFM_RES_BAD_KEY_LEN



)を設定し、 EINVAL



コード(22: Invalid argument )を返します







次に、 cipher_alg.cia_encrypt



に従ってブロック暗号化関数を定義します。







 static void xor_cipher_crypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) { struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); int i; for (i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { out[i] = in[i] ^ ctx->key[i]; } }
      
      





ここに新しいものはありません。 元のAPIのように、復号化のための追加の関数は定義せず、 cipher_alg.cia_decrypt



ポインターをcipher_alg.cia_decrypt



関数で初期化します。







最後に、モジュールのロード後およびアンロード前に、 crypto_alg



構造体のインスタンスを定義し、それを入力して、アルゴリズムの登録および登録crypto_alg



関数をそれぞれ呼び出します。







 static struct crypto_alg xor_cipher = { .cra_name = "xor-cipher", .cra_driver_name = "xor-cipher-generic", .cra_priority = 100, .cra_flags = CRYPTO_ALG_TYPE_CIPHER, .cra_blocksize = XOR_CIPHER_BLOCK_SIZE, .cra_ctxsize = sizeof(struct xor_cipher_ctx), .cra_module = THIS_MODULE, .cra_u = { .cipher = { .cia_min_keysize = XOR_CIPHER_KEY_SIZE, .cia_max_keysize = XOR_CIPHER_KEY_SIZE, .cia_setkey = xor_cipher_setkey, .cia_encrypt = xor_cipher_crypt, .cia_decrypt = xor_cipher_crypt } } }; static int __init xor_cipher_init(void) { return crypto_register_alg(&xor_cipher); } static void __exit xor_cipher_exit(void) { crypto_unregister_alg(&xor_cipher); }
      
      





上記のすべてを考慮すると、ここで疑問が生じることはありません。 そのようなモジュールをコンパイルおよびダウンロードした後、ターミナルでコマンド「 cat /proc/crypto



」を実行すると、登録済みアルゴリズムのリストで独自のアルゴリズムを見つけることができます。







 name : xor-cipher driver : xor-cipher-generic module : xor_cipher priority : 100 refcnt : 1 selftest : passed internal : no type : cipher blocksize : 16 min keysize : 16 max keysize : 16
      
      





悪くないでしょ? しかし、これはほんの始まりに過ぎず、理論的には、アルゴリズムをテストするモジュールの作成に進む必要がありましたが、それでも、埋め込みと使用の問題を混同しないことにしました。 したがって、後でテストします。次のパートでは、暗号のブロックを結合するモードでアルゴリズムの実装をどのように組み込むことができるかを説明します。










[3.1]コアへの「もう少し深い」浸漬



少し先に走ります。 注意深い読者は、パターンの存在を覚えています。 したがって、最後の部分でブロック暗号化を実装すると、この実装をCBCテンプレートで簡単にラップし、暗号ブロックをブロックするモードでアルゴリズムの実装を取得できますが、シーケンスでテストすることにより、 CBCモードで暗号化を呼び出すとエラーコード22( EINVAL



) 次に、入力データの不完全なブロックとcrypto_alg.cra_blocksize



フィールドの処理について言ったことを思い出します。 実際、 CBCカーネルモードの実装には、不完全なブロックを処理する方法がわかりません。 さらに、アルゴリズムをCBCモードに変更すると、カーネルは新しいアルゴリズムを登録します。そのブロックサイズは、基本アルゴリズムのブロックサイズと同じです。 cbc(xor-cipher)アルゴリズムを使用して「暗号化」暗号化関数を呼び出した後、入力データのサイズはブロックサイズによって多重度がチェックされ、複数でない場合、関数はEINVAL



返します。 CBCモードでの暗号化のテストベクトルのサイズ(40バイト)は、ブロックサイズの倍数ではないように意図的に選択されています。 私の意見では、暗号化標準が不完全なブロックの処理を提供し、実装がそうしない場合、そのような実装は、多重度条件が満たされたときに実装が正しい結果を与えたとしても、標準への準拠のチェックに合格する可能性は低いです(この場合、これはそうです)。 したがって、ここで、アルゴリズムの暗号ブロックの結合モードを完全に実装します。 2年前にこれを行ったとき、今では時代遅れのブロッキング対称暗号化 APIを介してそれらを使用することを期待してアルゴリズムを組み込みましたが、当時は非ブロッキングAPIが望ましいと考えられていましたが、これも今では時代遅れです。 Symmetric Key Cipher API .







, , , , Crypto API , . , ( arch/x86/crypto/aesni-intel_glue.c ), , , . , / :







 /* include/crypto/internal/skcipher.h */ int crypto_register_skcipher(struct skcipher_alg *alg); int crypto_register_skciphers(struct skcipher_alg *algs, int count); void crypto_unregister_skcipher(struct skcipher_alg *alg); void crypto_unregister_skciphers(struct skcipher_alg *algs, int count);
      
      





skcipher_alg



:







 /* include/crypto/skcipher.h */ struct skcipher_alg { int (*setkey)(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen); int (*encrypt)(struct skcipher_request *req); int (*decrypt)(struct skcipher_request *req); int (*init)(struct crypto_skcipher *tfm); void (*exit)(struct crypto_skcipher *tfm); unsigned int min_keysize; unsigned int max_keysize; unsigned int ivsize; unsigned int chunksize; unsigned int walksize; struct crypto_alg base; };
      
      





, include/crypto/skcipher.h . , struct crypto_alg



, , , init



exit



, crypto_alg



. , , chunksize



walksize



:









encrypt



decrypt



. skcipher_request



, : / , . , - API , , , , .







, Crypto API . , - API / ( API ), scatterlist



. , , Synchronous Block Cipher API :







 /* include/linux/crypto.h */ int crypto_blkcipher_encrypt(struct blkcipher_desc *desc, struct scatterlist *dst, struct scatterlist *src, unsigned int nbytes);
      
      





skcipher_request



struct scatterlist *src



struct scatterlist *dst



. scatterlist



:







 /* include/linux/scatterlist.h */ struct scatterlist { /* ... */ unsigned long page_link; unsigned int offset; unsigned int length; /* ... */ };
      
      





. , sg_init_one



:







 /* include/linux/scatterlist.h */ void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen);
      
      





:







, "" buf ( page_link



), buf ( offset



).

, . , :







 struct crypto_blkcipher *tfm; struct blkcipher_dest desc; struct scatterlist sg[2]; u8 *first_segment, *second_segment; /* crypto and data allocation... */ sg_init_table(sg, 2); sg_set_buf(&sg[0], first_segment, len); sg_set_buf(&sg[1], second_segment, len); crypto_blkcipher_encrypt(&desc, &sg, &sg, 2*len);
      
      





, first_segment



second_segment



, . , Crypto API ( ) "" scatterlist



-, "" ( scattered ) . Crypto API IPsec :







One of the initial goals of this design was to readily support IPsec, so that processing can be applied to paged skb's without the need for linearization.

, scatterlist



API / ( Direct Memory Access , DMA I/O ). / , "" , , , .







. , :







 static int xor_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, unsigned int len) { return xor_cipher_setkey(crypto_skcipher_tfm(tfm), key, len); }
      
      





. CBC ( struct xor_cipher_ctx



), , , .







.







 static int cbc_encrypt(struct skcipher_request *req) { struct crypto_tfm *tfm = crypto_skcipher_tfm(crypto_skcipher_reqtfm(req)); struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); struct skcipher_walk walk; u32 nbytes; int i, blocks; u8 *src, *dst, *iv; skcipher_walk_virt(&walk, req, true); iv = walk.iv; while ((nbytes = walk.nbytes) >= XOR_CIPHER_BLOCK_SIZE) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; blocks = nbytes / XOR_CIPHER_BLOCK_SIZE; while (blocks) { for (i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { dst[i] = src[i] ^ iv[i]; } xor_cipher_crypt(tfm, dst, dst); iv = dst; src += XOR_CIPHER_BLOCK_SIZE; dst += XOR_CIPHER_BLOCK_SIZE; blocks--; } nbytes &= XOR_CIPHER_BLOCK_SIZE - 1; skcipher_walk_done(&walk, nbytes); } if ((nbytes = walk.nbytes)) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; for (i = 0; i < nbytes; i++) { dst[i] = src[i] ^ iv[i]; dst[i] ^= ctx->key[i]; } skcipher_walk_done(&walk, 0); } return 0; }
      
      





 #define XOR_CIPHER_IV_SIZE 16 static int cbc_decrypt(struct skcipher_request *req) { struct crypto_tfm *tfm = crypto_skcipher_tfm(crypto_skcipher_reqtfm(req)); struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); struct skcipher_walk walk; u8 u[XOR_CIPHER_BLOCK_SIZE], iv[XOR_CIPHER_BLOCK_SIZE]; u32 nbytes; int i, blocks; u8 *src, *dst; skcipher_walk_virt(&walk, req, true); memmove(iv, walk.iv, XOR_CIPHER_IV_SIZE); while ((nbytes = walk.nbytes) >= XOR_CIPHER_BLOCK_SIZE) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; blocks = nbytes / XOR_CIPHER_BLOCK_SIZE; while (blocks) { memmove(u, src, XOR_CIPHER_BLOCK_SIZE); xor_cipher_crypt(tfm, dst, src); for (i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { dst[i] ^= iv[i]; } memmove(iv, u, XOR_CIPHER_IV_SIZE); dst += XOR_CIPHER_BLOCK_SIZE; src += XOR_CIPHER_BLOCK_SIZE; blocks--; } nbytes &= XOR_CIPHER_BLOCK_SIZE - 1; skcipher_walk_done(&walk, nbytes); } if ((nbytes = walk.nbytes)) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; for (i = 0; i < nbytes; i++) { dst[i] = src[i] ^ ctx->key[i]; dst[i] ^= iv[i]; } skcipher_walk_done(&walk, 0); } return 0; }
      
      





, , , "" skcipher_walk



. , skcipher_walk



skcipher_walk_virt



, skcipher_walk_done



"" / ( walk.src.virt.addr



walk.dst.virt.addr



) walk.nbytes



, scatterlist



-.







skcipher_alg



, / :







 static struct skcipher_alg cbc_xor_cipher = { .base = { .cra_name = "cbc(xor-cipher)", .cra_driver_name = "cbc-xor-cipher", .cra_priority = 400, .cra_flags = CRYPTO_ALG_ASYNC, .cra_blocksize = 1, .cra_ctxsize = sizeof(struct xor_cipher_ctx), .cra_module = THIS_MODULE, }, .min_keysize = XOR_CIPHER_KEY_SIZE, .max_keysize = XOR_CIPHER_KEY_SIZE, .ivsize = XOR_CIPHER_IV_SIZE, .setkey = xor_skcipher_setkey, .encrypt = cbc_encrypt, .decrypt = cbc_decrypt, .chunksize = XOR_CIPHER_BLOCK_SIZE, }; static int __init xor_cipher_init(void) { crypto_register_alg(&xor_cipher); return crypto_register_skcipher(&cbc_xor_cipher); } static void __exit xor_cipher_exit(void) { crypto_unregister_alg(&xor_cipher); crypto_unregister_skcipher(&cbc_xor_cipher); }
      
      





cra_blocksize



. , , skcipher_walk_done



"" , .







, /proc/crypto



:







 name : cbc(xor-cipher) driver : cbc-xor-cipher module : xor_cipher priority : 400 refcnt : 1 selftest : passed internal : no type : skcipher async : yes blocksize : 1 min keysize : 16 max keysize : 16 ivsize : 16 chunksize : 16
      
      





( ) : " xor-cipher



" ( ) " cbc(xor-cipher)



" ( ). , , . , ( arch/x86/crypto ), .










[4]



, , "", , , , . , , — , : , , Crypto API . , , , .







. cipher_testvec_t



:







 typedef enum test_t { TEST_BLK_ENCRYPT = 0, TEST_BLK_DECRYPT, TEST_CBC_ENCRYPT, TEST_CBC_DECRYPT, TEST_END, } test_t; struct cipher_testvec_t { test_t test; u32 len; char *key; char *iv; char *in; char *result; };
      
      





: in



, len



, ( .test = TEST_BLK_*



) ( .test = TEST_CBC_*



), key



, , iv



. result



. TEST_END



cipher_testvec_t



.







— :







 static int test_blk(cipher_testvec_t *testvec) { struct crypto_cipher *tfm = NULL; int encrypt = (testvec->test == TEST_BLK_ENCRYPT) ? 1 : 0; u8 dst[16]; tfm = crypto_alloc_cipher("xor-cipher", 0, 0); if (IS_ERR(tfm)) { pr_err("error allocating xor-cipher: %ld\n", PTR_ERR(tfm)); return 0; } crypto_cipher_setkey(tfm, (u8*)testvec->key, 16); if (encrypt) { crypto_cipher_encrypt_one(tfm, dst, (u8*)testvec->in); } else { crypto_cipher_decrypt_one(tfm, dst, (u8*)testvec->in); } crypto_free_cipher(tfm); if (memcmp(dst, testvec->result, 16)) { pr_err("block %sciphering test failed!\n", encrypt ? "" : "de"); dumpb((u8*)testvec->key, 16, "key"); dumpb((u8*)testvec->in, 16, "in"); dumpb(dst, 16, "result"); dumpb((u8*)testvec->result, 16, "should be"); return 0; } return 1; }
      
      





, Single Block Cipher API . , crypto_alloc_cipher



, " xor-cpher ". , ( crypto_cipher_setkey



), , , ( crypto_cipher_encrypt_one



) ( crypto_cipher_decrypt_one



) . , ( crypto_free_cipher



) .







CBC .







CBC
 static int test_cbc(cipher_testvec_t *testvec) { struct scatterlist sg; struct cb_data_t cb_data; struct crypto_skcipher *tfm = NULL; struct skcipher_request *req = NULL; int encrypt = (testvec->test == TEST_CBC_ENCRYPT) ? 1 : 0; u32 err; u8 *buf = NULL; tfm = crypto_alloc_skcipher("cbc-xor-cipher", 0, 0); if (IS_ERR(tfm)) { pr_err("error allocating cbc-xor-cipher: %ld\n", PTR_ERR(tfm)); goto exit; } req = skcipher_request_alloc(tfm, GFP_KERNEL); if (!req) { pr_err("error allocating skcipher request\n"); goto exit; } buf = kmalloc(testvec->len, GFP_KERNEL); if (!buf) { pr_err("memory allocation error\n"); goto exit; } memmove(buf, (u8*)testvec->in, testvec->len); sg_init_one(&sg, buf, testvec->len); crypto_skcipher_setkey(tfm, (u8*)testvec->key, 16); skcipher_request_set_crypt(req, &sg, &sg, testvec->len, (u8*)testvec->iv); skcipher_request_set_callback(req, 0, skcipher_cb, &cb_data); init_completion(&cb_data.completion); err = (encrypt) ? crypto_skcipher_encrypt(req) : crypto_skcipher_decrypt(req); switch (err) { case 0: break; case -EINPROGRESS: case -EBUSY: wait_for_completion(&cb_data.completion); err = cb_data.err; if (!err) { break; } default: pr_err("failed with error: %d\n", err); goto exit; } if (memcmp(buf, testvec->result, testvec->len)) { pr_err("cbc %sciphering test failed!\n", encrypt ? "" : "de"); dumpb((u8*)testvec->key, 16, "key"); dumpb((u8*)testvec->iv, 16, "iv"); dumpb((u8*)testvec->in, testvec->len, "in"); dumpb(buf, testvec->len, "result"); dumpb((u8*)testvec->result, testvec->len, "should be"); goto exit; } skcipher_request_free(req); crypto_free_skcipher(tfm); kfree(buf); return 1; exit: if (buf) { kfree(buf); } if (req) { skcipher_request_free(req); } if (tfm) { crypto_free_skcipher(tfm); } return 0; }
      
      





: API ( Symmetric Key Cipher API ), .







test_cbc



:









. API , , , . , , , . skcipher_request_set_callback



. cb_data_t



, ( skcipher_cb



) :







 struct cb_data_t { struct completion completion; int err; }; static void skcipher_cb(struct crypto_async_request *req, int error) { struct cb_data_t *data = req->data; if (error == -EINPROGRESS) { return; } data->err = error; complete(&data->completion); }
      
      





completion



test_cbc



, . , , test_cbc



. , test_cbc



, :









switch



-a . . , ( , ), - :







 insmod: ERROR: could not insert module xor_cipher_testing.ko: Operation not permitted
      
      





( dmesg



) :







 [---] done 4 tests, passed: 4, failed: 0
      
      





, SK Cipher API , . — "", Asynchronous Block Cipher API , . -, -, , .










[4.1] user-space



, . , , , , , , Linux . libkcapi , Crypto API ( ). "" Netlink -, "" Netlink API . , , , , , ( 1.0.3).







, . , , , ( ), Crypto API . af_alg Strongswan , .







, . , , . , , . , test_cbc



.







CBC user-space
 static int test_cbc(cipher_testvec_t *testvec) { uint8_t dst[testvec->len]; int encrypt = (testvec->test == TEST_CBC_ENCRYPT) ? 1 : 0; struct af_alg_skcipher *tfm = NULL; tfm = af_alg_allocate_skcipher("cbc-xor-cipher"); if (!tfm) { fprintf(stderr, "error allocating \"cbc-xor-cipher\"\n"); goto err; } if (!af_alg_skcipher_setkey(tfm, (uint8_t*)testvec->key, XOR_CIPHER_KEY_SIZE)) { fprintf(stderr, "can't set \"cbc-xor-cipher\" key\n"); goto err; } if (!af_alg_skcipher_crypt(tfm, encrypt, dst, testvec->len, (uint8_t*)testvec->in, (uint8_t*)testvec->iv, XOR_CIPHER_IV_SIZE)) { goto err; } af_alg_free_skcipher(tfm); if (memcmp(dst, (uint8_t*)testvec->result, testvec->len)) { fprintf(stderr, "cbc %sciphering test failed!\n", encrypt ? "" : "de"); dumpb((uint8_t*)testvec->key, XOR_CIPHER_KEY_SIZE, "key"); dumpb((uint8_t*)testvec->iv, XOR_CIPHER_IV_SIZE, "iv"); dumpb((uint8_t*)testvec->in, testvec->len, "in"); dumpb(dst, testvec->len, "result"); dumpb((uint8_t*)testvec->result, testvec->len, "should be"); return 0; } return 1; err: if (tfm) { af_alg_free_skcipher(tfm); } return 0; }
      
      





. , , , , , . af_alg_skcipher



af_alg_*



. .







 struct af_alg_skcipher { int sockfd; }; static struct af_alg_skcipher* af_alg_allocate_skcipher(char *name) { struct af_alg_skcipher *tfm = NULL; struct sockaddr_alg sa = { .salg_family = AF_ALG, .salg_type = "skcipher", }; strncpy((char*)sa.salg_name, name, sizeof(sa.salg_name)); tfm = calloc(1, sizeof(struct af_alg_skcipher)); if (!tfm) { errno = ENOMEM; goto err; } tfm->sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0); if (tfm->sockfd == -1) { goto err; } if (bind(tfm->sockfd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { goto err; } return tfm; err: if (tfm->sockfd > 0) { close(tfm->sockfd); } if (tfm) { free(tfm); } return NULL; }
      
      





:









socket



, "" . AF_ALG Crypto API . AF_ALG , , sys/socket.h , , :







 #ifndef AF_ALG #define AF_ALG 38 #endif
      
      







sockaddr_alg



, bind



:







 /* sys/socket.h */ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
      
      





sockadr



:







 struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
      
      





, , ( sa_family



), AF_ALG :







 /* linux/if_alg.h */ struct sockaddr_alg { __u16 salg_family; __u8 salg_type[14]; __u32 salg_feat; __u32 salg_mask; __u8 salg_name[64]; };
      
      





:









, bind



-1, errno



ENOENT



.







, af_alg_allocate_skcipher



, . .







:







 static int af_alg_skcipher_setkey(struct af_alg_skcipher *tfm, uint8_t *key, uint32_t keylen) { return (setsockopt(tfm->sockfd, SOL_ALG, ALG_SET_KEY, key, keylen) == -1) ? 0 : 1; }
      
      





setsockopt



, man - . , SOL_ALG



ALG_SET_KEY



, , sys/socket.h linux/af_alg.h , , , , :







 #ifndef SOL_ALG #define SOL_ALG 279 #endif #ifndef ALG_SET_KEY #define ALG_SET_KEY 1 #endif
      
      





, .







 static int af_alg_skcipher_crypt(struct af_alg_skcipher *tfm, int encrypt, uint8_t *_dst, uint32_t _len, uint8_t *_src, uint8_t *iv, uint32_t ivlen) { int type = encrypt ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT; struct msghdr msg = {}; struct cmsghdr *cmsg; struct af_alg_iv *ivm; struct iovec iov; char buf[CMSG_SPACE(sizeof(type)) + CMSG_SPACE(offsetof(struct af_alg_iv, iv) + ivlen)]; int op = 0; ssize_t len, remainig = _len; uint8_t *src = _src, *dst = _dst; op = accept(tfm->sockfd, NULL, 0); if (op == -1) { goto end; } memset(buf, 0, sizeof(buf)); /* fill in af_alg cipher controll data */ msg.msg_control = buf; msg.msg_controllen = sizeof(buf); /* operation type: encrypt or decrypt */ cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; cmsg->cmsg_len = CMSG_LEN(sizeof(type)); memmove(CMSG_DATA(cmsg), &type, sizeof(type)); /* initialization vector */ cmsg = CMSG_NXTHDR(&msg, cmsg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; cmsg->cmsg_len = CMSG_LEN(offsetof(struct af_alg_iv, iv) + ivlen); ivm = (void*)CMSG_DATA(cmsg); ivm->ivlen = ivlen; memmove(ivm->iv, iv, ivlen); /* set data stream (scatter/gather list) */ msg.msg_iov = &iov; msg.msg_iovlen = 1; while (remainig) { iov.iov_base = src; iov.iov_len = remainig; len = sendmsg(op, &msg, 0); if (len == -1) { if (errno == EINTR) { continue; } goto end; } while (read(op, dst, len) != len) { if (errno != EINTR) { goto end; } } src += len; remainig -= len; /* no iv for subsequent data chunks */ msg.msg_controllen = 0; } /* done */ close(op); return 1; end: if (op > 0) { close(op); } return 0; }
      
      





, . , , , , , . , 3 :









( ALG_SET_OP



): ( ALG_OP_ENCRYPT



) ( ALG_OP_DECRYPT



), ( ALG_SET_IV



, af_alg_iv



). , , linux/if_alg.h , , , :







 #ifndef ALG_SET_IV #define ALG_SET_IV 2 #endif #ifndef ALG_SET_OP #define ALG_SET_OP 3 #endif #ifndef ALG_OP_DECRYPT #define ALG_OP_DECRYPT 0 #endif #ifndef ALG_OP_ENCRYPT #define ALG_OP_ENCRYPT 1 #endif
      
      







以上です。 . , :







 done 2 tests, passed: 2, failed: 0
      
      





— , .










[5] ?



, , , , , , , , . , , . .







— . , , , . , Crypto API . , - API , . , , , , .







, : , , , . , github . !












, "" , — . ! . , xor-cipher , , CBC , ( SK Cipher API , — ):







 struct crypto_skcipher *tfm = crypto_alloc_skcipher("cbc(xor-cipher)", 0, 0);
      
      





xor-cipher , ( crypto/cbc.c ).







" ! — . — , cbc(xor-cipher) ?". " ", — . , .







, :









. "" . , " cbc ", :







 .cra_name = "xor-cipher"; .cra_driver_name = "xor-cipher-generic";
      
      





:







 .cra_name = "cbc(xor-cipher)"; .cra_driver_name = "cbc(xor-cipher-generic)";
      
      





, crypto_alg



, , cra_driver_name



, . , , cra_driver_name



" cbc(xor-cipher-generic) ", :







 struct crypto_skcipher *tfm = crypto_alloc_skcipher("cbc(xor-cipher-generic)", 0, 0);
      
      










, , , . , Crypto API , , .







tcrypt ( crypto/tcrypt.c ) . , crypto/tcrypt.c , , crypto/testmgr.c . , testmgr , ( ).







, tcrypt . "" tcrypt testmgr , Makefile , — tcryptext (tcrypt External) . .







tcryptext ( tcrypt , ), - , . ( README.md , ).







testmgr.c Crypto API .












, Linux-based . ( ) .







. " crypto " crypto/Kconfig Makefile . , crypto xor_cipher.c , crypto/Kconfig ( comment "Ciphers" ) :







 ... config CRYPTO_XOR_CIPHER tristate "XOR cipher algorithm" help Custom XOR cipher algorithm. ...
      
      





crypto/Makefile :







 ... obj-$(CONFIG_CRYPTO_XOR_CIPHER) += xor_cipher.o ...
      
      





( " Cryptographic API ") . , .







tcrypt testmgr .










便利なリンク



:









Git:










All Articles