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

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

このコミットは、Go言語の標準ライブラリ crypto/cipher パッケージにおける Cipher Feedback (CFB) モードの実装に関するバグ修正です。具体的には、XORKeyStream メソッド内のループ条件の不備と、入力 (src) と出力 (dst) のバイトスライスが同じメモリ領域を指す(エイリアシングする)場合の安全性に関する問題が修正されています。

コミット

commit 1d546005af72e186c319722ccd293b64246488e3
Author: Adam Langley <agl@golang.org>
Date:   Sun Dec 15 12:55:59 2013 -0500

    crypto/cipher: fix CFB mode.
    
    a073d65e6f8c had a couple of bugs in the CFB mode that I missed in code review:
            1) The loop condition wasn't updated from the old version.
            2) It wasn't safe when src and dst aliased.
    
    Fixes #6950.
    
    R=golang-dev, hanwen
    CC=golang-dev
    https://golang.org/cl/42110043

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

https://github.com/golang/go/commit/1d546005af72e186c319722ccd293b64246488e3

元コミット内容

このコミットは、以前のコミット a073d65e6f8c で導入された crypto/cipher パッケージの CFB モードにおける2つのバグを修正するものです。

変更の背景

Go言語の crypto/cipher パッケージは、暗号化アルゴリズムとそのモードを提供します。CFB (Cipher Feedback) モードは、ブロック暗号をストリーム暗号のように扱うためのモードの一つであり、ネットワーク通信など、データがブロック単位ではなくストリームとして扱われる場合に特に有用です。

このコミットの背景には、以前のコミット a073d65e6f8c で導入された CFB モードの実装に、以下の2つの重要なバグが存在したことがあります。

  1. ループ条件の不備: XORKeyStream メソッド内のデータ処理ループが、スライスが更新されるロジックに合わせて適切に更新されていなかったため、一部のデータが処理されない、または不正な処理が行われる可能性がありました。
  2. エイリアシングの安全性欠如: src (入力) と dst (出力) のバイトスライスが同じメモリ領域を指す(エイリアシングする)場合に、暗号化または復号化の処理が正しく行われないというセキュリティ上の問題がありました。これは、src の内容が dst への書き込みによって途中で変更されてしまい、その後の処理で誤った src の値が参照されることで発生します。

これらのバグは、GoのIssue #6950 として報告されており、このコミットはその問題を解決するために作成されました。暗号ライブラリにおけるこのようなバグは、データの破損だけでなく、セキュリティ上の脆弱性にもつながるため、迅速な修正が求められました。

前提知識の解説

1. ブロック暗号とストリーム暗号

  • ブロック暗号: データを固定長のブロック(例: 128ビット)に分割し、ブロックごとに暗号化・復号化を行う暗号方式です(例: AES)。
  • ストリーム暗号: データをビットまたはバイト単位で暗号化・復号化を行う暗号方式です。鍵ストリームと呼ばれる擬似乱数を生成し、平文とXOR演算することで暗号文を生成します。

2. 暗号利用モード (Modes of Operation)

ブロック暗号を繰り返し適用して、任意の長さのデータを暗号化・復号化するための方法です。様々なモードがあり、それぞれ異なる特性とセキュリティ上の考慮事項があります。

3. CFB (Cipher Feedback) モード

CFBモードは、ブロック暗号をストリーム暗号のように動作させるためのモードです。

  • 特徴:
    • ブロック暗号の暗号化関数のみを使用します。
    • 前の暗号文ブロック(または初期ベクトル IV)をブロック暗号に入力し、その出力の一部を鍵ストリームとして使用します。
    • 鍵ストリームと平文(または暗号文)をXORすることで、暗号文(または平文)を生成します。
    • 自己同期性を持つことができます(一部のモードでは、ビットエラーが発生しても、数ブロック後には同期が回復します)。
  • 暗号化の仕組み:
    1. 初期ベクトル (IV) をブロック暗号に入力し、出力を生成します。
    2. その出力の一部を鍵ストリームとして使用し、現在の平文ブロックとXOR演算して暗号文ブロックを生成します。
    3. 生成された暗号文ブロックを次のブロックの入力として使用します。
  • 復号化の仕組み:
    1. 暗号化時と同じ方法で鍵ストリームを生成します(前の暗号文ブロックをブロック暗号に入力します)。
    2. 生成された鍵ストリームと現在の暗号文ブロックをXOR演算して平文ブロックを復元します。

4. エイリアシング (Aliasing)

プログラミングにおいて、複数の変数や参照が同じメモリ上のデータ(オブジェクトや配列など)を指している状態を指します。特に、入力バッファと出力バッファが同じメモリ領域を共有している場合、出力への書き込みが入力の読み取りに影響を与え、予期せぬ動作やバグを引き起こす可能性があります。暗号処理では、このようなエイリアシングがセキュリティ上の脆弱性につながることもあります。

5. Go言語のスライス (Slice)

Go言語のスライスは、配列の一部を参照する動的なビューです。srcdst がスライスである場合、それらが同じ基底配列を共有している可能性があります。

6. XORKeyStream インターフェース

Goの crypto/cipher パッケージでは、ストリーム暗号は XORKeyStream(dst, src []byte) メソッドを持つインターフェースを実装することが一般的です。このメソッドは、src からデータを読み込み、鍵ストリームとXOR演算した結果を dst に書き込みます。

技術的詳細

このコミットは、crypto/cipher/cfb.go 内の cfb 構造体の XORKeyStream メソッドに焦点を当てています。

1. ループ条件の修正

元のコードでは、for i := 0; i < len(src); i++ というループ条件が使用されていました。しかし、ループ内で srcdst スライスは dst = dst[n:] および src = src[n:] のように、処理されたバイト数 n に応じて先頭が切り詰められて(スライスが再設定されて)いました。このため、len(src) はループの各イテレーションで変化しますが、i は常にインクリメントされるため、ループが正しく終了しない、または一部のデータが処理されない可能性がありました。

修正後のコードでは、ループ条件が for len(src) > 0 に変更されています。これにより、src スライスにデータが残っている限りループが継続され、srcdst が適切に切り詰められることで、すべてのデータが確実に処理されるようになります。これは、ストリーム処理において非常に一般的なパターンです。

2. エイリアシング問題の解決

CFBモードでは、次のブロックの鍵ストリームを生成するために、暗号化の場合は生成された暗号文を、復号化の場合は入力された暗号文を内部状態 (x.next) にコピーする必要があります。

元のコードでは、xorBytes の呼び出しと x.next への copy の順序が、エイリアシングのシナリオで問題を引き起こしていました。

  • 元のコードの課題:

    // ...
    if x.decrypt {
        copy(x.next[x.outUsed:], src[:n]) // 復号化: src (暗号文) をコピー
    } else {
        copy(x.next[x.outUsed:], dst[:n]) // 暗号化: dst (平文) をコピー
    }
    n := xorBytes(dst, src, x.out[x.outUsed:]) // dst に結果を書き込む
    // ...
    

    暗号化の場合 (!x.decrypt)、copy(x.next[x.outUsed:], dst[:n])xorBytes の前に実行されていました。この時点での dst はまだ平文を含んでいます。しかし、CFB暗号化では、次のブロックの入力として生成された暗号文が必要になります。xorBytes が実行されると dst は暗号文に上書きされますが、その前に平文が x.next にコピーされてしまうため、内部状態が不正になり、結果として誤った鍵ストリームが生成されていました。

  • 修正後のコード:

    // ...
    if x.decrypt {
        // 復号化: src (入力された暗号文) を x.next にコピー。
        // src は xorBytes の後でスライスされるため、ここでは全体をコピー。
        copy(x.next[x.outUsed:], src)
    }
    // xorBytes を先に実行し、dst に正しい暗号文/平文を書き込む
    n := xorBytes(dst, src, x.out[x.outUsed:])
    if !x.decrypt {
        // 暗号化: dst (生成された暗号文) を x.next にコピー。
        // xorBytes の後に実行されるため、dst は既に暗号文になっている。
        copy(x.next[x.outUsed:], dst)
    }
    // ...
    

    この修正では、xorBytes の呼び出しを常に copy 操作の前に移動させました。

    • 復号化の場合 (x.decrypt): x.next には入力された暗号文 (src) が必要です。srcxorBytes の後でスライスされるため、xorBytes の前に src をコピーしても問題ありません。
    • 暗号化の場合 (!x.decrypt): x.next には生成された暗号文 (dst) が必要です。xorBytes が先に実行されることで dst に正しい暗号文が書き込まれ、その後に copy(x.next[x.outUsed:], dst) が実行されることで、正しい暗号文が x.next にコピーされるようになります。

この変更により、srcdst がエイリアシングしている場合でも、x.next が常に正しい値で更新されることが保証され、CFBモードの安全性が向上しました。

3. テストケースの追加

cfb_test.go では、エイリアシングのシナリオを明示的にテストするために、plaintext の長さを増やし、XORKeyStream の呼び出しで srcdst に同じスライスを渡すように変更されました。

// 暗号化
copy(ciphertext, plaintext) // plaintext を ciphertext にコピー
cfb.XORKeyStream(ciphertext, ciphertext) // src と dst が同じスライス

// 復号化
copy(plaintextCopy, ciphertext) // ciphertext を plaintextCopy にコピー
cfbdec.XORKeyStream(plaintextCopy, plaintextCopy) // src と dst が同じスライス

これにより、XORKeyStream がエイリアシング条件下で正しく動作するかどうかが検証されるようになりました。

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

src/pkg/crypto/cipher/cfb.go

--- a/src/pkg/crypto/cipher/cfb.go
+++ b/src/pkg/crypto/cipher/cfb.go
@@ -16,21 +16,22 @@ type cfb struct {
 }
 
 func (x *cfb) XORKeyStream(dst, src []byte) {
-	for i := 0; i < len(src); i++ {
+	for len(src) > 0 {
 		if x.outUsed == len(x.out) {
 			x.b.Encrypt(x.out, x.next)
 			x.outUsed = 0
 		}
 
-		n := xorBytes(dst, src, x.out[x.outUsed:])
 		if x.decrypt {
 			// We can precompute a larger segment of the
 			// keystream on decryption. This will allow
 			// larger batches for xor, and we should be
 			// able to match CTR/OFB performance.
-			copy(x.next[x.outUsed:], src[:n])
-		} else {
-			copy(x.next[x.outUsed:], dst[:n])
+			copy(x.next[x.outUsed:], src)
+		}
+		n := xorBytes(dst, src, x.out[x.outUsed:])
+		if !x.decrypt {
+			copy(x.next[x.outUsed:], dst)
 		}
 		dst = dst[n:]
 		src = src[n:]

src/pkg/crypto/cipher/cfb_test.go

--- a/src/pkg/crypto/cipher/cfb_test.go
+++ b/src/pkg/crypto/cipher/cfb_test.go
@@ -19,16 +19,18 @@ func TestCFB(t *testing.T) {
 		return
 	}
 
-	plaintext := []byte("this is the plaintext")
+	plaintext := []byte("this is the plaintext. this is the plaintext.")
 	iv := make([]byte, block.BlockSize())
 	rand.Reader.Read(iv)
 	cfb := cipher.NewCFBEncrypter(block, iv)
 	ciphertext := make([]byte, len(plaintext))
-	cfb.XORKeyStream(ciphertext, plaintext)
+	copy(ciphertext, plaintext)
+	cfb.XORKeyStream(ciphertext, ciphertext)
 
 	cfbdec := cipher.NewCFBDecrypter(block, iv)
 	plaintextCopy := make([]byte, len(plaintext))
-	cfbdec.XORKeyStream(plaintextCopy, ciphertext)
+	copy(plaintextCopy, ciphertext)
+	cfbdec.XORKeyStream(plaintextCopy, plaintextCopy)
 
 	if !bytes.Equal(plaintextCopy, plaintext) {
 		t.Errorf("got: %x, want: %x", plaintextCopy, plaintext)

コアとなるコードの解説

cfb.go の変更点

  1. ループ条件の変更:

    • for i := 0; i < len(src); i++ { から for len(src) > 0 { へ変更。
    • これにより、src スライスが処理されるたびに先頭が切り詰められる(src = src[n:])という動作と同期し、残りのデータがなくなるまでループが正確に繰り返されるようになりました。
  2. xorBytescopy の順序変更と条件分岐の修正:

    • 元のコードでは、xorBytes の前に x.next への copy が行われていました。
    • 修正後、xorBytes が常に先に実行されるようになりました。
    • 復号化 (x.decrypt が true の場合):
      • copy(x.next[x.outUsed:], src): 入力された暗号文 (src) を x.next にコピーします。復号化では、次の鍵ストリーム生成のために前の暗号文が必要なので、これは正しい動作です。srcxorBytes の後でスライスされるため、xorBytes の前にコピーしても問題ありません。
    • 暗号化 (!x.decrypt が true の場合):
      • copy(x.next[x.outUsed:], dst): xorBytes の実行後、dst には平文と鍵ストリームのXOR結果である暗号文が格納されます。この暗号文を x.next にコピーすることで、次のブロックの鍵ストリーム生成のための正しい入力が提供されます。エイリアシングのシナリオでは、この順序が非常に重要です。

cfb_test.go の変更点

  1. plaintext の長さの増加:

    • "this is the plaintext" から "this is the plaintext. this is the plaintext." へ変更。
    • これにより、より長いデータに対するテストが可能になり、特に複数ブロックにわたる処理の正確性を検証しやすくなります。
  2. エイリアシングテストの追加:

    • 暗号化と復号化の両方で、XORKeyStreamsrcdst 引数に同じスライスを渡すように変更されました。
    • copy(ciphertext, plaintext)cfb.XORKeyStream(ciphertext, ciphertext): plaintext の内容を ciphertext にコピーした後、ciphertext を入力と出力の両方に使用して暗号化を実行します。
    • copy(plaintextCopy, ciphertext)cfbdec.XORKeyStream(plaintextCopy, plaintextCopy): 同様に、復号化でも同じスライスを使用します。
    • これらの変更により、XORKeyStream メソッドが入力と出力のバッファが重複している(エイリアシングしている)場合でも、正しく動作することが保証されるようになりました。これは、多くの暗号ライブラリで考慮されるべき重要なエッジケースです。

これらの変更は、CFBモードの実装の堅牢性と安全性を大幅に向上させ、暗号処理における一般的な落とし穴(ループ処理の不備やエイリアシング)を効果的に修正しています。

関連リンク

参考にした情報源リンク