「ハッシュ関数」および「ワンタイムメモ帳」という用語の知識は、読むために必要ではありません。
使い捨てノート
ワンタイムメモ帳では、暗号文はプレーンテキストにキーを重ねることで取得されます。 重複は、たとえばビット単位のXORを使用して実行できます。プレーンテキストの各ビットは、キーの対応する(同じ順序の)ビットとXORです。

図1.各平文ビットは、同じキービットを順番に持つXORです
したがって、復号化を行うには、受信した暗号文をキーとXORで取得する必要があります。
var encoding = Encoding.GetEncoding(1251); var plainText = encoding.GetBytes("111111"); var key = encoding.GetBytes("123456"); // encrypt: key XOR plainText byte[] cipherText = new byte[plainText.Length]; new BitArray(key).Xor(new BitArray(plainText)) .CopyTo(cipherText,0); // decrypt: key XOR chipherText var decripted = new byte[cipherText.Length]; new BitArray(key).Xor(new BitArray(cipherText)) .CopyTo(decripted, 0); var decriptedStr = encoding.GetString(decripted); 1. , ,
キーがランダムで予測不能であればあるほど、それを拾うのは難しくなります。 実際の「ワンタイムノートブック」の場合、キーはランダムである必要があります。
var plainText = encoding.GetBytes("111111"); var key = new byte[6]; using (var randomGenerator = new RNGCryptoServiceProvider()) randomGenerator.GetBytes(key); 2.
キーは1回限りである必要があります
明らかに、キーの長さは暗号化されたテキストの長さ以上でなければなりません。 キーがオープンテキストよりも短い場合は、周期的に繰り返してみてください-図2:

図2.鍵は平文よりも短い。 キーを繰り返して、暗号化されたすべてのテキストをカバーします
テキスト上でキーを重ねることをギャミングと呼びます。 この用語では、「ワンタイムノートブック」キーはガンマと呼ばれます。
ガンマサイクル法の例
// XOR // data XOR, // static IEnumerable<byte> XORStrimmed(byte[] gamma, IEnumerable<byte> data) { var gammaIndex = 0; foreach (var bb in data) { // XOR yield return (byte)(bb ^ gamma[gammaIndex]); if (gammaIndex < gamma.Length - 1) gammaIndex++; else gammaIndex = 0; } } 3. XOR
テキストがキーよりも長い場合に何が起こるか見てみましょう。ガンマサイクルを繰り返します。
var plainText = encoding.GetBytes("11111111111111111111111111111111111111111111111111"); var key = new byte[6]; using (var randomGenerator = new RNGCryptoServiceProvider()) randomGenerator.GetBytes(key); // encrypt: key XOR plainText var cipherText = XORStrimmed(key, plainText); // decrypt: key XOR chipherText var decripted = XORStrimmed(key, cipherText); var decriptedStr = encoding.GetString(decripted.ToArray()); 4.
暗号文がそれほど強くないことが判明したことに気付くために暗号作成者である必要はありません(少なくとも、平文の文字が繰り返されていることがわかります)。

図3.ガンマの単純な繰り返しによる暗号文は不安定です
同じ理由で、ノートブックはワンタイムと呼ばれます。同じキーを2回使用することはできません。
例のこのようなクリアテキストは、説明のために選択されています。 優れた暗号は、そのような文字列であっても、パターンを繰り返すことなく暗号テキストを生成します。
ストリーム暗号
これまで見てきたように、ガンマを繰り返すべきではありません。 次のラウンドでガンマが終了するたびに、何らかの方法で新しいガンマを取得する必要があります。

図4.ガンマが終了するたびに、新しいガンマを作成する必要があります。 各ラウンドには独自のガンマがあります
また、ガンマを繰り返すだけでなく、できるだけランダムにする必要があることもわかっています。 ただし、各ラウンドで本当にランダムなガンマを生成すると、ワンタイムメモ帳が得られます。キーは暗号文のサイズになります。 これは必要なものではありません。
キー依存の擬似ランダムガンマ生成メカニズムが必要です。 キーを知って解読するとき、同じ色域を生成できるはずです。

図5.ストリーム暗号スキーム
ジェネレータがすべてのプレーンテキストに対してキー依存の擬似ランダムガンマを作成すると、ストリーム暗号が取得されます。
カウンターモード暗号化CTR
補足図4:キーとラウンド数に応じてガンマを生成する関数が必要です。

図6.各ラウンドのガンマは、キーとラウンドの数に応じた関数によって実行されます。
これは、カウンターモード暗号化CTRです。ガンマジェネレーターは、入力でラウンドのカウンターを受け取ります。

図7. Counter Mode Encryption CTRを使用した3ラウンドのストリーム暗号(1つのコンポーネントを除き、以下を参照)
ガンマジェネレーターとしてのハッシュ関数
ハッシュ関数は、プレーンテキストを特定の長さの擬似ランダムシーケンスに変換する関数です。 ハッシュ(ハッシュ関数の結果)を知っていると、プレーンテキストを復元することは不可能です。 プレーンテキストを1ビットでも変更すると、まったく異なる擬似ランダムハッシュになります。
すなわち ハッシュ関数をガンマジェネレータとして使用できます。
Gamma_ round = HASH(キー+ RoundIndex)
private static IEnumerable<byte> XorCounterModeEncryptDecrypt(byte[] keyBytes, IEnumerable<byte> data) { if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); if (data == null) throw new ArgumentNullException(nameof(data)); int roundIndex = 0; byte[] roundGamma = null; int gammaIndex = 0; foreach (var d in data) { if (gammaIndex == 0) { // create gamma var counterBlock = keyBytes.Concat(BitConverter.GetBytes(roundIndex)).ToArray(); using (var sha = new SHA512Managed()) roundGamma = sha.ComputeHash(counterBlock); } yield return (byte)(d ^ roundGamma[gammaIndex]); if (gammaIndex < roundGamma.Length - 1) gammaIndex++; else { gammaIndex = 0; roundIndex++; } } // foreach } 5. XOR,
使用例
var plainText = encoding.GetBytes("11111111111111111111111111111111111111111111111111"); var key = encoding.GetBytes("1123456"); // encrypt: key XOR plainText var cipherText = XorCounterModeEncryptDecrypt(key, plainText); // decrypt: key XOR chipherText var decripted = XorCounterModeEncryptDecript(key, cipherText); var decriptedStr = encoding.GetString(decripted.ToArray()); 6. / Counter Mode Encryption
ノンス、HMAC
暗号の実装はまだ完了していません。 現在の実装では、同じプレーンテキストを持つ同じキーは常に同じ暗号文を生成します。 これは受け入れられません。同じ暗号化されたコマンド「緊急にビットコインを販売する」が通信チャネルを介して送信されると想像してください-これは冗談ですが、あなたはその考えを理解しました。
そのため、もう1つの要件が追加されます。暗号化のたびに、異なる暗号文を取得する必要があります。 私たちの場合、これはガンマジェネレーターを完成させる必要があることを意味します。
この問題は、いわゆるNonceによって解決されます。
- 暗号化のたびに、ランダムなビットシーケンスが生成されます-Nonce
- ノンスはガンマの形成に関与しています
- Gamma_ round = HASH(ノンス+キー+ RoundIndex)
- すべてのラウンドで同じ
- Nonceは暗号文とともに配布/保存されます。 すなわち 暗号文の作成に使用されたノンスの知識は秘密ではありません。

図8. Nonceを追加したCounter Mode Encryption CTRを使用した3ラウンドのストリーム暗号
HMACは、いくつかの追加パラメーターへの依存関係をハッシュ関数に追加する方法です。 連結を使用して同じ結果(キャッシュをいくつかのパラメーターに依存させる)を達成しました。 すなわち 論理的に
HASH(RoundIndex +キー)
と同じ
HMAC(RoundIndex、キー)
結論は異なりますが、論理的には同じです。
private static IEnumerable<byte> XorCounterModeEncryptDecrypt(byte[] keyBytes, byte[] nonceBytes, IEnumerable<byte> data) { if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); if (data == null) throw new ArgumentNullException(nameof(data)); if (nonceBytes == null) throw new ArgumentNullException(nameof(nonceBytes)); if (nonceBytes.Length < 4) throw new ArgumentOutOfRangeException(nameof(nonceBytes)); var nonce = new BitArray(nonceBytes); int roundIndex = 0; byte[] roundGamma = null; int gammaIndex = 0; foreach (var d in data) { if (gammaIndex == 0) { // create gamma // create counter block: Nonce + Counter // another way: Nonce XOR Counter (has some constraints) var counterBlock = nonceBytes.Concat(BitConverter.GetBytes(roundIndex)).ToArray(); using (var hmacSHA = new HMACSHA512(keyBytes)) roundGamma = hmacSHA.ComputeHash(counterBlock); } yield return (byte)(d ^ roundGamma[gammaIndex]); if (gammaIndex < roundGamma.Length - 1) gammaIndex++; else { gammaIndex = 0; roundIndex++; } } // foreach } 7. XOR, . Nonce HMAC
var plainText = encoding.GetBytes("11111111111111111111111111111111111111111111111111"); var key = encoding.GetBytes("1123456"); var nonce = new byte[4]; using (var randomGenerator = new RNGCryptoServiceProvider()) randomGenerator.GetBytes(nonce); // encrypt: key XOR plainText var cipherText = nonce.Concat( XorCounterModeEncryptDecrypt(key, nonce, plainText) ); // decript: key XOR chipherText var decripted = XorCounterModeEncryptDecrypt( keyBytes: key, nonceBytes: cipherText.Take(4).ToArray(), data: cipherText.Skip(4)); var decriptedStr = encoding.GetString(decripted.ToArray()); 8.
アルゴリズム
結果のアルゴリズムを簡単に説明します。
- ランダムナンスを作る。 One Nonceはすべてのラウンドで使用されます。
- 各ラウンドで、ガンマを生成するために使用される一意のCounterBlockを計算します。
CounterBlock = Nonce + Round Number - ラウンドのガンマを生成します
ガンマ= HMAC(CounterBlock、Key) - ビット単位のXORを使用したプレーンテキストのガンマオーバーレイ
- ガンマが終了すると、次のラウンドが始まります。 ラウンド数を1増やす-新しいラウンドが新しい色域を取得する
免責事項
上記の自転車は教育目的のみです。 ストリーム暗号でハッシュ関数を使用するという考え方は新しいものではありません(以下を参照)。 私の知る限り、そのようなスキームの耐久性について明確な意見はありません。 おそらく、この質問は単に関係ないだけです-ハッシュの動作はより遅くなります。
このアイデアを擁護するために、pgpru.comでの最初の議論からコメントを提供します 。
ハッシュをPRFとして使用して、暗号化されたデータをXORitするストリームを作成することは新しいことではありません。
たとえば、SHA3ファイナリストの1つであるSkeinハッシュ関数(http://www.skein-hash.info/sites/default/files/skein1.3.pdf)の説明では、ハッシュPDF自体に同様の使用例が説明されています。
不正確、エラーが表示された場合は、理解して処理してください。 私は暗号作成者ではありません。情報セキュリティは、プロジェクト開発の適用目的にのみ使用され、情報セキュリティの過剰な要件はありません。
参照資料
GitHub StreamCipherOnHashAndCTRMode
pgpru.comでの最初のディスカッション
ブロック暗号モードの操作
ハッシュ関数で5つの動作モードを実装する
情報理論とワンタイムノート
ストリーム暗号