Habrでの奇妙な1週間の暗号化を続けて、PHPで
Anubis暗号化アルゴリズムの実装を共有することにしました。 Anubisはブロック暗号化アルゴリズムであり、実際には、米国で暗号化標準として採用されているRijndaelアルゴリズムの修正版です。 暗号は、Rijndaelの開発者の1人であるVincent Raymanと、Whirlpoolハッシュ関数の開発者の1人である有名な暗号作成者のPaulo S. L. M. Barretoです。
なぜアヌビスを選んだのですか? これは、無料で使用できる独自のアルゴリズムではありません。 Anubisは最新のセキュリティ要件を満たしています。AESの場合と同様に、ブロックサイズは128ビットで、キーの長さは128〜320ビットの範囲で変更できます。 さらに、2000年の公開以来、Anubisアルゴリズムに弱点はありませんでした。 彼は
NESSIEプロジェクトには参加しませんでしたが、それはRijndaelとの類似性のためだけです。
一番下には、アルゴリズムの公式ページへのリンクがあり、興味のある人はその完全な説明とCおよびJavaでの実装の例を見つけることができます。 私の実装では、アルゴリズムの修正版(「調整済み」のAnubis)を使用しました。これは、擬似ランダム
S-Boxではなく、著者が選択した最適な
S-Boxを使用する点が異なります。 その結果、次のインターフェイスを備えたクラスを取得しました。
class Anubis { string $key
Anubis::setKey($key, $raw_key = false)
は、その名前が示すとおり、キーを設定するために使用されます。 私の実装では、単純なテキストキー(たとえば
"VeryStrongPassword"
)を使用する可能性と、ビット文字列を指定する機能を提供しました(例:
hex2bin('575a42654a85020b4f6eaeff03aecb0e')
)。 ビット文字列をキーとして使用するには、パラメーター
$raw_key
を使用します。
キーにビット文字列を使用する場合、キーの各ビットのコンテンツとその長さ(32ビット単位で128から320ビット)を制御しますが、その正確性にも注意を払います。 特に、キーが長すぎる、短すぎる、または32ビットの倍数でない場合、
Anubis::setKey()
メソッドは例外をスローします。 単純なテキストキーを使用する場合、暗号化の前に
キー出力関数によって変換
されます (文献では、「キー生成関数」または「キーグラインド関数」などの翻訳も見つけることができます)。
キー派生関数として、通常の
HMACを使用しました。そのパラメーターは、対応するプロパティ(
Anubis::KDF_salt
、
Anubis::KDF_algo
)によって設定されます。 デフォルトでは、16個のゼロオクテットの文字列
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
ソルトとして使用され、アルゴリズムの品質は
SHA-256です。 原則として、ソルトなしで通常のMD5を使用することは可能です。なぜなら、キーを印刷するとき、不適切な長さ(128未満、320を超える、または32ビットを超えない32ビット)、限られた文字セット(通常
a-zA-Z0-9!@#$%^&*(){}[]\/*-+.,?';:~`_"|
)およびテキストの統計的依存性(パスワードがテキストフレーズの場合)、しかし、特定のタスク用にアルゴリズムをカスタマイズする余地を残すことにしました。
Anubis::key
プロパティもキーの設定に使用されますが、ビット文字列を指定する可能性はありません。 個人的には、正確にプロパティを使用することを常に好みます(外部からアクセス可能なオブジェクト変数であるフィールドではなく、プロパティ-読み取りまたは書き込みの各操作で適切なメソッドの呼び出しが必要な場合)、私の意見では、より便利で簡潔です。
テキストキーの使用についてもう少し。 前述したように、Anubis暗号のキーは、32ビット単位(4バイト)で128ビット(16バイト)から320ビット(40バイト)でなければなりません。 たとえば、
Anubis::key
を介して割り当てられたテキストキーは、指定されたサイズに適合しないことが非常に多くあります。 原則として、キー出力機能を使用すると、最小の抵抗ですべてのキーのサイズを単純に等しくすることができます-それらを最小限にするか、パラノイドモードをオンに、またはその逆に長さを320ビットに最大化します。 しかし、異なる長さのキーをインストールできる場合、それに応じて暗号化システムが異なる長さのキーを使用すると予想できるため、このアプローチは間違っていると考えました。
その結果、私の実装では、キーはキー出力関数によって処理された後、常に可能な限り最短の長さを持ちますが、元のキーの長さより短くなりません。 つまり たとえば、キー
"010101010101"
(12バイト)を入力すると、暗号化中にキーが16バイト(128ビット)に拡張されます。
"10101010101010101"
(17バイト)と入力すると、暗号化で使用されるキーはすでに20バイト(160ビット)の長さになります。
キーはすでに十分に述べられているので、他のメソッドの説明に戻りましょう。
Anubis::encrypt($data)
および
Anubis::decrypt($data)
メソッドの名前は、それ自身を表しています。
decrypt(encrypt($data)) == $data
ことは明らかです。
ここでは、メッセージの暗号化の実行方法を説明する必要があります。 アヌビス自体はブロック暗号であり、名前が示すようにブロックを暗号化できます。 つまり 元のメッセージはブロックに分割されます。この場合、128ビット長で、各ブロックはキーで個別に暗号化されます。 1ブロックよりも長い暗号化メッセージ(およびこれは16バイトしかないことを思い出します)は暗号化できないことは明らかだと思います-統計的な依存関係がいくつか保持されるため、暗号化されたテキストを分析できるようになります。 1つのキーで大量に暗号化されます。 ここでの問題は、同じキーを使用するときに同じテキストブロックが常に暗号化されたメッセージの同じブロックで表されることです。
説明した問題を克服するために、
暗号文ブロック結合モードを使用
しました。この
モードでは、後続の各ブロックが、前のブロックを暗号化して取得した情報を使用して暗号化されます。 最初のブロックを暗号化するとき、前のブロックを暗号化することからの情報の代わりに、特別な初期化ベクトルが使用されます。 したがって、1回限りの初期化ベクトルが使用される場合、ソーステキストの同じブロックは毎回異なる暗号テキストを提供し、潜在的なクラッカーから分析のための統計情報を奪います。
私の実装では、マイクロ秒までの正確なタイムスタンプと擬似乱数を使用して初期化ベクトルを生成します(同じキーに対して2つの初期化ベクトルを同時に作成する場合)。
初期化ベクトルを生成して適用した後、暗号化されたメッセージと一緒に何らかの方法で送信する必要があります。そうしないと、テキストを正しく復号化できません。 初期化ベクトルは秘密にしておく必要がないため、実装では暗号化されたメッセージの最初のブロックに挿入します。 暗号化を解除する場合、最初に暗号化テキストから初期化ベクトルが読み取られ、次に直接暗号化されたデータが読み取られます。
ブロック暗号の特性から生じるもう1つの機能は、メッセージサイズが常にブロックサイズの倍数であることです。 そうでない場合は、メッセージの残りをブロックのサイズに追加する必要があります。 ここでの問題は、復号化するときに、拡張バイトを何らかの方法で削除する必要があることです。 最も簡単な解決策は、ヌル文字を含む残りを追加することであり、復号化すると、それらは単に削除されます。 しかし、最初に、元のメッセージにヌル文字を含めることはできず、メッセージ自体の一部を削除しないと誰が言ったのでしょうか? 次に、潜在的なクラッカーに、メッセージの最後に多くの場合1〜15個のゼロ文字があるという追加情報を提供するのはなぜですか。
少し違った方法で行ったからです。 メッセージの残りの部分は擬似ランダムバイトで補完され、最後のバイトには追加された文字数に関する情報が含まれます。 突然元のメッセージの長さがすでにブロックサイズの倍数であり、残余がない場合、1つのブロックを完全に擬似ランダム文字で追加する必要があります-結局、アルゴリズムはバイトが追加されたかどうかを事前に知ることができず、最後に示されている数の文字を削除します最後のバイト。 もちろん、特定の依存関係もここに残ります-元のメッセージの最後のバイトには、常に
chr(1)
から
chr(16)
あり、合計16のオプションがあります。 ただし、これは、特に潜在的なクラッカーに知られている可能性のあるソーステキストの特性を破壊するためにソースメッセージが前処理される場合、一連の明確な文字よりもわずかに優れています(たとえば、テキストメッセージは、特定の「テール」を提供しないアルゴリズムによって圧縮できます) 。
メソッド
Anubis::encryptFile($src, $dest)
および
Anubis::decryptFile($src, $dest)
、元のメッセージを除き、
Anubis::encrypt($data)
および
Anubis::decrypt($data)
似ていますここでは、
$src
という名前のファイルから読み取られ、結果は
$dest
ファイルに書き込まれます。 データはチャンク単位で読み書きされます(ここでは、データを暗号化するブロックで許可されないように「ブロック」という単語を避けています)。サイズは
Anubis::file_blocksize
で設定でき、ファイルシステムがreadと合理的な量のメモリを消費しながら、レコードは可能な限り迅速に実行されました(チャンクはRAM全体に読み込まれます)。 デフォルトでは、チャンクサイズは0.5メガバイトに設定されています。
さて、実際には、ここに全体の説明があります。 使用の小さな例を示すためだけに残ります。
<?php require_once 'anubis.class.php'; $src = 'secret_message.txt'; $encrypted = 'encrypted.file'; $decrypted = 'decrypted_message.txt'; $cypher = new Anubis(); $cypher->key = 'strong password'; $cypher->encryptFile($src, $encrypted); $cypher->decryptFile($encrypted, $decrypted); $src_hash = md5_file($src); $decrypted_hash = md5_file($decrypted); echo "Src: $src_hash\n"; echo "Dest: $decrypted_hash\n";
最後に、暗号化はそれほど速くなかったと言います。 最大キー長で、システムは約100 KB /秒(約6400ブロック/秒)、最小キー長は約150-160 KB /秒(約10,000ブロック/秒)で暗号化できます。 したがって、アルゴリズムの実装を最適化するための提案がある場合(プロファイラーは、プライベートメソッド
Anubis::crypt()
-暗号化/復号化機能によって直接使用されます)、私はそれを何度も聞きます。
GitHubプロジェクトリポジトリ(ソースコード、例、wiki):
https :
//github.com/kolonist/php-anubisアヌビス公式アルゴリズムページ:
http :
//www.larc.usp.br/~pbarreto/AnubisPage.html