最近、 Ethondボードをミニルーターとして使用し、OpenVPNを起動しました。
しかし、プロセッサの負荷は100%であることが多く、速度が15〜16 Mbit / sを超えることはありませんでした。 通信チャネルでは、100メガビットは非常に小さいため、ハードウェアでプロセスを高速化することにしました。
FPGA開発者グループのメンバーは、Altera CycloneVのオープンIPコアに基づいて、8 Gb / sの暗号化と700 Mbit / sの復号化が可能なAES-128暗号を実装したファームウェアを作成しました。 比較のために、同じCycloneVのCPU(ARM Cortex A9)上のopenssl
プログラムは、約160 Mbpsしか処理できません。
この記事は、AESハードウェア暗号化の使用に関する研究に専念しています。 Linuxの暗号化インフラストラクチャについて簡単に説明し、FPGAとカーネル間で交換されるドライバー(ソースコードはgithubで公開されています)について説明します。 FPGAでの暗号化の実装はこの記事のトピックではありません。プロセッサがアクセラレータと対話するインターフェイスのみを説明します。
チャネルスループットを低下させる主な要因は何であるかを最初に判断する方がよいことを理解しました。暗号化プロセスが本当に重要ではなく、ソフトウェアスタックの通過に時間がかかる場合はどうでしょうか。 これを行わなかったため、結果としてのパフォーマンスの向上は予想とはまったく異なることが判明しました。 ただし、まだいくつかの利点があります:Linuxでハードウェアアクセラレーションメカニズムを登録する方法、ユーザープログラムからアクセスする方法、そして最後に、ソフトウェアに実装された標準アルゴリズムではなく、 openssl
やopenvpn
などの人気のあるものにアクセラレーションアルゴリズムを選択させる方法を学ぶのは興味深いことでした。
将来、 Ethondで OpenVPNを高速化する予定です。その後、別の記事を期待してください!
はじめに
なぜ暗号化にそれほど興味があるのですか? 一見、私たちが作った機器と並んでいるわけではありません。 ただし、そのためのアプリケーションも見つけることができます。 トラフィックの大部分は暗号化されており、すべてのインターネットユーザーは、知らなくても暗号化に定期的に遭遇します。 たとえば、VPNの人気は高まっています。 調査によると、2017年の初めに、10人中3人がこのテクノロジーを使用しています。
暗号化と復号化を高速で実行できる独自の小さなルーターを作成するというアイデアを思いつきました。 アイデアは、マシンのユーザーではなくVPN経由で接続するということです。 さて、新しいことに挑戦してみるのはおもしろかったです。
暗号化アルゴリズムの高速化はどのように達成できますか? 算術演算と論理演算で表現すると、ゆっくりと判明します。 特殊なデジタル回路に実装すると、はるかに高いパフォーマンスを実現できます。 関連リンクの良いセットは、AES命令セットに関するWikipediaの記事にあります。
CPUのハードウェアにアルゴリズムを実装し、特別な命令を通じてその実行を呼び出すことができます。 有名な例は、 IntelのAES-NIです。 ただし、組み込みプロセッサには多くの場合、この機能がないか、エクスポート/インポートに制限があります。 この場合、データを処理する周辺機器を追加インストールできます。 そして、もちろん、そのような周辺機器の機能はFPGAに実装することもできます。
可能性を現実に変換することに決めたため、FPGAでの暗号化のハードウェアアクセラレーションの研究を開始しました。
理論情報
FPGAとユーザープログラムでハードウェア暗号化を組み合わせる方法を研究する過程で、私たちはしばらくの間材料の研究にとらわれていました。
Linuxカーネル
Linuxカーネルは、対称暗号、ハッシュ、ブロック暗号の動作モードなど、多くの暗号アルゴリズムを実装しています。 これはすべて、カーネル自体で使用できます。たとえば、ディスクの暗号化(dm-crypt)や作業VPN(IPsec)に使用できます。 暗号化機能への統合アクセスはKernel CryptoAPIにより提供され、ドライバーは対応するアルゴリズムのハードウェア実装を登録できます。
ユーザープログラムはCryptoAPIにもアクセスできます。 この機能を提供するインターフェイスの1つは、アドレスファミリAF_ALG
とその上のAF_ALG
ラッパーライブラリです。 AF_ALG
の主な競合AF_ALG
は、 cryptodev
カーネルcryptodev
( /dev/crypto
characterデバイスを介したCryptoAPIへのアクセス)。
cryptodev
の作者によると、彼らのソリューションはAF_ALG
よりもはるかに生産的AF_ALG
( 比較を参照)。
この図は、CryptoAPIを示しています。ここでは、AES-128の2つの実装が登録されています。適切なドライバーを介してアクセスされるソフトウェアとハードウェアです。
たとえば、 IPsec
、 af_alg.ko
およびcryptodev.ko
モジュールはCryptoAPIにリクエストを送信できます。 どちらも、ユーザープログラムにカーネルの暗号化サブシステムにアクセスする機能を提供します。1つ目はアドレスファミリを介して、2つ目はキャラクタデバイスを介してアクセスします。
原則として、これは透過的に行われます。CryptoAPIは要求を受信すると、使用する実装を選択しますが、必要に応じて、 /proc/crypto
ファイルを介して操作の詳細を確認できます。 特に、次のフィールドが含まれます。
- name-実装されたアルゴリズムの名前。
-
driver
は、アルゴリズムの個別の実装の一意の名前です。-generic
で終わるものは通常、カーネル内の標準ソフトウェア実装です。 -
priority
-優先度。 カーネルに同じアルゴリズムの実装が複数ある場合、最も優先度の高い実装が選択されます。 各ドライバー自体は、アルゴリズムの登録時に任意の優先順位を割り当てます。 最も優先度の高い実装が選択されます。
ユーザースペース
CryptoAPIは、すでに高度な抽象化であるにもかかわらず、もう1つのラッパーを持っています。ユーザー空間では、直接アクセスする人はほとんどなく、ほとんどのプログラムはライブラリ( openssl
プロジェクトのlibcrypto
やlibssl
など)を使用します。 それらは、たとえばopenssh
、 opvenvpn
、および予想どおりopenssl
によって使用されます。 これらのライブラリは、一般にプラグインと呼ばれるエンジン、暗号化アルゴリズムの新しい実装を追加するメカニズムをサポートします。
AF_ALG
開発者は、 AF_ALG
および/dev/crypto
AF_ALG
を介してカーネルで暗号化用のエンジンをすでに作成しています。 したがって、暗号化アルゴリズムがCryptoAPIに登録されている場合、多くのプログラムは暗号化アルゴリズムのハードウェア実装に自動的にアクセスします。
この図は、 /dev/crypto
AF_ALG
またはAF_ALG
介してCryptoAPIにアクセスできるlibssl
およびlibcrypto
ライブラリで提供される暗号化機能を使用するいくつかのプログラムを示しています。 たとえば、 libcrypto
cryptodev engine
とafalg engine
両方に指定されています。
実際の経験
理論を扱った後、私たちは厳しい現実に目を向けます。それは私たちの実際の経験の記述です。
研究目的
暗号化アクセラレータドライバーを開発する際の主な研究課題は、CycloneVオンボード(EthondやBlueSomなど )を搭載したマザーボードでFPGAとユーザープログラム間の帯域幅を提供できるかどうかでした 。 私たちの状況では、これは重要であることが判明しました。暗号化が非常に高速な場合、ほとんどの時間はデータの送信とシステムのさまざまな部分で起こっていることの同期に費やされます。
メタル
ドライバーは、提供するハードウェアによってほぼ完全に決定されます。 したがって、ハードウェアの説明から始めます。
この図は、CycloneV SoC内のプロセッサとFPGAの相互作用の単純化されたモデルを示しています。 より正確で詳細な説明については、 Cyclone V Device Handbook Volume 3:Hard Processor Technical Reference Manualの魅力的だが厚い本の「Introduction to the Hard Processor」の章の元の画像を参照できます。
ドライバーを搭載したLinuxは、HPS(ハードプロセッサーシステム)内部のMPU(マイクロプロセッサーユニット)で実行されています。 プロセッサは、HPS-to-FPGAインターフェイスを介してL3 SWITCHを介してFPGAレジスタにアクセスできます。 図のレジスタの呼び出しは、青い矢印で示されています。
FPGAに実装されたデバイスは、DMAを介してFPGA-to-HPSを介してSDRAMメモリにアクセスし(緑色の矢印)、プロセッサに割り込みを送信します(赤色の矢印)。 FPGAは、暗号化アクセラレータと復号化アクセラレータの2つの独立したエンティティを実装しています。 それぞれが2つの接続されたブロックで構成され、1つはアルゴリズム(暗号化/復号化)を実装し、もう1つはDMAを介してメモリとデータを交換します。
FPGAに実装された両方の「ブロック」ペアには、独自のレジスタがあります。
暗号化コアと復号化コアには、次の暗号化/復号化操作で使用されるキー(キー)、初期化ベクトル(IV、初期化ベクトル)を指定できる同一のレジスタセットがあります。
Decrypt DMAとEncrypt DMAもミラー構造になっています。 レジスタを介して、メモリセグメントのアドレスと長さを設定できます(これらのパラメータセットは記述子と呼ばれます)。ソースデータが配置されている部分と、AESを適用した結果が必要な部分があります。 割り込みを有効または無効にする機能を使用して、各記述子の処理が完了したことを通知することもできます。
暗号化API
Linux暗号化サブシステムのいくつかの概念についてもう少し話しましょう。
その主要なエンティティの1つは「変換」です。これは、データ変換の名前です:ハッシュ合計計算、圧縮、暗号化。 ドライバーは、独立して変換を「登録」できます-それを使用する機会を提供します。
変換タイプのクラスは非常に広範囲に渡りますが、暗号化に関与するタイプは3つだけです。
-
CRYPTO_ALG_TYPE_CIPHER
:単一ブロックで動作する暗号(ブロック暗号の観点から)。 -
CRYPTO_ALG_TYPE_BLKCIPHER
:ブロックサイズの倍数の長さのデータを操作する暗号。同期:暗号化機能は、暗号化が完了するまで終了しません。 -
CRYPTO_ALG_TYPE_ABLKCIPHER
:非同期であるという点で以前のものと異なります。暗号化機能は、暗号化を開始するためだけに使用され、完了を待たずに終了します。 操作の終了は、暗号化を実装するドライバー自体によって報告されます。
CryptoAPIでは、「テンプレート」-単一のデータブロックの暗号化やハッシュサムの計算などの単純な変換に基づいた、特定のブロック暗号やHMACモードなどの複雑なエンティティの実装も設定できます。
特に、テンプレートの存在により、 CRYPTO_ALG_TYPE_CIPHER
を使用してブロック暗号を実装できますが、変換自体は16バイトブロックのみで動作します。 ただし、これはCPUに大きな負荷をかけます。カーネル自体が暗号の動作モードを制御します。 CRYPTO_ALG_TYPE_ABLKCIPHER
とCRYPTO_ALG_TYPE_ABLKCIPHER
はこれを自分自身にCRYPTO_ALG_TYPE_ABLKCIPHER
し、カーネルはモードを提供するテンプレートを使用する必要はありません。
FPGAのファームウェアがCBCモードでAES-128を実装するようになったため、 CRYPTO_ALG_TYPE_CIPHER
はCRYPTO_ALG_TYPE_CIPHER
はありません。必要な動作モードを既に実装しており、プロセッサからの追加コストは不要です。
私たちのドライバーはCRYPTO_ALG_TYPE_BLKCIPHER
ような変換を提供しますが、それを実装する方が簡単だと思われました。
変換により、IV(初期化ベクトル)、キー、暗号化および復号化を設定するためのCryptoAPI関数が提供されます。 これらはすべて、 blkcipher_alg
構造体のフィールドに登録されたときに、変換ドライバーによって示されます。 この構造は次のようになります。
struct blkcipher_alg { int (*setkey)(struct crypto_tfm *tfm, const u8 *key, unsigned int keylen); int (*encrypt)(struct blkcipher_desc *desc, struct scatterlist *dst, struct scatterlist *src, unsigned int nbytes); int (*decrypt)(struct blkcipher_desc *desc, struct scatterlist *dst, struct scatterlist *src, unsigned int nbytes); const char *geniv; unsigned int min_keysize; unsigned int max_keysize; unsigned int ivsize; };
最も興味深いフィールドを考えてみましょう: setkey
、キーを設定するためのコールバック、およびencrypt
/ decrypt
ための暗号化/復号化。 blkcipher_desc
構造には、暗号化操作のIVが含まれています。 encrypt
およびdecrypt
コールバックのsrc
およびdst
コールバックencrypt
は、ソースデータを取得し、結果を配置するメモリ領域を指定します。
例としてOpensslを使用した暗号スタックオプション
これで、Linuxで暗号スタックがどのように実装されているかが少しわかりやすくなり、関連するさまざまな長所と短所を使って構築するためのさまざまなオプションをより責任を持って検討できます。
明らかに、暗号化アクセラレータのドライバをカーネル自体で使用する必要がある場合(たとえばIPsecで)、実装をCryptoAPIに登録する必要があります。 この場合、ユーザープログラムとLinuxの両方がドライバーにアクセスできます。 これは、図では「ドライバーオプション1」としてマークされています。
ただし、「ドライバーオプション2」と呼ばれる代替機能がありますcryptodev
とaf_alg
およびaf_alg
バイパスするユーザーインターフェイスインターフェイスを実装しaf_alg
。 これは間違った決定のように思えるかもしれません。標準化されたメカニズムを避けることはめったに楽しい結果につながりません。 ただし、このケースがCryptoAPIにうまく適合し、重大なパフォーマンス制限を課さないことを確認するのに十分なほど、この問題をまだ調査していません。
現在の実装では、最初のオプションを選択していますが、2番目のオプションを試す準備ができています。
SOC-AES-ACCELアーキテクチャ
カーネルが何かを暗号化または復号化することを希望する場合、 fpga_encrypt
およびfpga_decrypt
をfpga_decrypt
。 これらは同一の署名を持ち、同じことを行います。FPGA内の暗号化デバイスのレジスタにアクセスするのは1つだけで、2番目は復号化器のレジスタを参照します。
これらの関数は、 struct scatterlist
から配列への2つのポインターを受け入れます。 それぞれに一連のメモリ領域が格納されますdst
ソースデータの取得先、 dst
結果の格納先。 これらのメモリがそれぞれ1ページの境界を越えないことが保証されています。
ドライバータスクは簡単に見えます。
- 各メモリ領域のアドレスを対応するバスアドレスにマップします-デバイスがDMA経由で呼び出しを行うことができるアドレス。
- バスアドレスとメモリ領域の長さをDMAコントローラのレジスタに書き込みます。
- すべてのピースを処理した後、DMAコントローラーに割り込みを送信するよう依頼します。
- 中断を待ちます。
しかし、悪魔は詳細にあります。実装したDMAコントローラーのFPGAバージョンは、16バイトの倍数である入力データのみを受け入れます。 すぐにこの制限はなくなりますが、FPGA開発者はファームウェアを変更する時間がありませんが、16バイトの倍数ではないメモリの一部をシリアルバッファーにコピーします。
性能
ドライバーが作成されたので、書き込みに関する質問に答える必要があります。
もちろん、測定にopensslを使用することも可能ですが、独自のプログラムopenssl_benchmark.cを作成することにしました。opensslは十分な柔軟性を提供しません。 特に、opensslは入力を部分的に処理することを決定する可能性があるため、libcryptoに送信される単一のバッファーの正確なサイズを設定することはできません。 また、パフォーマンスインジケータは、I / O、メモリ割り当て、opensslの初期化などに時間がかかるため、区別が難しくなります。
私たちのプログラムは単純に動作します。指定されたサイズのバッファーを入力として割り当ててゼロにし、指定された回数処理するためにそれらをlibcryptoに送信します。 libcrypto呼び出しにかかった時間のみを測定します。 処理が何度も発生するという事実により、入力データの取得などの補助タスクによる損失を考慮せずに、AES実行のパフォーマンスを非常に正確に確立することができます。
このプログラムを使用して、異なる長さのバッファーを暗号化および暗号化解除しました(バッファー長ごとに両方のタイプの1000の操作)。 次に、スループットをMbpsで計算しました。 このすべてを2回行いました。最初のケースでは、libcryptoはソフトウェア実装(グラフ上の「ソフトウェア」)でデータを暗号化し、2番目では、cryptodevエンジン(グラフ上の「ハードウェア」)を通じてカーネルを提供しました。
特に興味深いのは、帯域幅の点でハードウェア実装がソフトウェアを追い抜く点です。 より短い間隔で、より短い間隔で測定を行います。
ソフトウェア実装のパフォーマンスは、バッファのサイズに実質的に依存しないことがわかります。 これは、プログラムとlibcryptoの間のデータ交換が非常に高速であり、バッファのサイズが大きくなると、関数を呼び出すための目立たない時間の無駄になるという事実によって説明できます。
FPGA暗号化では、状況はより複雑です。 libryptoの呼び出しはcryptodevエンジンに渡され、 /dev/crypto
cryptを開いて、いくつかのシステムコールで暗号化のセッションを構成し、暗号化バッファーへのポインターをcryptodev
モジュールにcryptodev
ます。 次に、 struct scatterlist *
形式でフォームを作成し、ユーザーバッファーが仮想アドレス空間に投影される物理ページの2つのリストをCryptoAPIに渡します。 私たちのドライバーは、登録されているすべてのAES実装の中で最も高い優先順位を持っているため、リストを取得します。 ドライバーは、すべてのメモリの長さがAESブロックの長さの倍数であることを確認すると、対応するバスアドレスを受信し、それらをFPGAレジスタに書き込みます。
FPGAとユーザープログラムの間では、リクエストは複数のレイヤーを通過する必要があり、それらの時間損失は非常に大きいことがわかります。 非常に大量の帯域幅のデータでのみ、リクエストの実装に強い影響を与える時間はなくなります。 グラフでは、この瞬間は線がほぼ水平になるという事実によって判断できます。ほとんどの時間はデータ処理に費やされます。
線の形だけでなく特定の数値も見る読者は、きっと驚かれることでしょう。なぜ復号化は250 Mbit / sで、暗号化は400 Mbit / sで安定するのでしょうか? , , FPGA, , -.
, CPU . CPU : CPU , . , .
CPU top
. , " " top
, , - .
:
16 | 256 | 1024 | 4096 | 8192 | 450000 | |
暗号化 | 92% | 89% | 81% | 63% | 53% | 29% |
93% | 91% | 86% | 74% | 64% | 43% |
- , FPGA , FPGA .
, , , CPU. , , .
おわりに
FPGA . : . . - .