Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15159] ファイルの概要

このコミットは、Go言語の標準ライブラリ crypto/md5 パッケージにおける、MD5ハッシュ計算のブロック処理に関するバグ修正です。具体的には、ビッグエンディアンプロセッサ上でのMD5計算が正しく行われない問題を解決しています。

コミット

commit 2b44c33b4a9ca2fa25c88e85803daa57f1080a03
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Feb 7 13:31:53 2013 -0800

    crypto/md5: fix for big-endian processors
    
    R=golang-dev, minux.ma, agl
    CC=golang-dev
    https://golang.org/cl/7305059

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2b44c33b4a9ca2fa25c88e85803daa57f1080a03

元コミット内容

このコミットは、crypto/md5 パッケージの md5block.go ファイルにおいて、ビッグエンディアンプロセッサでのMD5ハッシュ計算の誤りを修正するものです。既存のコードでは、x86アーキテクチャ(リトルエンディアン)に最適化されたデータ処理パスがありましたが、これが他のアーキテクチャ(特にビッグエンディアン)で問題を引き起こす可能性がありました。

変更の背景

MD5アルゴリズムは、512ビット(64バイト)のメッセージブロックを処理し、それを32ビットワードの配列として扱います。x86アーキテクチャはリトルエンディアンであり、メモリ上のバイト順序がワードの最下位バイトから始まるため、MD5の仕様と自然に整合します。

しかし、Go言語はクロスプラットフォームを志向しており、様々なアーキテクチャで動作します。ARMやPowerPCなど、一部のアーキテクチャはビッグエンディアンを採用しています。ビッグエンディアンシステムでは、メモリ上のバイト順序がワードの最上位バイトから始まるため、リトルエンディアンを前提としたデータ処理を行うと、バイト順序が逆転してしまい、MD5ハッシュ値が誤って計算されるという問題が発生していました。

このコミット以前のコードでは、x86アーキテクチャの場合に、入力データブロック p を直接 *[16]uint32 型にキャストして処理する最適化が行われていました。これは、x86がリトルエンディアンであるため、バイト順序の変換が不要であり、パフォーマンス上の利点があったためです。しかし、この最適化パスがビッグエンディアンシステムで誤って適用されると、データが正しく解釈されず、MD5ハッシュ値が不正になるというバグがありました。

この修正は、このエンディアンネスの問題に対処し、MD5の実装がすべてのサポートされるアーキテクチャで正しく動作するようにすることを目的としています。

前提知識の解説

MD5 (Message-Digest Algorithm 5)

MD5は、1991年にロナルド・リベストによって設計された暗号学的ハッシュ関数です。任意の長さの入力データから、128ビット(16バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。MD5は、データの完全性チェック(データが改ざんされていないことの確認)によく使用されますが、衝突耐性(異なる入力から同じハッシュ値が生成されること)の脆弱性が発見されているため、セキュリティが要求される場面ではSHA-256などのより強力なハッシュ関数が推奨されています。

MD5の処理は、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して一連のビット演算と加算を行うことでハッシュ値を更新していきます。このブロック処理において、入力ブロックを32ビットワードの配列として解釈する際に、エンディアンネスが重要になります。MD5の仕様では、入力ブロックはリトルエンディアン形式で32ビットワードに変換されることを前提としています。

エンディアンネス (Endianness)

エンディアンネスとは、コンピュータのメモリ上で複数バイトのデータを格納する際のバイト順序の規則を指します。主に以下の2種類があります。

  • リトルエンディアン (Little-Endian): データの最下位バイト(Least Significant Byte, LSB)が、メモリ上の最も小さいアドレスに格納されます。x86アーキテクチャ(Intel/AMDプロセッサ)がこれに該当します。 例: 32ビット整数 0x12345678 は、メモリ上では 78 56 34 12 の順で格納されます。

  • ビッグエンディアン (Big-Endian): データの最上位バイト(Most Significant Byte, MSB)が、メモリ上の最も小さいアドレスに格納されます。ネットワークバイトオーダーや、ARM(一部設定)、PowerPC、SPARCなどのプロセッサがこれに該当します。 例: 32ビット整数 0x12345678 は、メモリ上では 12 34 56 78 の順で格納されます。

MD5アルゴリズムは、内部的に32ビットワードをリトルエンディアンとして扱います。したがって、ビッグエンディアンのシステムでMD5を実装する場合、入力データを32ビットワードとして解釈する前に、バイト順序をリトルエンディアンに変換する必要があります。

Go言語の unsafe パッケージ

Go言語の unsafe パッケージは、Goの型システムをバイパスして、低レベルなメモリ操作を可能にする機能を提供します。これには、ポインタと整数間の変換、任意の型のポインタへの変換などが含まれます。

  • unsafe.Pointer: 任意の型のポインタを保持できる特殊なポインタ型です。Goの型安全性を一時的に無効にし、異なる型のデータ構造間でポインタを変換するために使用されます。
  • unsafe.Alignof(x): 変数 x のアライメント(メモリ上の配置制約)を返します。
  • uintptr(p): ポインタ p を整数型に変換します。これにより、ポインタの値を算術演算で操作できるようになります。

unsafe パッケージの使用は、Goの型安全性を損なうため、非常に注意深く行う必要があります。通常は、パフォーマンスがクリティカルな部分や、特定のハードウェア特性に依存するコードでのみ使用されます。このコミットでは、入力バイトスライス p を直接 *[16]uint32 にキャストすることで、バイト順序変換なしに32ビットワードとしてアクセスする最適化のために使用されています。

runtime.GOARCH

runtime.GOARCH は、Goプログラムがコンパイルされ、実行されるターゲットアーキテクチャを示す文字列定数です。例えば、"amd64"、"386"、"arm"、"arm64" などがあります。この定数を利用することで、特定のアーキテクチャに依存するコードパスを条件分岐させることができます。

技術的詳細

この修正の核心は、MD5のブロック処理において、入力データ p*[16]uint32 型に直接キャストしてアクセスする最適化パスを、リトルエンディアンシステムでのみ有効にする点にあります。

元のコードでは、以下の条件でこの最適化パスが適用されていました。

  1. runtime.GOARCH == "amd64" || runtime.GOARCH == "386": x86系のアーキテクチャである場合。これらのアーキテクチャはリトルエンディアンです。
  2. uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0: 入力データ p の先頭アドレスが uint32 のアライメント境界に揃っている場合。これは、unsafe.Pointer を使って puint32 の配列として扱う際に、メモリアクセス違反を防ぐためのチェックです。

問題は、2番目の条件が満たされていても、システムがビッグエンディアンである場合に、バイト順序が逆転した状態でデータが読み込まれてしまうことでした。MD5の仕様はリトルエンディアンを前提としているため、ビッグエンディアンシステムでこの最適化パスが適用されると、誤ったハッシュ値が生成されます。

この修正では、以下の変更が加えられました。

  1. x86 定数の導入: const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386" を導入し、x86アーキテクチャの判定を簡潔にしました。
  2. littleEndian 変数の導入と初期化: var littleEndian bool を宣言し、init() 関数内で実行時にシステムのエンディアンネスを判定するようにしました。
    func init() {
        x := uint32(0x04030201)
        y := [4]byte{0x1, 0x2, 0x3, 0x4}
        littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
    }
    
    このコードは、uint32 の値 0x04030201 をメモリに書き込み、それをバイト配列として読み出すことでエンディアンネスを判定します。
    • リトルエンディアンの場合: 0x04030201 はメモリ上で 01 02 03 04 と格納されるため、y と一致し littleEndiantrue になります。
    • ビッグエンディアンの場合: 0x04030201 はメモリ上で 04 03 02 01 と格納されるため、y と一致せず littleEndianfalse になります。
  3. 最適化パスの条件変更: 元の else if 条件 uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 が、littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 に変更されました。 これにより、入力データ p を直接 *[16]uint32 にキャストする最適化パスは、システムがリトルエンディアンであり、かつアライメントが揃っている場合のみに適用されるようになりました。

この変更により、ビッグエンディアンシステムでは、この最適化パスがスキップされ、代わりに汎用的な(バイト順序変換を伴う)処理パスが使用されるようになります。これにより、MD5ハッシュ計算がすべてのアーキテクチャで正しく行われるようになります。

コアとなるコードの変更箇所

src/pkg/crypto/md5/md5block.go ファイルの以下の部分が変更されました。

--- a/src/pkg/crypto/md5/md5block.go
+++ b/src/pkg/crypto/md5/md5block.go
@@ -5,6 +5,16 @@ import (
 	"unsafe"
 )
 
+const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386"
+
+var littleEndian bool
+
+func init() {
+	x := uint32(0x04030201)
+	y := [4]byte{0x1, 0x2, 0x3, 0x4}
+	littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
+}
+
 func block(dig *digest, p []byte) {
 	a := dig.s[0]
 	b := dig.s[1]
@@ -16,13 +26,13 @@ func block(dig *digest, p []byte) {
 		aa, bb, cc, dd := a, b, c, d
 
 		// This is a constant condition - it is not evaluated on each iteration.
-		if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
+		if x86 {
 			// MD5 was designed so that x86 processors can just iterate
 			// over the block data directly as uint32s, and we generate
 			// less code and run 1.3x faster if we take advantage of that.
 			// My apologies.
 			X = (*[16]uint32)(unsafe.Pointer(&p[0]))
-		} else if uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
+		} else if littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
 			X = (*[16]uint32)(unsafe.Pointer(&p[0]))
 		} else {
 			X = &xbuf

コアとなるコードの解説

  1. const x86 の追加: const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386" これは、Goがx86系のアーキテクチャ(amd64または386)で実行されているかどうかを判定するための定数です。これにより、後続の条件分岐がより読みやすくなります。x86アーキテクチャはリトルエンディアンであるため、この定数はリトルエンディアンの最適化パスに関連します。

  2. var littleEndian boolinit() 関数の追加:

    var littleEndian bool
    
    func init() {
        x := uint32(0x04030201)
        y := [4]byte{0x1, 0x2, 0x3, 0x4}
        littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
    }
    

    littleEndian 変数は、現在のシステムがリトルエンディアンであるかどうかを示すフラグです。 init() 関数は、パッケージが初期化される際に一度だけ実行されます。この関数内で、uint32(0x04030201) という特定の値をメモリに書き込み、そのバイト表現が [4]byte{0x1, 0x2, 0x3, 0x4} と一致するかどうかをチェックすることで、システムのエンディアンネスを動的に判定しています。

    • 0x04030201 は、最下位バイトが 0x01、次に 0x020x03、最上位バイトが 0x04 となる32ビット整数です。
    • リトルエンディアンシステムでは、メモリ上では 01 02 03 04 の順で格納されます。
    • ビッグエンディアンシステムでは、メモリ上では 04 03 02 01 の順で格納されます。 この判定により、littleEndian フラグが正確に設定されます。
  3. block 関数内の条件分岐の変更: block 関数は、MD5のコアとなるブロック処理を行う関数です。この関数内で、入力データ pX という *[16]uint32 型の変数に割り当てる部分の条件が変更されました。

    • 変更前:

      if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else if uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else {
          X = &xbuf
      }
      

      このコードでは、x86アーキテクチャの場合、またはアライメントが揃っている場合に、p を直接 uint32 の配列としてキャストしていました。

    • 変更後:

      if x86 { // runtime.GOARCH == "amd64" || runtime.GOARCH == "386"
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else if littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else {
          X = &xbuf
      }
      

      最初の if 文は x86 定数を使用するように変更され、意味は変わりません。 重要な変更は else if 文です。littleEndian && が追加されました。これにより、p を直接 uint32 の配列としてキャストする最適化パスは、システムがリトルエンディアンであり、かつアライメントが揃っている場合のみに適用されるようになりました。 もしシステムがビッグエンディアンであれば、littleEndianfalse となり、この else if ブロックは実行されません。その結果、X = &xbuf のパス(汎用的な処理パス)が選択され、バイト順序の変換が適切に行われるようになります。

この修正により、MD5のブロック処理が、リトルエンディアンの最適化パスと、ビッグエンディアンを含む汎用的なパスとで適切に分岐されるようになり、すべてのアーキテクチャで正しいMD5ハッシュ値が生成されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • MD5アルゴリズムの仕様に関する情報
  • エンディアンネスに関する一般的なコンピュータサイエンスの知識
  • Go言語の unsafe パッケージの利用例に関する情報
  • Go言語の runtime.GOARCH に関する情報
  • Go言語の init() 関数に関する情報
  • Go言語のコードレビューシステム (Gerrit) の変更リスト (CL) 7305059 (コミットメッセージに記載されているリンク)
    • https://golang.org/cl/7305059 (現在はGoのGerritインスタンスにリダイレクトされます)
    • このリンクは、このコミットの元のコードレビューページであり、議論や背景情報が含まれている可能性があります。# [インデックス 15159] ファイルの概要

このコミットは、Go言語の標準ライブラリ crypto/md5 パッケージにおける、MD5ハッシュ計算のブロック処理に関するバグ修正です。具体的には、ビッグエンディアンプロセッサ上でのMD5計算が正しく行われない問題を解決しています。

コミット

commit 2b44c33b4a9ca2fa25c88e85803daa57f1080a03
Author: Ian Lance Taylor <iant@golang.org>
Date:   Thu Feb 7 13:31:53 2013 -0800

    crypto/md5: fix for big-endian processors
    
    R=golang-dev, minux.ma, agl
    CC=golang-dev
    https://golang.org/cl/7305059

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2b44c33b4a9ca2fa25c88e85803daa57f1080a03

元コミット内容

このコミットは、crypto/md5 パッケージの md5block.go ファイルにおいて、ビッグエンディアンプロセッサでのMD5ハッシュ計算の誤りを修正するものです。既存のコードでは、x86アーキテクチャ(リトルエンディアン)に最適化されたデータ処理パスがありましたが、これが他のアーキテクチャ(特にビッグエンディアン)で問題を引き起こす可能性がありました。

変更の背景

MD5アルゴリズムは、512ビット(64バイト)のメッセージブロックを処理し、それを32ビットワードの配列として扱います。x86アーキテクチャはリトルエンディアンであり、メモリ上のバイト順序がワードの最下位バイトから始まるため、MD5の仕様と自然に整合します。

しかし、Go言語はクロスプラットフォームを志向しており、様々なアーキテクチャで動作します。ARMやPowerPCなど、一部のアーキテクチャはビッグエンディアンを採用しています。ビッグエンディアンシステムでは、メモリ上のバイト順序がワードの最上位バイトから始まるため、リトルエンディアンを前提としたデータ処理を行うと、バイト順序が逆転してしまい、MD5ハッシュ値が誤って計算されるという問題が発生していました。

このコミット以前のコードでは、x86アーキテクチャの場合に、入力データブロック p を直接 *[16]uint32 型にキャストして処理する最適化が行われていました。これは、x86がリトルエンディアンであるため、バイト順序の変換が不要であり、パフォーマンス上の利点があったためです。しかし、この最適化パスがビッグエンディアンシステムで誤って適用されると、データが正しく解釈されず、MD5ハッシュ値が不正になるというバグがありました。

この修正は、このエンディアンネスの問題に対処し、MD5の実装がすべてのサポートされるアーキテクチャで正しく動作するようにすることを目的としています。

前提知識の解説

MD5 (Message-Digest Algorithm 5)

MD5は、1991年にロナルド・リベストによって設計された暗号学的ハッシュ関数です。任意の長さの入力データから、128ビット(16バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。MD5は、データの完全性チェック(データが改ざんされていないことの確認)によく使用されますが、衝突耐性(異なる入力から同じハッシュ値が生成されること)の脆弱性が発見されているため、セキュリティが要求される場面ではSHA-256などのより強力なハッシュ関数が推奨されています。

MD5の処理は、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して一連のビット演算と加算を行うことでハッシュ値を更新していきます。このブロック処理において、入力ブロックを32ビットワードの配列として解釈する際に、エンディアンネスが重要になります。MD5の仕様では、入力ブロックはリトルエンディアン形式で32ビットワードに変換されることを前提としています。

エンディアンネス (Endianness)

エンディアンネスとは、コンピュータのメモリ上で複数バイトのデータを格納する際のバイト順序の規則を指します。主に以下の2種類があります。

  • リトルエンディアン (Little-Endian): データの最下位バイト(Least Significant Byte, LSB)が、メモリ上の最も小さいアドレスに格納されます。x86アーキテクチャ(Intel/AMDプロセッサ)がこれに該当します。 例: 32ビット整数 0x12345678 は、メモリ上では 78 56 34 12 の順で格納されます。

  • ビッグエンディアン (Big-Endian): データの最上位バイト(Most Significant Byte, MSB)が、メモリ上の最も小さいアドレスに格納されます。ネットワークバイトオーダーや、ARM(一部設定)、PowerPC、SPARCなどのプロセッサがこれに該当します。 例: 32ビット整数 0x12345678 は、メモリ上では 12 34 56 78 の順で格納されます。

MD5アルゴリズムは、内部的に32ビットワードをリトルエンディアンとして扱います。したがって、ビッグエンディアンのシステムでMD5を実装する場合、入力データを32ビットワードとして解釈する前に、バイト順序をリトルエンディアンに変換する必要があります。

Go言語の unsafe パッケージ

Go言語の unsafe パッケージは、Goの型システムをバイパスして、低レベルなメモリ操作を可能にする機能を提供します。これには、ポインタと整数間の変換、任意の型のポインタへの変換などが含まれます。

  • unsafe.Pointer: 任意の型のポインタを保持できる特殊なポインタ型です。Goの型安全性を一時的に無効にし、異なる型のデータ構造間でポインタを変換するために使用されます。
  • unsafe.Alignof(x): 変数 x のアライメント(メモリ上の配置制約)を返します。
  • uintptr(p): ポインタ p を整数型に変換します。これにより、ポインタの値を算術演算で操作できるようになります。

unsafe パッケージの使用は、Goの型安全性を損なうため、非常に注意深く行う必要があります。通常は、パフォーマンスがクリティカルな部分や、特定のハードウェア特性に依存するコードでのみ使用されます。このコミットでは、入力バイトスライス p を直接 *[16]uint32 にキャストすることで、バイト順序変換なしに32ビットワードとしてアクセスする最適化のために使用されています。

runtime.GOARCH

runtime.GOARCH は、Goプログラムがコンパイルされ、実行されるターゲットアーキテクチャを示す文字列定数です。例えば、"amd64"、"386"、"arm"、"arm64" などがあります。この定数を利用することで、特定のアーキテクチャに依存するコードパスを条件分岐させることができます。

技術的詳細

この修正の核心は、MD5のブロック処理において、入力データ p*[16]uint32 型に直接キャストしてアクセスする最適化パスを、リトルエンディアンシステムでのみ有効にする点にあります。

元のコードでは、以下の条件でこの最適化パスが適用されていました。

  1. runtime.GOARCH == "amd64" || runtime.GOARCH == "386": x86系のアーキテクチャである場合。これらのアーキテクチャはリトルエンディアンです。
  2. uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0: 入力データ p の先頭アドレスが uint32 のアライメント境界に揃っている場合。これは、unsafe.Pointer を使って puint32 の配列として扱う際に、メモリアクセス違反を防ぐためのチェックです。

問題は、2番目の条件が満たされていても、システムがビッグエンディアンである場合に、バイト順序が逆転した状態でデータが読み込まれてしまうことでした。MD5の仕様はリトルエンディアンを前提としているため、ビッグエンディアンシステムでこの最適化パスが適用されると、誤ったハッシュ値が生成されます。

この修正では、以下の変更が加えられました。

  1. x86 定数の導入: const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386" を導入し、x86アーキテクチャの判定を簡潔にしました。
  2. littleEndian 変数の導入と初期化: var littleEndian bool を宣言し、init() 関数内で実行時にシステムのエンディアンネスを判定するようにしました。
    func init() {
        x := uint32(0x04030201)
        y := [4]byte{0x1, 0x2, 0x3, 0x4}
        littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
    }
    
    このコードは、uint32 の値 0x04030201 をメモリに書き込み、それをバイト配列として読み出すことでエンディアンネスを判定します。
    • リトルエンディアンの場合: 0x04030201 はメモリ上で 01 02 03 04 と格納されるため、y と一致し littleEndiantrue になります。
    • ビッグエンディアンの場合: 0x04030201 はメモリ上で 04 03 02 01 と格納されるため、y と一致せず littleEndianfalse になります。
  3. 最適化パスの条件変更: 元の else if 条件 uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 が、littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 に変更されました。 これにより、入力データ p を直接 *[16]uint32 にキャストする最適化パスは、システムがリトルエンディアンであり、かつアライメントが揃っている場合のみに適用されるようになりました。

この変更により、ビッグエンディアンシステムでは、この最適化パスがスキップされ、代わりに汎用的な(バイト順序変換を伴う)処理パスが使用されるようになります。これにより、MD5ハッシュ計算がすべてのアーキテクチャで正しく行われるようになります。

コアとなるコードの変更箇所

src/pkg/crypto/md5/md5block.go ファイルの以下の部分が変更されました。

--- a/src/pkg/crypto/md5/md5block.go
+++ b/src/pkg/crypto/md5/md5block.go
@@ -5,6 +5,16 @@ import (
 	"unsafe"
 )
 
+const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386"
+
+var littleEndian bool
+
+func init() {
+	x := uint32(0x04030201)
+	y := [4]byte{0x1, 0x2, 0x3, 0x4}
+	littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
+}
+
 func block(dig *digest, p []byte) {
 	a := dig.s[0]
 	b := dig.s[1]
@@ -16,13 +26,13 @@ func block(dig *digest, p []byte) {
 		aa, bb, cc, dd := a, b, c, d
 
 		// This is a constant condition - it is not evaluated on each iteration.
-		if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
+		if x86 {
 			// MD5 was designed so that x86 processors can just iterate
 			// over the block data directly as uint32s, and we generate
 			// less code and run 1.3x faster if we take advantage of that.
 			// My apologies.
 			X = (*[16]uint32)(unsafe.Pointer(&p[0]))
-		} else if uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
+		} else if littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
 			X = (*[16]uint32)(unsafe.Pointer(&p[0]))
 		} else {
 			X = &xbuf

コアとなるコードの解説

  1. const x86 の追加: const x86 = runtime.GOARCH == "amd64" || runtime.GOARCH == "386" これは、Goがx86系のアーキテクチャ(amd64または386)で実行されているかどうかを判定するための定数です。これにより、後続の条件分岐がより読みやすくなります。x86アーキテクチャはリトルエンディアンであるため、この定数はリトルエンディアンの最適化パスに関連します。

  2. var littleEndian boolinit() 関数の追加:

    var littleEndian bool
    
    func init() {
        x := uint32(0x04030201)
        y := [4]byte{0x1, 0x2, 0x3, 0x4}
        littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
    }
    

    littleEndian 変数は、現在のシステムがリトルエンディアンであるかどうかを示すフラグです。 init() 関数は、パッケージが初期化される際に一度だけ実行されます。この関数内で、uint32(0x04030201) という特定の値をメモリに書き込み、そのバイト表現が [4]byte{0x1, 0x2, 0x3, 0x4} と一致するかどうかをチェックすることで、システムのエンディアンネスを動的に判定しています。

    • 0x04030201 は、最下位バイトが 0x01、次に 0x020x03、最上位バイトが 0x04 となる32ビット整数です。
    • リトルエンディアンシステムでは、メモリ上では 01 02 03 04 の順で格納されます。
    • ビッグエンディアンシステムでは、メモリ上では 04 03 02 01 の順で格納されます。 この判定により、littleEndian フラグが正確に設定されます。
  3. block 関数内の条件分岐の変更: block 関数は、MD5のコアとなるブロック処理を行う関数です。この関数内で、入力データ pX という *[16]uint32 型の変数に割り当てる部分の条件が変更されました。

    • 変更前:

      if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else if uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else {
          X = &xbuf
      }
      

      このコードでは、x86アーキテクチャの場合、またはアライメントが揃っている場合に、p を直接 uint32 の配列としてキャストしていました。

    • 変更後:

      if x86 { // runtime.GOARCH == "amd64" || runtime.GOARCH == "386"
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else if littleEndian && uintptr(unsafe.Pointer(&p[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
          X = (*[16]uint32)(unsafe.Pointer(&p[0]))
      } else {
          X = &xbuf
      }
      

      最初の if 文は x86 定数を使用するように変更され、意味は変わりません。 重要な変更は else if 文です。littleEndian && が追加されました。これにより、p を直接 uint32 の配列としてキャストする最適化パスは、システムがリトルエンディアンであり、かつアライメントが揃っている場合のみに適用されるようになりました。 もしシステムがビッグエンディアンであれば、littleEndianfalse となり、この else if ブロックは実行されません。その結果、X = &xbuf のパス(汎用的な処理パス)が選択され、バイト順序の変換が適切に行われるようになります。

この修正により、MD5のブロック処理が、リトルエンディアンの最適化パスと、ビッグエンディアンを含む汎用的なパスとで適切に分岐されるようになり、すべてのアーキテクチャで正しいMD5ハッシュ値が生成されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • MD5アルゴリズムの仕様に関する情報
  • エンディアンネスに関する一般的なコンピュータサイエンスの知識
  • Go言語の unsafe パッケージの利用例に関する情報
  • Go言語の runtime.GOARCH に関する情報
  • Go言語の init() 関数に関する情報
  • Go言語のコードレビューシステム (Gerrit) の変更リスト (CL) 7305059 (コミットメッセージに記載されているリンク)
    • https://golang.org/cl/7305059 (現在はGoのGerritインスタンスにリダイレクトされます)
    • このリンクは、このコミットの元のコードレビューページであり、議論や背景情報が含まれている可能性があります。