[インデックス 16954] ファイルの概要
このコミットは、Go言語の crypto/des
パッケージにおけるDES (Data Encryption Standard) 暗号化アルゴリズムの実装に対して、パフォーマンス最適化を施したものです。具体的には、初期順列 (Initial Permutation) と最終順列 (Final Permutation) の処理を高速化し、S-boxの出力に対する3番目の順列 (P-box permutation) を事前に計算することで、DESのブロック暗号処理全体の効率を大幅に向上させています。
コミット
commit 441ef7978d262745fd275e29cede0522f88955d0
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Jul 31 22:06:48 2013 +0200
crypto/des: faster permutation.
This patch introduces specialized functions for initial
and final permutations, and precomputes the output of the
third permutation on the S-box elements.
benchmark old ns/op new ns/op delta
BenchmarkEncrypt 3581 1226 -65.76%
BenchmarkDecrypt 3590 1224 -65.91%
benchmark old MB/s new MB/s speedup
BenchmarkEncrypt 2.23 6.52 2.92x
BenchmarkDecrypt 2.23 6.53 2.93x
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12072045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/441ef7978d262745fd275e29cede0522f88955d0
元コミット内容
このコミットの元の内容は、crypto/des
パッケージにおけるDES暗号化処理のパフォーマンス改善です。特に、順列処理の高速化とS-box出力の事前計算に焦点を当てています。
ベンチマーク結果は以下の通りです。
ベンチマーク | old ns/op | new ns/op | delta | old MB/s | new MB/s | speedup |
---|---|---|---|---|---|---|
BenchmarkEncrypt | 3581 | 1226 | -65.76% | 2.23 | 6.52 | 2.92x |
BenchmarkDecrypt | 3590 | 1224 | -65.91% | 2.23 | 6.53 | 2.93x |
この結果は、暗号化・復号化処理が約65%高速化され、スループットが約2.9倍に向上したことを示しています。
変更の背景
DESアルゴリズムは、その設計上、データのビット位置を入れ替える「順列 (Permutation)」操作を頻繁に実行します。特に、入力ブロックに対する初期順列 (IP)、出力ブロックに対する最終順列 (FP)、そしてFeistel関数の内部で行われるP-box順列は、ビット単位の操作であり、ナイーブな実装ではCPUサイクルを多く消費する可能性があります。
このコミットの背景には、Go言語の crypto/des
パッケージのDES実装において、これらの順列操作がボトルネックとなっていたという認識があります。汎用的な permuteBlock
関数は、任意の順列テーブルに基づいてビットを移動させるため、柔軟性は高いものの、特定の固定順列(IPやFP)に対してはオーバーヘッドが生じます。また、Feistel関数内でS-boxの出力に対して適用されるP-box順列も同様に、計算コストが高い部分でした。
パフォーマンスの向上は暗号ライブラリにとって非常に重要であり、特にDESのような広く使われる(当時は)アルゴリズムにおいては、わずかな改善でも全体のスループットに大きな影響を与えます。このコミットは、これらのパフォーマンス上の課題を解決し、DESの暗号化・復号化処理をより効率的に実行することを目的としています。
前提知識の解説
このコミットを理解するためには、DES暗号化アルゴリズムの基本的な構造と、それに含まれる主要な操作について理解しておく必要があります。
DES (Data Encryption Standard)
DESは、1970年代にIBMによって開発され、アメリカ合衆国の連邦情報処理標準 (FIPS) として採用されたブロック暗号です。64ビットのブロックサイズと56ビットの実効鍵長を持ち、Feistel構造に基づいています。DESは16ラウンドの反復処理を行い、各ラウンドで鍵スケジュールによって生成されたサブ鍵と、Feistel関数と呼ばれる非線形変換を組み合わせます。
Feistel構造
Feistel構造は、ブロック暗号の設計に広く用いられる構造です。入力ブロックを左右の半分に分割し、片方の半分をFeistel関数に通し、その出力をもう片方の半分とXORします。その後、左右の半分を入れ替えて次のラウンドに進みます。この構造の利点は、Feistel関数が可逆である必要がない点にあります。
順列 (Permutation)
順列は、データのビット位置を入れ替える操作です。DESにはいくつかの重要な順列があります。
- 初期順列 (Initial Permutation, IP): 64ビットの平文ブロックがDESアルゴリズムに入力される際に最初に行われる順列です。ビットの並びを特定のパターンで入れ替えます。
- 最終順列 (Final Permutation, FP): 16ラウンドのFeistel処理が完了した後に、出力ブロックに対して行われる順列です。IPの逆操作であり、IPによって入れ替えられたビットを元の位置に戻します。
- P-box順列 (Permutation Function): Feistel関数内で、S-boxの出力(32ビット)に対して行われる順列です。S-boxの非線形変換の後に、ビットの拡散を促進する役割があります。
これらの順列は、DESのセキュリティ特性(拡散と混乱)に不可欠な要素です。
S-box (Substitution Box)
S-boxは、DESのFeistel関数の中核をなす非線形変換要素です。48ビットの入力(拡張された右半分とラウンド鍵のXOR結果)を8つの6ビットのブロックに分割し、それぞれを対応するS-boxに入力します。各S-boxは6ビットの入力を4ビットの出力に変換します。この変換は固定のルックアップテーブルによって行われ、DESの非線形性と混乱の源となります。S-boxの出力は合計32ビットとなり、その後P-box順列にかけられます。
ビット操作とパフォーマンス
CPUにおけるビット操作は、通常、非常に高速です。しかし、多数のビットを個別に移動させるような複雑な順列操作を汎用的なループで実装すると、ループのオーバーヘッドやメモリアクセス(順列テーブルの参照)のコストが無視できなくなります。特に、固定された順列であれば、ビットシフトやビットマスク、XORなどの低レベルなCPU命令を組み合わせることで、より効率的な実装が可能です。
技術的詳細
このコミットの技術的詳細は、主に以下の3つの最適化に集約されます。
-
初期順列 (IP) と最終順列 (FP) の専用関数化:
- 以前は、
permuteBlock
という汎用関数がinitialPermutation
およびfinalPermutation
というバイト配列を引数に取って順列を実行していました。 - 新しい実装では、
permuteInitialBlock
とpermuteFinalBlock
という2つの専用関数が導入されました。これらの関数は、特定のビット操作(ビットシフト、ビットマスク、XOR)を組み合わせることで、IPとFPを直接、かつ非常に効率的に実行します。 - これらの専用関数は、DESのIPとFPが固定された順列であるという特性を最大限に活用し、ループや配列参照のオーバーヘッドを排除しています。ビット操作のシーケンスは、IPとFPの定義に基づいて手作業で最適化されています。例えば、
block ^= b1 ^ b2 ^ b1<<48 ^ b2>>48
のような操作は、複数のビットを同時に移動させるための巧妙なテクニックです。
- 以前は、
-
S-box出力に対するP-box順列の事前計算:
- DESのFeistel関数では、S-boxの出力(32ビット)が生成された後、
permutationFunction
に基づくP-box順列が適用されます。 - 以前は、このP-box順列も
permuteBlock
関数を使って実行されていました。 - 新しい実装では、
feistelBox
という8x64のuint32
型の二次元配列が導入されました。この配列は、init()
関数内でプログラムの起動時に一度だけ計算されます。 feistelBox[s][16*i+j]
は、s
番目のS-boxのi
行j
列の出力値sBoxes[s][i][j]
に対して、P-box順列を適用した結果を格納します。- これにより、Feistel関数内でS-boxの出力が得られた後、P-box順列をリアルタイムで計算する代わりに、
feistelBox
から直接結果をルックアップできるようになります。これは、計算コストの高い順列操作を事前に行うことで、実行時のオーバーヘッドを削減する典型的な最適化手法です。
- DESのFeistel関数では、S-boxの出力(32ビット)が生成された後、
-
feistel
関数の変更:feistel
関数内で、S-boxの出力sBoxResult
に対してpermuteBlock
を呼び出す代わりに、feistelBox
から直接値を取得するように変更されました。- 具体的には、
sBoxResult |= uint32(permuteBlock(uint64(sBoxes[i][row][column]) << (4 * (7 - i)), permutationFunction[:]))
のような行が、sBoxResult ^= feistelBox[i][16*row+column]
に変更されています。これは、S-boxの出力が4ビットであり、それが32ビットのsBoxResult
の適切な位置に配置され、さらにP-box順列が適用されるという複雑な処理を、事前計算されたfeistelBox
のルックアップとXOR操作に置き換えることで簡素化・高速化しています。
これらの変更により、DESの主要な計算ボトルネックであった順列操作が大幅に効率化され、ベンチマーク結果に示されるような顕著なパフォーマンス向上が実現されました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/crypto/des/block.go
:cryptBlock
関数内で、permuteBlock(b, initialPermutation[:])
がpermuteInitialBlock(b)
に、permuteBlock(preOutput, finalPermutation[:])
がpermuteFinalBlock(preOutput)
にそれぞれ置き換えられました。feistel
関数内で、S-boxの出力に対するP-box順列の適用方法が変更されました。以前はpermuteBlock
を呼び出していましたが、新しいfeistelBox
を利用するように変更されています。feistelBox
という[8][64]uint32
型のグローバル変数が追加されました。init()
関数が追加され、プログラム起動時にfeistelBox
を事前計算するロジックが実装されました。permuteInitialBlock(block uint64)
関数が新規追加されました。これは、初期順列をビット操作のみで高速に実行する専用関数です。permuteFinalBlock(block uint64)
関数が新規追加されました。これは、最終順列をビット操作のみで高速に実行する専用関数です。
-
src/pkg/crypto/des/const.go
:expansionFunction
という48バイトの配列が追加されましたが、これはコミットメッセージには直接言及されていませんが、expandBlock
関数のコメントと関連している可能性があります。ただし、このコミットの主要な最適化とは直接関係ありません。
-
src/pkg/crypto/des/des_test.go
:TestInitialPermute
関数が追加されました。これはpermuteInitialBlock
関数の正確性を検証するためのテストです。TestFinalPermute
関数が追加されました。これはpermuteFinalBlock
関数の正確性を検証するためのテストです。TestExpandBlock
関数が追加されました。これはexpandBlock
関数の正確性を検証するためのテストです。
コアとなるコードの解説
src/pkg/crypto/des/block.go
permuteInitialBlock(block uint64) uint64
および permuteFinalBlock(block uint64) uint64
これらの関数は、DESの初期順列 (IP) と最終順列 (FP) を、ビット操作(シフト、マスク、XOR)のみで実装したものです。従来の汎用的な permuteBlock
関数が順列テーブルをループで参照していたのに対し、これらの関数は固定された順列パターンを直接コードに埋め込むことで、CPUのレジスタレベルでの高速な処理を可能にしています。
例として、permuteInitialBlock
の一部を見てみましょう。
func permuteInitialBlock(block uint64) uint64 {
// block = b7 b6 b5 b4 b3 b2 b1 b0 (8 bytes)
b1 := block >> 48
b2 := block << 48
block ^= b1 ^ b2 ^ b1<<48 ^ b2>>48
// ...
}
この部分は、64ビットブロックの特定のバイト(またはビットグループ)を効率的に交換する操作を示しています。このようなビット操作のシーケンスは、IPとFPの定義に基づいて慎重に設計されており、各ビットが最終的に正しい位置に移動するように計算されています。permuteFinalBlock
は permuteInitialBlock
と逆の操作を行うため、同様のビット操作を逆順に適用します。
feistelBox [8][64]uint32
と init()
関数
feistelBox
は、S-boxの出力にP-box順列を適用した結果を事前に格納するためのルックアップテーブルです。
var feistelBox [8][64]uint32
func init() {
for s := range sBoxes { // 各S-box (0-7)
for i := 0; i < 4; i++ { // S-boxの行 (0-3)
for j := 0; j < 16; j++ { // S-boxの列 (0-15)
// S-boxの出力値を取得し、適切な位置にシフト
f := uint64(sBoxes[s][i][j]) << (4 * (7 - uint(s)))
// その値にP-box順列を適用
f = permuteBlock(uint64(f), permutationFunction[:])
// 結果をfeistelBoxに格納
feistelBox[s][16*i+j] = uint32(f)
}
}
}
}
init()
関数はGoプログラムの起動時に一度だけ実行されます。この関数内で、すべてのS-boxの可能な入力(行と列の組み合わせ)に対して、S-boxの出力値を計算し、その出力値にP-box順列 (permutationFunction
) を適用した結果を feistelBox
に格納します。16*i+j
は、S-boxの行 i
と列 j
を一意のインデックスにマッピングするための計算です。
feistel
関数の変更
feistel
関数はDESのFeistel構造の中核であり、S-boxの処理とP-box順列を適用します。
変更前:
// ...
sBoxResult |= uint32(permuteBlock(uint64(sBoxes[i][row][column]) << (4 * (7 - i)), permutationFunction[:]))
// ...
return uint32(permuteBlock(uint64(sBoxResult), permutationFunction[:])) // この行は削除
変更後:
// ...
sBoxResult ^= feistelBox[i][16*row+column]
// ...
return sBoxResult // P-box順列はfeistelBoxに事前計算済み
この変更により、feistel
関数内でS-boxの出力に対してP-box順列をリアルタイムで計算する必要がなくなり、feistelBox
から直接結果をルックアップするだけでよくなりました。これにより、Feistel関数の実行速度が大幅に向上します。
src/pkg/crypto/des/des_test.go
追加されたテスト関数 (TestInitialPermute
, TestFinalPermute
, TestExpandBlock
) は、新しく導入された高速化された順列関数が、従来の汎用順列関数と同じ正しい結果を生成することを確認するために重要です。これらのテストは、特定のビットが順列によって正しく移動するかどうかを検証することで、最適化が機能的に正しいことを保証します。
関連リンク
- DES (Data Encryption Standard):
- Feistel構造:
- S-box:
参考にした情報源リンク
- Go言語のコミットログ:
- Go言語のコードレビューシステム (Gerrit):
- https://golang.org/cl/12072045 (コミットメッセージに記載されているリンク)
- DESアルゴリズムに関する一般的な情報源:
- 暗号技術に関する書籍やオンラインリソース(例: Bruce Schneierの "Applied Cryptography" など)
- ビット操作による最適化手法に関するプログラミング記事やチュートリアル。
- Go言語の
crypto/des
パッケージのソースコード。 - Go言語のベンチマークに関するドキュメント。
- Go言語の
init()
関数: - Go言語のベンチマーク:
- ビット操作のテクニック:
- Hacker's Delight (Henry S. Warren Jr.) などの書籍や、ビット操作に関するオンラインリソース。
- 特定のビット順列を効率的に実装するためのアルゴリズムに関する情報。
これらの情報源は、DESアルゴリズムの理解、Go言語の特定の機能(init
関数、ベンチマーク)、およびビット操作による最適化手法について深く掘り下げる上で役立ちました。