[インデックス 1232] ファイルの概要
このコミットは、Go言語の標準ライブラリにMD5ハッシュアルゴリズムのブロック処理部分を実装したsrc/lib/hash/md5block.go
ファイルを追加するものです。このファイルは、MD5計算のコアとなるブロック処理ステップをGoで記述しており、将来的にアセンブリ言語やC言語による高速な実装に置き換えられることを想定した設計になっています。
コミット
Go言語の初期開発段階において、MD5ハッシュアルゴリズムの実装に必要なmd5block.go
ファイルが追加されました。このファイルは、MD5のメインとなる計算ロジック、特に512ビット(64バイト)のデータブロックを処理する部分を担っています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a874603ae5eca706a2ddaead972772c5be2cb19
元コミット内容
commit 2a874603ae5eca706a2ddaead972772c5be2cb19
Author: Rob Pike <r@golang.org>
Date: Mon Nov 24 13:47:52 2008 -0800
add missing file
R=rsc
OCL=19926
CL=19926
変更の背景
このコミットは、Go言語の標準ライブラリに暗号学的ハッシュ関数であるMD5を組み込む過程の一部として行われました。Go言語は、その設計思想の一つとして、ネットワークプログラミングやシステムプログラミングにおいて必要となる基本的な機能(暗号化、ハッシュなど)を標準ライブラリで提供することを目指していました。MD5は当時広く利用されていたハッシュ関数であり、その実装はGo言語の初期の暗号ライブラリの重要な構成要素でした。
md5block.go
が「missing file」として追加されたのは、おそらくMD5パッケージの全体的な構造が先に定義され、その中でブロック処理の具体的な実装が後から追加される計画だったためと考えられます。このファイルは、MD5アルゴリズムの計算負荷が高い部分を担うため、将来的なパフォーマンス最適化(アセンブリ言語などでの実装)を見越して独立したファイルとして設計されました。
前提知識の解説
MD5 (Message-Digest Algorithm 5)
MD5は、1991年にロナルド・リベストによって設計された暗号学的ハッシュ関数です。任意の長さの入力データ(メッセージ)から、128ビット(16バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。MD5は、データの完全性検証(データが改ざんされていないことの確認)や、パスワードのハッシュ化などに広く利用されていました。
しかし、2000年代に入ると、MD5に対する衝突攻撃(異なる入力から同じハッシュ値が生成されること)が可能であることが示され、セキュリティ上の脆弱性が明らかになりました。そのため、現在ではセキュリティが要求される用途(デジタル署名など)での使用は推奨されておらず、SHA-256やSHA-3などのより強力なハッシュ関数に置き換えられています。
暗号学的ハッシュ関数
暗号学的ハッシュ関数は、以下の特性を持つ一方向関数です。
- 一方向性 (One-way property): ハッシュ値から元の入力データを効率的に復元することは計算上困難である。
- 衝突耐性 (Collision resistance): 異なる2つの入力から同じハッシュ値が生成されること(衝突)を効率的に見つけることは計算上困難である。
- 弱衝突耐性 (Weak collision resistance): 特定の入力に対して、それとは異なるが同じハッシュ値を持つ別の入力を効率的に見つけることは困難である。
- 強衝突耐性 (Strong collision resistance): 任意の2つの異なる入力で同じハッシュ値を持つペアを効率的に見つけることは困難である。
- 原像計算困難性 (Preimage resistance): 特定のハッシュ値が与えられたとき、そのハッシュ値を生成する元の入力データを効率的に見つけることは困難である。
- 第二原像計算困難性 (Second preimage resistance): 特定の入力データが与えられたとき、それとは異なるが同じハッシュ値を持つ別の入力データを効率的に見つけることは困難である。
MD5は、かつてはこれらの特性を満たすと考えられていましたが、衝突耐性において脆弱性が発見されました。
Go言語のパッケージ構造
Go言語では、関連する機能はパッケージとしてまとめられます。標準ライブラリは、src
ディレクトリ以下に配置され、各パッケージは独自のディレクトリを持ちます。例えば、このコミットで追加されたファイルはsrc/lib/hash/md5block.go
にあり、これはhash/md5
パッケージの一部として機能することを意味します。package md5
という宣言がファイル内にあることから、このファイルがmd5
パッケージに属していることがわかります。
技術的詳細
md5block.go
は、MD5アルゴリズムの中核である「ブロック処理」を実装しています。MD5は、入力メッセージを512ビット(64バイト)のブロックに分割し、各ブロックを処理して内部状態(4つの32ビットレジスタA, B, C, D)を更新していく構造です。
MD5のブロック処理の概要
MD5のブロック処理は、以下のステップで構成されます。
- 初期化: 4つの32ビットレジスタA, B, C, Dに初期値が設定されます。
- メッセージのパディング: 入力メッセージは、その長さが512ビットの倍数になるようにパディングされます。具体的には、まず1ビットの「1」が追加され、次にメッセージ長が512ビットの倍数から64ビット不足するまで「0」が追加されます。最後に、元のメッセージの長さ(ビット単位)が64ビットの整数として追加されます。
- ブロック処理: パディングされたメッセージは、512ビットのブロックに分割されます。各ブロックは、以下の4つの「ラウンド」を通じて処理されます。
- 各ラウンドは16ステップから構成され、合計で64ステップ実行されます。
- 各ステップでは、非線形関数(F, G, H, I)、定数(Tテーブル)、左ビットローテーション、および加算が組み合わされて、レジスタA, B, C, Dの値が更新されます。
- MD5の各ラウンドで使用される非線形関数は異なります。
- ラウンド1:
F(X, Y, Z) = (X AND Y) OR (NOT X AND Z)
- ラウンド2:
G(X, Y, Z) = (X AND Z) OR (Y AND NOT Z)
- ラウンド3:
H(X, Y, Z) = X XOR Y XOR Z
- ラウンド4:
I(X, Y, Z) = Y XOR (X OR NOT Z)
- ラウンド1:
T
テーブルは、int((1<<32) * abs(sin(i+1 radians)))
という式で計算される64個の32ビット定数です。これらは、アルゴリズムに非線形性を導入し、セキュリティを強化するために使用されます。shift
配列は、各ステップで適用される左ビットローテーションの量を定義します。
md5block.go
の役割
md5block.go
ファイルは、このブロック処理の具体的な実装、特にBlock
関数を含んでいます。この関数は、MD5の内部状態を表すDigest
構造体と、処理対象のデータブロックを受け取り、内部状態を更新します。
ファイル冒頭のコメント「In its own file so that a faster assembly or C version can be substituted easily.」が示すように、このGo言語による実装は、将来的にパフォーマンスが要求される場合に、アセンブリ言語やC言語で書かれた最適化されたバージョンに置き換えやすいように、独立したファイルとして設計されています。これは、Go言語の設計哲学の一つである「パフォーマンスとシンプルさのバランス」を反映しています。
コアとなるコードの変更箇所
--- /dev/null
+++ b/src/lib/hash/md5block.go
@@ -0,0 +1,178 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// MD5 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package md5
+
+import "md5"
+
+// T[i] = int((1<<32) * abs(sin(i+1 radians))).
+var T = []uint32 {
+ // round 1
+ 0xd76aa478,
+ 0xe8c7b756,
+ 0x242070db,
+ 0xc1bdceee,
+ 0xf57c0faf,
+ 0x4787c62a,
+ 0xa8304613,
+ 0xfd469501,
+ 0x698098d8,
+ 0x8b44f7af,
+ 0xffff5bb1,
+ 0x895cd7be,
+ 0x6b901122,
+ 0xfd987193,
+ 0xa679438e,
+ 0x49b40821,
+
+ // round 2
+ 0xf61e2562,
+ 0xc040b340,
+ 0x265e5a51,
+ 0xe9b6c7aa,
+ 0xd62f105d,
+ 0x2441453,
+ 0xd8a1e681,
+ 0xe7d3fbc8,
+ 0x21e1cde6,
+ 0xc33707d6,
+ 0xf4d50d87,
+ 0x455a14ed,
+ 0xa9e3e905,
+ 0xfcefa3f8,
+ 0x676f02d9,
+ 0x8d2a4c8a,
+
+ // round3
+ 0xfffa3942,
+ 0x8771f681,
+ 0x6d9d6122,
+ 0xfde5380c,
+ 0xa4beea44,
+ 0x4bdecfa9,
+ 0xf6bb4b60,
+ 0xbebfbc70,
+ 0x289b7ec6,
+ 0xeaa127fa,
+ 0xd4ef3085,
+ 0x4881d05,
+ 0xd9d4d039,
+ 0xe6db99e5,
+ 0x1fa27cf8,
+ 0xc4ac5665,
+
+ // round 4
+ 0xf4292244,
+ 0x432aff97,
+ 0xab9423a7,
+ 0xfc93a039,
+ 0x655b59c3,
+ 0x8f0ccc92,
+ 0xffeff47d,
+ 0x85845dd1,
+ 0x6fa87e4f,
+ 0xfe2ce6e0,
+ 0xa3014314,
+ 0x4e0811a1,
+ 0xf7537e82,
+ 0xbd3af235,
+ 0x2ad7d2bb,
+ 0xeb86d391,
+}
+
+var shift1 = []uint { 7, 12, 17, 22 };
+var shift2 = []uint { 5, 9, 14, 20 };
+var shift3 = []uint { 4, 11, 16, 23 };
+var shift4 = []uint { 6, 10, 15, 21 };
+
+package func Block(dig *Digest, p *[]byte) int {
+ a := dig.s[0];
+ b := dig.s[1];
+ c := dig.s[2];
+ d := dig.s[3];
+ n := 0;
+ var X [16]uint32;
+ for len(p) >= Chunk {
+ aa, bb, cc, dd := a, b, c, d;
+
+ for i := 0; i < 16; i++ {
+ j := i*4;
+ X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24;
+ }
+
+ // If this needs to be made faster in the future,
+ // the usual trick is to unroll each of these
+ // loops by a factor of 4; that lets you replace
+ // the shift[] lookups with constants and,
+ // with suitable variable renaming in each
+ // unrolled body, delete the a, b, c, d = d, a, b, c
+ // (or you can let the optimizer do the renaming).
+
+ // Round 1.
+ for i := 0; i < 16; i++ {
+ x := i;
+ t := i;
+ s := shift1[i%4];
+ f := ((c ^ d) & b) ^ d;
+ a += f + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ // Round 2.
+ for i := 0; i < 16; i++ {
+ x := (1+5*i)%16;
+ t := 16+i;
+ s := shift2[i%4];
+ g := ((b ^ c) & d) ^ c;
+ a += g + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ // Round 3.
+ for i := 0; i < 16; i++ {
+ x := (5+3*i)%16;
+ t := 32+i;
+ s := shift3[i%4];
+ h := b ^ c ^ d;
+ a += h + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ // Round 4.
+ for i := 0; i < 16; i++ {
+ x := (7*i)%16;
+ s := shift4[i%4];
+ t := 48+i;
+ ii := c ^ (b | ^d);
+ a += ii + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ a += aa;
+ b += bb;
+ c += cc;
+ d += dd;
+
+ p = p[Chunk:len(p)];
+ n += Chunk;
+ }
+
+ dig.s[0] = a;
+ dig.s[1] = b;
+ dig.s[2] = c;
+ dig.s[3] = d;
+ return n;
+}
コアとなるコードの解説
このファイルは、MD5のブロック処理を行うBlock
関数と、MD5アルゴリズムで使用される定数テーブルT
、およびビットシフト量を定義するshift
配列を含んでいます。
T
テーブル
var T = []uint32 { ... }
で定義されているT
テーブルは、MD5アルゴリズムの各ステップで使用される32ビットの定数です。これらの値は、int((1<<32) * abs(sin(i+1 radians)))
という式に基づいて事前に計算されたもので、アルゴリズムの非線形性を高める役割があります。全部で64個の定数があり、各ラウンドで16個ずつ使用されます。
shift
配列
shift1
, shift2
, shift3
, shift4
は、MD5の各ラウンドで適用される左ビットローテーションの量を定義する配列です。MD5では、計算の途中で中間結果をビットローテーションさせることで、データの拡散を促進し、ハッシュ関数のセキュリティを強化します。各ラウンドで異なるシフト量が使用されます。
Block
関数
func Block(dig *Digest, p *[]byte) int
関数は、MD5のコアとなるブロック処理を実行します。
dig *Digest
: MD5の現在の内部状態(4つの32ビットレジスタA, B, C, D)を保持するDigest
構造体へのポインタです。p *[]byte
: 処理対象の入力データブロック(バイトスライス)へのポインタです。
関数の内部では、以下の処理が行われます。
- レジスタの初期化:
dig.s
から現在のMD5レジスタ値a
,b
,c
,d
を取得し、aa
,bb
,cc
,dd
にバックアップします。これは、ブロック処理の最後に元のレジスタ値に加算するために使用されます。 - メッセージブロックのパース: 入力バイトスライス
p
から、512ビット(64バイト)のデータブロックを読み込み、それを16個の32ビットワードX[0]
からX[15]
に変換します。Go言語のuint32(p[j]) | uint32(p[j+1])<<8 | ...
という記述は、リトルエンディアン形式でバイトを32ビットワードに結合する処理を示しています。 - 4つのラウンド処理: MD5アルゴリズムの4つの主要なラウンドがループで実装されています。各ラウンドは16ステップから構成されます。
- ラウンド1: 非線形関数
F
(((c ^ d) & b) ^ d
) を使用します。shift1
配列からシフト量を取得します。 - ラウンド2: 非線形関数
G
(((b ^ c) & d) ^ c
) を使用します。shift2
配列からシフト量を取得します。 - ラウンド3: 非線形関数
H
(b ^ c ^ d
) を使用します。shift3
配列からシフト量を取得します。 - ラウンド4: 非線形関数
I
(c ^ (b | ^d)
) を使用します。shift4
配列からシフト量を取得します。 - 各ステップでは、現在のレジスタ値、非線形関数の結果、メッセージワード
X[x]
、およびT
テーブルの定数T[t]
が加算され、指定されたビット量だけ左ローテーションされ、最後にb
が加算されます。その後、レジスタa, b, c, d
がd, a, b, c
の順にローテーションされます。
- ラウンド1: 非線形関数
- レジスタの更新: 4つのラウンドが終了した後、バックアップしておいた初期レジスタ値
aa, bb, cc, dd
を現在のレジスタ値a, b, c, d
にそれぞれ加算し、その結果をdig.s
に格納してMD5の内部状態を更新します。 - 処理バイト数の返却: 処理したバイト数(
n
)を返します。
コメントにあるように、将来的なパフォーマンス向上のためには、これらのループをアンロール(ループ展開)することで、shift
配列のルックアップを定数に置き換えたり、変数のリネームによってレジスタのローテーション処理を最適化したりすることが可能です。これは、Goコンパイラの最適化に任せることもできますが、手動で最適化されたアセンブリコードに置き換えることも考慮されています。
関連リンク
- MD5 (Wikipedia): https://ja.wikipedia.org/wiki/MD5
- RFC 1321 - The MD5 Message-Digest Algorithm: https://datatracker.ietf.org/doc/html/rfc1321 (MD5アルゴリズムの公式仕様)
- Go言語の暗号パッケージ: https://pkg.go.dev/crypto (Go言語の現在の暗号パッケージに関するドキュメント)
参考にした情報源リンク
- MD5アルゴリズムに関する一般的な知識 (Wikipedia, RFC 1321)
- Go言語の標準ライブラリの構造に関する一般的な知識
- コミットメッセージとコードの内容
- Go言語の初期のコミット履歴 (GitHub)
- Go言語の
crypto/md5
パッケージの現在の実装 (参考として、当時の実装との比較)
[インデックス 1232] ファイルの概要
このコミットは、Go言語の標準ライブラリにMD5ハッシュアルゴリズムのブロック処理部分を実装したsrc/lib/hash/md5block.go
ファイルを追加するものです。このファイルは、MD5計算のコアとなるブロック処理ステップをGoで記述しており、将来的にアセンブリ言語やC言語による高速な実装に置き換えられることを想定した設計になっています。
コミット
Go言語の初期開発段階において、MD5ハッシュアルゴリズムの実装に必要なmd5block.go
ファイルが追加されました。このファイルは、MD5のメインとなる計算ロジック、特に512ビット(64バイト)のデータブロックを処理する部分を担っています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a874603ae5eca706a2ddaead972772c5be2cb19
元コミット内容
commit 2a874603ae5eca706a2ddaead972772c5be2cb19
Author: Rob Pike <r@golang.org>
Date: Mon Nov 24 13:47:52 2008 -0800
add missing file
R=rsc
OCL=19926
CL=19926
変更の背景
このコミットは、Go言語の標準ライブラリに暗号学的ハッシュ関数であるMD5を組み込む過程の一部として行われました。Go言語は、その設計思想の一つとして、ネットワークプログラミングやシステムプログラミングにおいて必要となる基本的な機能(暗号化、ハッシュなど)を標準ライブラリで提供することを目指していました。MD5は当時広く利用されていたハッシュ関数であり、その実装はGo言語の初期の暗号ライブラリの重要な構成要素でした。
md5block.go
が「missing file」として追加されたのは、おそらくMD5パッケージの全体的な構造が先に定義され、その中でブロック処理の具体的な実装が後から追加される計画だったためと考えられます。このファイルは、MD5アルゴリズムの計算負荷が高い部分を担うため、将来的なパフォーマンス最適化(アセンブリ言語などでの実装)を見越して独立したファイルとして設計されました。
前提知識の解説
MD5 (Message-Digest Algorithm 5)
MD5は、1991年にロナルド・リベストによって設計された暗号学的ハッシュ関数です。任意の長さの入力データ(メッセージ)から、128ビット(16バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。MD5は、データの完全性検証(データが改ざんされていないことの確認)や、パスワードのハッシュ化などに広く利用されていました。
しかし、2000年代に入ると、MD5に対する衝突攻撃(異なる入力から同じハッシュ値が生成されること)が可能であることが示され、セキュリティ上の脆弱性が明らかになりました。そのため、現在ではセキュリティが要求される用途(デジタル署名など)での使用は推奨されておらず、SHA-256やSHA-3などのより強力なハッシュ関数に置き換えられています。
暗号学的ハッシュ関数
暗号学的ハッシュ関数は、以下の特性を持つ一方向関数です。
- 一方向性 (One-way property): ハッシュ値から元の入力データを効率的に復元することは計算上困難である。
- 衝突耐性 (Collision resistance): 異なる2つの入力から同じハッシュ値が生成されること(衝突)を効率的に見つけることは計算上困難である。
- 弱衝突耐性 (Weak collision resistance): 特定の入力に対して、それとは異なるが同じハッシュ値を持つ別の入力を効率的に見つけることは困難である。
- 強衝突耐性 (Strong collision resistance): 任意の2つの異なる入力で同じハッシュ値を持つペアを効率的に見つけることは困難である。
- 原像計算困難性 (Preimage resistance): 特定のハッシュ値が与えられたとき、そのハッシュ値を生成する元の入力データを効率的に見つけることは困難である。
- 第二原像計算困難性 (Second preimage resistance): 特定の入力データが与えられたとき、それとは異なるが同じハッシュ値を持つ別の入力データを効率的に見つけることは困難である。
MD5は、かつてはこれらの特性を満たすと考えられていましたが、衝突耐性において脆弱性が発見されました。
Go言語のパッケージ構造
Go言語では、関連する機能はパッケージとしてまとめられます。標準ライブラリは、src
ディレクトリ以下に配置され、各パッケージは独自のディレクトリを持ちます。例えば、このコミットで追加されたファイルはsrc/lib/hash/md5block.go
にあり、これはhash/md5
パッケージの一部として機能することを意味します。package md5
という宣言がファイル内にあることから、このファイルがmd5
パッケージに属していることがわかります。
技術的詳細
md5block.go
は、MD5アルゴリズムの中核である「ブロック処理」を実装しています。MD5は、入力メッセージを512ビット(64バイト)のブロックに分割し、各ブロックを処理して内部状態(4つの32ビットレジスタA, B, C, D)を更新していく構造です。
MD5のブロック処理の概要
MD5のブロック処理は、以下のステップで構成されます。
- 初期化: 4つの32ビットレジスタA, B, C, Dに初期値が設定されます。
- メッセージのパディング: 入力メッセージは、その長さが512ビットの倍数になるようにパディングされます。具体的には、まず1ビットの「1」が追加され、次にメッセージ長が512ビットの倍数から64ビット不足するまで「0」が追加されます。最後に、元のメッセージの長さ(ビット単位)が64ビットの整数として追加されます。
- ブロック処理: パディングされたメッセージは、512ビットのブロックに分割されます。各ブロックは、以下の4つの「ラウンド」を通じて処理されます。
- 各ラウンドは16ステップから構成され、合計で64ステップ実行されます。
- 各ステップでは、非線形関数(F, G, H, I)、定数(Tテーブル)、左ビットローテーション、および加算が組み合わされて、レジスタA, B, C, Dの値が更新されます。
- MD5の各ラウンドで使用される非線形関数は異なります。
- ラウンド1:
F(X, Y, Z) = (X AND Y) OR (NOT X AND Z)
- ラウンド2:
G(X, Y, Z) = (X AND Z) OR (Y AND NOT Z)
- ラウンド3:
H(X, Y, Z) = X XOR Y XOR Z
- ラウンド4:
I(X, Y, Z) = Y XOR (X OR NOT Z)
- ラウンド1:
T
テーブルは、int((1<<32) * abs(sin(i+1 radians)))
という式で計算される64個の32ビット定数です。これらは、アルゴリズムに非線形性を導入し、セキュリティを強化するために使用されます。shift
配列は、各ステップで適用される左ビットローテーションの量を定義します。
md5block.go
の役割
md5block.go
ファイルは、このブロック処理の具体的な実装、特にBlock
関数を含んでいます。この関数は、MD5の内部状態を表すDigest
構造体と、処理対象のデータブロックを受け取り、内部状態を更新します。
ファイル冒頭のコメント「In its own file so that a faster assembly or C version can be substituted easily.」が示すように、このGo言語による実装は、将来的にパフォーマンスが要求される場合に、アセンブリ言語やC言語で書かれた最適化されたバージョンに置き換えやすいように、独立したファイルとして設計されています。これは、Go言語の設計哲学の一つである「パフォーマンスとシンプルさのバランス」を反映しています。
コアとなるコードの変更箇所
--- /dev/null
+++ b/src/lib/hash/md5block.go
@@ -0,0 +1,178 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// MD5 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package md5
+
+import "md5"
+
+// T[i] = int((1<<32) * abs(sin(i+1 radians))).
+var T = []uint32 {
+ // round 1
+ 0xd76aa478,
+ 0xe8c7b756,
+ 0x242070db,
+ 0xc1bdceee,
+ 0xf57c0faf,
+ 0x4787c62a,
+ 0xa8304613,
+ 0xfd469501,
+ 0x698098d8,
+ 0x8b44f7af,
+ 0xffff5bb1,
+ 0x895cd7be,
+ 0x6b901122,
+ 0xfd987193,
+ 0xa679438e,
+ 0x49b40821,
+
+ // round 2
+ 0xf61e2562,
+ 0xc040b340,
+ 0x265e5a51,
+ 0xe9b6c7aa,
+ 0xd62f105d,
+ 0x2441453,
+ 0xd8a1e681,
+ 0xe7d3fbc8,
+ 0x21e1cde6,
+ 0xc33707d6,
+ 0xf4d50d87,
+ 0x455a14ed,
+ 0xa9e3e905,
+ 0xfcefa3f8,
+ 0x676f02d9,
+ 0x8d2a4c8a,
+
+ // round3
+ 0xfffa3942,
+ 0x8771f681,
+ 0x6d9d6122,
+ 0xfde5380c,
+ 0xa4beea44,
+ 0x4bdecfa9,
+ 0xf6bb4b60,
+ 0xbebfbc70,
+ 0x289b7ec6,
+ 0xeaa127fa,
+ 0xd4ef3085,
+ 0x4881d05,
+ 0xd9d4d039,
+ 0xe6db99e5,
+ 0x1fa27cf8,
+ 0xc4ac5665,
+
+ // round 4
+ 0xf4292244,
+ 0x432aff97,
+ 0xab9423a7,
+ 0xfc93a039,
+ 0x655b59c3,
+ 0x8f0ccc92,
+ 0xffeff47d,
+ 0x85845dd1,
+ 0x6fa87e4f,
+ 0xfe2ce6e0,
+ 0xa3014314,
+ 0x4e0811a1,
+ 0xf7537e82,
+ 0xbd3af235,
+ 0x2ad7d2bb,
+ 0xeb86d391,
+}
+
+var shift1 = []uint { 7, 12, 17, 22 };
+var shift2 = []uint { 5, 9, 14, 20 };
+var shift3 = []uint { 4, 11, 16, 23 };
+var shift4 = []uint { 6, 10, 15, 21 };
+
+package func Block(dig *Digest, p *[]byte) int {
+ a := dig.s[0];
+ b := dig.s[1];
+ c := dig.s[2];
+ d := dig.s[3];
+ n := 0;
+ var X [16]uint32;
+ for len(p) >= Chunk {
+ aa, bb, cc, dd := a, b, c, d;
+
+ for i := 0; i < 16; i++ {
+ j := i*4;
+ X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24;
+ }
+
+ // If this needs to be made faster in the future,
+ // the usual trick is to unroll each of these
+ // loops by a factor of 4; that lets you replace
+ // the shift[] lookups with constants and,
+ // with suitable variable renaming in each
+ // unrolled body, delete the a, b, c, d = d, a, b, c
+ // (or you can let the optimizer do the renaming).
+
+ // Round 1.
+ for i := 0; i < 16; i++ {
+ x := i;
+ t := i;
+ s := shift1[i%4];
+ f := ((c ^ d) & b) ^ d;
+ a += f + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ // Round 2.
+ for i := 0; i < 16; i++ {
+ x := (1+5*i)%16;
+ t := 16+i;
+ s := shift2[i%4];
+ g := ((b ^ c) & d) ^ c;
+ a += g + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ // Round 3.
+ for i := 0; i < 16; i++ {
+ x := (5+3*i)%16;
+ t := 32+i;
+ s := shift3[i%4];
+ h := b ^ c ^ d;
+ a += h + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ // Round 4.
+ for i := 0; i < 16; i++ {
+ x := (7*i)%16;
+ s := shift4[i%4];
+ t := 48+i;
+ ii := c ^ (b | ^d);
+ a += ii + X[x] + T[t];
+ a = a<<s | a>>(32-s);
+ a += b;
+ a, b, c, d = d, a, b, c;
+ }
+
+ a += aa;
+ b += bb;
+ c += cc;
+ d += dd;
+
+ p = p[Chunk:len(p)];
+ n += Chunk;
+ }
+
+ dig.s[0] = a;
+ dig.s[1] = b;
+ dig.s[2] = c;
+ dig.s[3] = d;
+ return n;
+}
コアとなるコードの解説
このファイルは、MD5のブロック処理を行うBlock
関数と、MD5アルゴリズムで使用される定数テーブルT
、およびビットシフト量を定義するshift
配列を含んでいます。
T
テーブル
var T = []uint32 { ... }
で定義されているT
テーブルは、MD5アルゴリズムの各ステップで使用される32ビットの定数です。これらの値は、int((1<<32) * abs(sin(i+1 radians)))
という式に基づいて事前に計算されたもので、アルゴリズムの非線形性を高める役割があります。全部で64個の定数があり、各ラウンドで16個ずつ使用されます。
shift
配列
shift1
, shift2
, shift3
, shift4
は、MD5の各ラウンドで適用される左ビットローテーションの量を定義する配列です。MD5では、計算の途中で中間結果をビットローテーションさせることで、データの拡散を促進し、ハッシュ関数のセキュリティを強化します。各ラウンドで異なるシフト量が使用されます。
Block
関数
func Block(dig *Digest, p *[]byte) int
関数は、MD5のコアとなるブロック処理を実行します。
dig *Digest
: MD5の現在の内部状態(4つの32ビットレジスタA, B, C, D)を保持するDigest
構造体へのポインタです。p *[]byte
: 処理対象の入力データブロック(バイトスライス)へのポインタです。
関数の内部では、以下の処理が行われます。
- レジスタの初期化:
dig.s
から現在のMD5レジスタ値a
,b
,c
,d
を取得し、aa
,bb
,cc
,dd
にバックアップします。これは、ブロック処理の最後に元のレジスタ値に加算するために使用されます。 - メッセージブロックのパース: 入力バイトスライス
p
から、512ビット(64バイト)のデータブロックを読み込み、それを16個の32ビットワードX[0]
からX[15]
に変換します。Go言語のuint32(p[j]) | uint32(p[j+1])<<8 | ...
という記述は、リトルエンディアン形式でバイトを32ビットワードに結合する処理を示しています。 - 4つのラウンド処理: MD5アルゴリズムの4つの主要なラウンドがループで実装されています。各ラウンドは16ステップから構成されます。
- ラウンド1: 非線形関数
F
(((c ^ d) & b) ^ d
) を使用します。shift1
配列からシフト量を取得します。 - ラウンド2: 非線形関数
G
(((b ^ c) & d) ^ c
) を使用します。shift2
配列からシフト量を取得します。 - ラウンド3: 非線形関数
H
(b ^ c ^ d
) を使用します。shift3
配列からシフト量を取得します。 - ラウンド4: 非線形関数
I
(c ^ (b | ^d)
) を使用します。shift4
配列からシフト量を取得します。 - 各ステップでは、現在のレジスタ値、非線形関数の結果、メッセージワード
X[x]
、およびT
テーブルの定数T[t]
が加算され、指定されたビット量だけ左ローテーションされ、最後にb
が加算されます。その後、レジスタa, b, c, d
がd, a, b, c
の順にローテーションされます。
- ラウンド1: 非線形関数
- レジスタの更新: 4つのラウンドが終了した後、バックアップしておいた初期レジスタ値
aa, bb, cc, dd
を現在のレジスタ値a, b, c, d
にそれぞれ加算し、その結果をdig.s
に格納してMD5の内部状態を更新します。 - 処理バイト数の返却: 処理したバイト数(
n
)を返します。
コメントにあるように、将来的なパフォーマンス向上のためには、これらのループをアンロール(ループ展開)することで、shift
配列のルックアップを定数に置き換えたり、変数のリネームによってレジスタのローテーション処理を最適化したりすることが可能です。これは、Goコンパイラの最適化に任せることもできますが、手動で最適化されたアセンブリコードに置き換えることも考慮されています。
関連リンク
- MD5 (Wikipedia): https://ja.wikipedia.org/wiki/MD5
- RFC 1321 - The MD5 Message-Digest Algorithm: https://datatracker.ietf.org/doc/html/rfc1321 (MD5アルゴリズムの公式仕様)
- Go言語の暗号パッケージ: https://pkg.go.dev/crypto (Go言語の現在の暗号パッケージに関するドキュメント)
参考にした情報源リンク
- MD5アルゴリズムに関する一般的な知識 (Wikipedia, RFC 1321)
- Go言語の標準ライブラリの構造に関する一般的な知識
- コミットメッセージとコードの内容
- Go言語の初期のコミット履歴 (GitHub)
- Go言語の
crypto/md5
パッケージの現在の実装 (参考として、当時の実装との比較)