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

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

このコミットは、Go言語の標準ライブラリ crypto/rc4 パッケージ内の rc4_ref.go ファイルに対する修正です。このファイルは、RC4ストリーム暗号の純粋なGo言語によるリファレンス実装を含んでいます。具体的には、RC4の主要な操作であるXORKeyStream関数における型エラーを修正しています。

コミット

commit 9394629b897b81284d1a02ecf840c1a991bd3b96
Author: Richard Musiol <mail@richard-musiol.de>
Date:   Thu Dec 12 14:32:31 2013 -0500

    crypto/rc4: fix type errors in pure Go implementation
    
    R=golang-dev, agl
    CC=golang-dev
    https://golang.org/cl/40540049

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

https://github.com/golang/go/commit/9394629b897b81284d1a02ecf840c1a991bd3b96

元コミット内容

crypto/rc4: fix type errors in pure Go implementation

このコミットは、crypto/rc4 パッケージの純粋なGo言語実装における型エラーを修正することを目的としています。

変更の背景

RC4アルゴリズムの実装において、Go言語の型システムに起因する潜在的なバグが存在していました。特に、uint8 型の変数に対する算術演算の結果が、自動的にデフォルトの整数型(通常は int)に昇格されるGoの挙動が問題を引き起こしていました。RC4の内部状態S-boxは256バイトの配列であり、そのインデックスは0から255の範囲に収まる必要があります。uint8 型はまさにこの範囲を表現するのに適していますが、算術演算によって int に昇格された値が、再び uint8 の範囲に収まることを保証しないまま配列のインデックスとして使用されると、予期せぬ結果やランタイムパニック(例: インデックスが配列の範囲外になる)が発生する可能性がありました。

このコミットは、このような型昇格による問題を明示的な型キャスト uint8() を用いて修正し、RC4アルゴリズムの正確性と堅牢性を確保することを目的としています。

前提知識の解説

RC4 (Rivest Cipher 4)

RC4は、Ron Rivestによって開発されたストリーム暗号です。ストリーム暗号は、データをビットまたはバイト単位で暗号化・復号化する方式で、ブロック暗号とは異なり、固定長のブロックに分割せずに処理を行います。RC4は、そのシンプルさと高速性から、かつてはSSL/TLSやWEPなどの多くのプロトコルで広く利用されていました。

RC4の動作は、主に以下の2つのフェーズに分けられます。

  1. キー・スケジューリング・アルゴリズム (KSA: Key-Scheduling Algorithm):

    • 256バイトのS-box(State box)と呼ばれる配列 S を初期化します。最初は S[i] = i となります。
    • 提供された秘密鍵 key を使用して、S-boxの要素をシャッフルします。このシャッフルは、鍵の長さに基づいて複数回行われます。
    • このフェーズの出力は、初期化されたS-boxです。
  2. 擬似乱数生成アルゴリズム (PRGA: Pseudo-Random Generation Algorithm):

    • KSAで初期化されたS-boxと、2つのポインタ ij (初期値は0) を使用します。
    • 各ステップで、ij を更新し、S-boxからバイトを生成します。
    • 生成されたバイトは、平文のバイトとXOR演算され、暗号文のバイトが生成されます(またはその逆で復号化)。
    • ij の更新は以下のようになります:
      • i = (i + 1) mod 256
      • j = (j + S[i]) mod 256
      • S[i]S[j] をスワップ
      • t = (S[i] + S[j]) mod 256
      • 出力バイトは S[t]

RC4は、その設計上の欠陥(特に鍵の再利用や特定の弱い鍵の使用)が発見されたため、現在では安全ではないとされており、新しいシステムでの使用は推奨されていません。しかし、そのアルゴリズム自体は、暗号の基本的な概念を理解する上で良い例となります。

Go言語の型システムと整数型

Go言語は静的型付け言語であり、変数の型はコンパイル時に決定されます。Goの整数型には、符号付き整数 (int8, int16, int32, int64, int) と符号なし整数 (uint8, uint16, uint32, uint64, uint, uintptr) があります。

  • uint8 型は、0から255までの符号なし8ビット整数を表します。これは、バイトデータや、RC4のS-boxのインデックスのように256の範囲に収まる値を扱うのに非常に適しています。
  • int 型は、システムに依存するデフォルトの符号付き整数型です。通常は32ビットまたは64ビットです。

Go言語では、異なる型の数値演算を行う場合、より広い範囲を表現できる型に自動的に昇格されることがあります。特に、uint8 型の変数と int 型の定数(例: 1)との加算などでは、uint8int に昇格されて演算が行われます。この昇格自体は問題ありませんが、その結果を再び uint8 の範囲に収める必要がある場合や、uint8 を期待する配列のインデックスとして使用する場合には、明示的な型キャストが必要になります。

例えば、var x uint8 = 200; var y uint8 = 100; z := x + y のようなコードでは、x + y の結果は uint8 の最大値255を超えてしまうため、コンパイルエラーになります。Goはオーバーフローを許容せず、開発者に明示的な型変換を促します。しかし、j += c.s[i] のようなケースでは、c.s[i]uint8 ですが、juint8 であり、Goは j + c.s[i] の結果が uint8 の範囲に収まることを保証できないため、この演算の結果は int に昇格されます。この int 型の結果をそのまま uint8 型の j に代入しようとすると、型ミスマッチが発生します。

RC4のS-boxのインデックスは常に0から255の範囲に収まる必要があります。したがって、算術演算の結果がこの範囲に収まることを保証するために、明示的に uint8() にキャストすることが重要です。

技術的詳細

このコミットで修正されているのは、crypto/rc4 パッケージの Cipher 構造体の XORKeyStream メソッド内の2箇所です。

RC4アルゴリズムのPRGAフェーズでは、ij という2つのポインタがS-boxのインデックスとして使用されます。これらのポインタは常に0から255の範囲に収まる必要があります。S-box c.s[256]uint8 型の配列です。

元のコードでは、以下の行に問題がありました。

  1. j += c.s[i]
  2. dst[k] = v ^ c.s[c.s[i]+c.s[j]]

1. j += c.s[i] の問題

  • c.s[i]uint8 型の値です。
  • juint8 型です。
  • Go言語では、uint8 型同士の加算 j + c.s[i] は、結果が uint8 の範囲(0-255)を超える可能性があるため、自動的に int 型に昇格されて演算が行われます。
  • この int 型の結果を、再び uint8 型の j に代入しようとすると、型ミスマッチが発生します。Goは異なる型の代入を許さないため、コンパイルエラーになるか、あるいは暗黙的な型変換が行われる環境では予期せぬ結果(オーバーフローによる値の切り捨てなど)を招く可能性があります。Goの場合は、明示的なキャストがないとコンパイルエラーになります。

修正: j += uint8(c.s[i]) c.s[i]uint8 にキャストしているように見えますが、これは juint8 型であるため、j + c.s[i] の結果を uint8 に明示的にキャストして j に代入していることを意味します。Goの複合代入演算子 +=j = j + c.s[i] と等価です。したがって、j = uint8(j + c.s[i]) となり、j + c.s[i] の結果が int に昇格された後、その結果を明示的に uint8 にキャストして j に代入しています。これにより、j の値が常に uint8 の範囲に収まることが保証されます。

2. dst[k] = v ^ c.s[c.s[i]+c.s[j]] の問題

この行はさらに複雑です。c.s[i]+c.s[j] の部分が問題です。

  • c.s[i]c.s[j] はどちらも uint8 型の値です。
  • これらの加算 c.s[i]+c.s[j] も、結果が uint8 の範囲を超える可能性があるため、int 型に昇格されて演算が行われます。
  • この int 型の結果を、S-box c.s のインデックスとして直接使用しようとすると、Goの配列インデックスは通常 int 型を期待しますが、この値が int 型のままだと、S-boxのインデックスとして使用される際に、その値が255を超えてしまう可能性があります。RC4のS-boxは256要素しか持たないため、インデックスが255を超えるとランタイムパニック(index out of range)が発生します。

修正: dst[k] = v ^ uint8(c.s[uint8(c.s[i]+c.s[j])])

この修正は二重のキャストを含んでいます。

  • 内側の uint8(c.s[i]+c.s[j]): c.s[i]+c.s[j] の結果(int 型に昇格されている)を明示的に uint8 にキャストしています。これにより、S-boxのインデックスとして使用される値が確実に0から255の範囲に収まるようにします。RC4アルゴリズムの定義上、S[i] + S[j] の結果は mod 256 されるため、このキャストは mod 256 演算と同じ効果を持ちます(uint8 のオーバーフローはラップアラウンド挙動を示すため)。
  • 外側の uint8(c.s[...]): c.s[...] の結果は uint8 型ですが、vuint8 型です。v ^ c.s[...] のXOR演算の結果も uint8 型になるため、この外側のキャストは厳密には不要に見えます。しかし、Goのコンパイラが v ^ c.s[...] の結果を int に昇格させる可能性がある場合(例えば、vint 型の変数であるか、あるいはコンテキストによって int が期待される場合)、このキャストは結果を uint8 に明示的に戻す役割を果たします。このケースでは、vsrc スライスから取得された byte 型(uint8 のエイリアス)であり、c.s[...]uint8 なので、XOR演算の結果は uint8 になります。したがって、この外側のキャストは冗長である可能性もありますが、安全のために追加されたか、あるいはGoの古いバージョンでのコンパイラの挙動を考慮したものである可能性があります。最も重要なのは内側のキャストです。

これらの修正により、RC4のS-boxインデックス計算が常に正しい範囲で行われるようになり、アルゴリズムの正確性と安定性が保証されます。

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

--- a/src/pkg/crypto/rc4/rc4_ref.go
+++ b/src/pkg/crypto/rc4/rc4_ref.go
@@ -12,9 +12,9 @@ func (c *Cipher) XORKeyStream(dst, src []byte) {
 	i, j := c.i, c.j
 	for k, v := range src {
 		i += 1
-		j += c.s[i]
+		j += uint8(c.s[i])
 		c.s[i], c.s[j] = c.s[j], c.s[i]
-		dst[k] = v ^ c.s[c.s[i]+c.s[j]]
+		dst[k] = v ^ uint8(c.s[uint8(c.s[i]+c.s[j])])
 	}
 	c.i, c.j = i, j
 }

コアとなるコードの解説

変更は XORKeyStream メソッド内の2行に集中しています。

  1. j += uint8(c.s[i]):

    • RC4のPRGAフェーズにおける j の更新式 j = (j + S[i]) mod 256 に対応します。
    • c.s[i]uint8 型ですが、juint8 型であるため、Goの型推論と演算規則により j + c.s[i] の結果は int 型に昇格される可能性があります。
    • uint8(...) キャストは、この int 型に昇格された結果を明示的に uint8 型に戻し、j に代入することを保証します。これにより、j の値が常に0から255の範囲に収まり、S-boxの有効なインデックスとして機能することが保証されます。Goの uint8 型は256でモジュロ演算を行うかのように振る舞うため、このキャストは mod 256 と同等の効果を持ちます。
  2. dst[k] = v ^ uint8(c.s[uint8(c.s[i]+c.s[j])]):

    • RC4のPRGAフェーズにおける出力バイトの生成式 output = S[(S[i] + S[j]) mod 256] に対応します。
    • 内側の uint8(c.s[i]+c.s[j]): c.s[i]c.s[j] はどちらも uint8 型ですが、その和 c.s[i]+c.s[j]int 型に昇格される可能性があります。この int 型の値をS-box c.s のインデックスとして使用する前に、明示的に uint8 型にキャストしています。これにより、インデックスが常に0から255の範囲に収まることが保証され、index out of range のようなランタイムパニックを防ぎます。
    • 外側の uint8(...): c.s[...] の結果は uint8 型であり、vuint8 型(byte のエイリアス)です。これらのXOR演算の結果も uint8 型になるため、この外側のキャストは冗長に見えるかもしれません。しかし、Goのコンパイラの特定のバージョンや、将来的な型推論の変更に備えて、結果が確実に uint8 型であることを保証するために追加された可能性があります。

これらの修正は、Go言語の厳密な型システムと、RC4アルゴリズムのインデックス計算の特性を考慮したものであり、暗号実装の正確性と安全性を高める上で非常に重要です。

関連リンク

参考にした情報源リンク