暗号化についてもう一度GOST 28147-89

FTMは、この暗号化アルゴリズムの実装について一般的に説明しています 。また、単純な置換モードについても説明しています 。 C#でこのGOSTの既存のライブラリと個々の実装を検討した後、主に興味と経験のために、自転車を書くことにしました。 この作業の結果を評判の良いコミュニティと共有したいと思います。



GOST 28147-89-256ビットキーを使用した対称ブロック暗号化アルゴリズムは、64ビットデータブロックで動作します。

動作モードの1つであるフィードバック付きのゲーミングは、ブロック暗号のストリーミングモードです。



アルゴリズムの説明



  1. 元のメッセージは64ビットのブロックに分割されます
  2. 各ブロックで、XOR'om「スーパーインポーズ」ガンマ、64ビット長
  3. ガンマは、単純な置換モードでキーを使用して64ビットの「状態」ブロックを暗号化することにより形成されます

    • メッセージが暗号化された瞬間に、ブロックは同期パケットまたは初期化ベクトルに等しくなります
    • 次の反復では、同期送信の代わりに、前のテキストの暗号化されたブロック


上記の一連のアクションは、暗号化と復号化の両方に有効であることに注意してください。 違いは、暗号化されたテキストブロックが次のブロックを処理するためにどこから来るかです。これは写真で最もよく見られます。







実装



このアルゴリズムは、KeePassパスワードマネージャーへのプラグインの形で実装されました。

ソースはGitHubで入手できます。



フィードバックゲーミング



以下は、ブロックごとに暗号化データ変換を実際に実行する標準のICryptoTransformインターフェイスを実装するクラスのコードスニペットです。 インスタンスを作成すると、同期メッセージの値が_state属性に記録され、次に作業の方向(暗号化または復号化)から、暗号化されたデータの次のブロックが入力されます。



public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { byte[] dataBlock = new byte[inputCount]; byte[] gamma = new byte[GostECB.BlockSize]; byte[] result = new byte[inputCount]; Array.Copy(inputBuffer, inputOffset, dataBlock, 0, inputCount); gamma = GostECB.Process(_state, _key, GostECB.SBox_CryptoPro_A, true); result = XOr(dataBlock, gamma); Array.Copy(result, 0, outputBuffer, outputOffset, inputCount); Array.Copy(_encrypt ? result : dataBlock, _state, inputCount); return inputCount; }
      
      





簡単な交換モード



GostECB.Process-単純な置換モードでの同じGOSTの実装、または「電子コードブック」。 アルゴリズムの適切な説明は、 Wikipedia記事の対応するセクション、およびHabrahabrの記事GOST 28147-89(パート2.単純置換モード)にあります。



パッケージ、ガンマ、および「状態」のサイズは64バイトであるため、単純置換モードでの暗号化は1ブロック内で考慮することができます。 ただし、いくつかあります-それらは単純に順番に暗号化されます。



GostECB.Processメソッドのソースコード
 public static byte[] Process(byte[] data, byte[] key, byte[][] sBox, bool encrypt) { Debug.Assert(data.Length == BlockSize, "BlockSize must be 64-bit long"); Debug.Assert(key.Length == KeyLength, "Key must be 256-bit long"); var a = BitConverter.ToUInt32(data, 0); var b = BitConverter.ToUInt32(data, 4); var subKeys = GetSubKeys(key); var result = new byte[8]; for (int i = 0; i < 32; i++) { var keyIndex = GetKeyIndex(i, encrypt); var subKey = subKeys[keyIndex]; var fValue = F(a, subKey, sBox); var round = b ^ fValue; if (i < 31) { b = a; a = round; } else { b = round; } } Array.Copy(BitConverter.GetBytes(a), 0, result, 0, 4); Array.Copy(BitConverter.GetBytes(b), 0, result, 4, 4); return result; }
      
      







ソースブロックの32ビット部分を操作するには、 uint型を使用すると非常に便利です。

したがって、 F()関数では、キーとブロックの一部のモジュロ加算、および11ビットの循環シフトが簡単かつ簡潔に記述されます。



 private static uint F(uint block, uint subKey, byte[][] sBox) { block = (block + subKey) % uint.MaxValue; block = Substitute(block, sBox); block = (block << 11) | (block >> 21); return block; }
      
      





Sブロック置換方法は4ビットの32ビットサブブロックで機能します。ビットシフトとさらに0x0fの乗算で分離するのは非常に便利です



 private static uint Substitute(uint value, byte[][] sBox) { byte index, sBlock; uint result = 0; for (int i = 0; i < 8; i++) { index = (byte)(value >> (4 * i) & 0x0f); sBlock = sBox[i][index]; result |= (uint)sBlock << (4 * i); } return result; }
      
      





単純置換モードでの復号化からの暗号化は、キーの使用順序が異なります。 実際、フィードバック付きのゲーミングモードに関連して、何も解読する必要はありませんが、完全を期すために、この可能性を提供できます。



 private static int GetKeyIndex(int i, bool encrypt) { return encrypt ? (i < 24) ? i % 8 : 7 - (i % 8) : (i < 8) ? i % 8 : 7 - (i % 8); }
      
      





ソース






All Articles