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

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

このコミットは、Go言語の実験的なSSHパッケージ(exp/ssh)において、RFC 4253、RFC 4344、RFC 4345で定義されている主要な暗号スイートのサポートを追加するものです。これにより、SSH接続における暗号化の選択肢が大幅に拡張され、より現代的でセキュアな暗号アルゴリズム(特にCTRモードのAESと改良されたArcfour)が利用可能になります。

コミット

commit 0e60804b4a65559613ceae03b8a61b959d0a1cba
Author: John Beisley <huin@google.com>
Date:   Fri Nov 18 12:56:57 2011 -0500

    exp/ssh: Add support for (most) of the ciphers from RFC4253, RFC4344 and RFC4345.
    
    R=dave, agl, taruti, rsc, r
    CC=golang-dev
    https://golang.org/cl/5342057

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

https://github.com/golang/go/commit/0e60804b4a65559613ceae03b8a61b959d0a1cba

元コミット内容

exp/ssh: Add support for (most) of the ciphers from RFC4253, RFC4344 and RFC4345.

変更の背景

このコミットが行われた2011年当時、Go言語の実験的なSSHパッケージ(exp/ssh)は、限られた暗号アルゴリズムしかサポートしていませんでした。特に、SSHプロトコルにおいて広く利用され、セキュリティが強化された暗号モード(例えばAESのCTRモードや改良されたArcfour)への対応が不足していました。

SSHプロトコルは、クライアントとサーバー間の安全な通信を確立するために、鍵交換、認証、そしてデータの暗号化と完全性保護の各フェーズで様々なアルゴリズムを使用します。暗号化アルゴリズムは、通信されるデータの機密性を保証する上で極めて重要です。

RFC 4253はSSHトランスポート層プロトコルの基本を定義しており、暗号化アルゴリズムのネゴシエーションメカニズムを含んでいます。RFC 4344とRFC 4345は、それぞれAESのCTRモードと改良されたArcfour(RC4)モードをSSHに導入し、既存の暗号アルゴリズムに対するセキュリティとパフォーマンスの改善を提供しました。

このコミットの背景には、GoのSSH実装がこれらの標準的な、かつ推奨される暗号アルゴリズムをサポートすることで、相互運用性を高め、より堅牢なセキュリティを提供する必要があったと考えられます。特に、AES-CTRはブロック暗号をストリーム暗号のように扱うことができ、並列処理に適しているため、パフォーマンス面でも有利です。また、Arcfourの改良版は、初期の脆弱性を回避するためのスキップ処理を導入しています。

前提知識の解説

SSH (Secure Shell) プロトコル

SSHは、ネットワークを介して安全にコンピュータを操作するためのプロトコルです。主にリモートログインやファイル転送(SCP, SFTP)に利用されます。SSHは、公開鍵暗号方式と共通鍵暗号方式を組み合わせて、認証、データの機密性、完全性、および認証を提供します。

SSHの接続確立プロセスは、大きく以下のフェーズに分けられます。

  1. トランスポート層プロトコル (Transport Layer Protocol): RFC 4253で定義されており、サーバー認証、機密性、データの完全性を提供する暗号化されたトンネルを確立します。このフェーズで鍵交換アルゴリズム、サーバーホスト鍵アルゴリズム、暗号化アルゴリズム(Cipher)、メッセージ認証コード(MAC)アルゴリズム、圧縮アルゴリズムがネゴシエートされます。
  2. ユーザー認証プロトコル (User Authentication Protocol): RFC 4252で定義されており、クライアントがサーバーに対して自身を認証します。パスワード認証、公開鍵認証、ホストベース認証などがあります。
  3. 接続プロトコル (Connection Protocol): RFC 4254で定義されており、確立されたセキュアなチャネル上で複数の論理チャネル(セッション、X11転送、ポート転送など)を多重化します。

暗号化アルゴリズム (Cipher)

SSHにおける暗号化アルゴリズムは、トランスポート層で交換されるデータを暗号化・復号化するために使用されます。

  • ブロック暗号: データを固定長のブロックに分割して処理します。例: AES (Advanced Encryption Standard)。
  • ストリーム暗号: データをビットまたはバイト単位で連続的に処理します。例: RC4 (Rivest Cipher 4)。

暗号モード

ブロック暗号は、そのままでは単一のブロックしか暗号化できません。連続するデータを暗号化するためには、特定の「モード」と組み合わせて使用されます。

  • CTR (Counter) モード: ブロック暗号をストリーム暗号のように動作させるモードです。カウンタ値を暗号化し、その結果を平文とXORすることで暗号文を生成します。各ブロックの暗号化が独立しているため、並列処理が可能で、高速な処理が期待できます。また、ランダムアクセスが可能で、パディングが不要という利点があります。
  • CBC (Cipher Block Chaining) モード: 各ブロックの暗号化が前のブロックの暗号文に依存するモードです。

RFC 4253: The Secure Shell (SSH) Transport Layer Protocol

SSHのトランスポート層プロトコルを定義する基本的なRFCです。鍵交換、暗号化、メッセージ認証、圧縮などのネゴシエーションと適用方法について記述されています。このRFCは、SSH接続のセキュリティ基盤を形成します。

RFC 4344: The Secure Shell (SSH) Transport Layer Protocol Cryptographic Message Syntax (CMS) for AES-CTR

このRFCは、SSHトランスポート層プロトコルにおけるAESブロック暗号のCTRモードの使用を定義しています。具体的には、aes128-ctr, aes192-ctr, aes256-ctr といった暗号スイートが導入され、それぞれの鍵長(128, 192, 256ビット)に対応します。CTRモードは、その特性からSSHのようなストリームベースのプロトコルに適しています。

RFC 4345: Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol

このRFCは、ストリーム暗号であるArcfour(RC4)の改良版をSSHに導入しています。従来のRC4には初期の出力ストリームに偏りがあるという既知の脆弱性がありましたが、このRFCで定義されるarcfour128arcfour256は、初期の1536バイトのキーストリームを破棄(スキップ)することで、この脆弱性を緩和しています。これにより、より安全にArcfourを使用できるようになります。

crypto/aes および crypto/cipher パッケージ (Go言語)

  • crypto/aes: AESブロック暗号の実装を提供します。aes.NewCipher関数でAES暗号器のインスタンスを作成できます。
  • crypto/cipher: 共通の暗号インターフェース(cipher.Block, cipher.Streamなど)と、様々な暗号モード(CTR, CBCなど)の実装を提供します。cipher.NewCTR関数は、ブロック暗号と初期ベクトル(IV)からCTRモードのストリーム暗号器を作成します。

crypto/rc4 パッケージ (Go言語)

  • crypto/rc4: RC4ストリーム暗号の実装を提供します。rc4.NewCipher関数でRC4暗号器のインスタンスを作成できます。

技術的詳細

このコミットの主要な技術的変更点は、GoのSSHパッケージに複数の新しい暗号アルゴリズムを統合し、それらを柔軟に設定できるようにしたことです。

  1. cipher.go の新規追加:

    • noneCipher 構造体: 暗号化を行わない(コピーのみ)cipher.Stream の実装。鍵交換前の初期状態や、暗号化が不要な場合に利用されます。
    • newAESCTR 関数: AESブロック暗号とCTRモードを組み合わせて cipher.Stream を生成します。crypto/aescrypto/cipher パッケージを利用します。
    • newRC4 関数: RC4ストリーム暗号から cipher.Stream を生成します。crypto/rc4 パッケージを利用します。
    • cipherMode 構造体: 各暗号アルゴリズムのメタデータ(鍵サイズ keySize、IVサイズ ivSize、スキップバイト数 skip、およびストリーム暗号器を生成する関数 createFn)をカプセル化します。
    • cipherMode.createCipher メソッド: cipherMode の情報に基づいて、実際の cipher.Stream インスタンスを生成します。特に、Arcfourのスキップ処理(RFC 4345で定義される1536バイトの初期キーストリーム破棄)を実装しています。
    • DefaultCipherOrder 変数: OpenSSHのデフォルトクライアントの優先順位に基づいた、サポートされる暗号アルゴリズムのデフォルトリストを定義します。これには aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128 が含まれます。
    • cipherModes マップ: 各暗号アルゴリズム名とそれに対応する cipherMode インスタンスをマッピングします。これにより、アルゴリズム名からその特性と生成関数を動的に取得できるようになります。
  2. ClientConfig および ServerConfig への CryptoConfig の追加:

    • common.goCryptoConfig 構造体が新設されました。この構造体は、クライアントとサーバーの両方で共通の暗号関連設定を保持します。
    • CryptoConfigCiphers フィールドを持ち、ユーザーが許可する暗号アルゴリズムのリストを文字列スライスで指定できるようにします。このフィールドが指定されない場合、DefaultCipherOrder が使用されます。
    • ClientConfigServerConfigCrypto フィールド(型は CryptoConfig)が追加され、SSH接続の暗号設定を外部から制御できるようになりました。
  3. 鍵交換メッセージ (kexInitMsg) の更新:

    • client.goserver.go において、鍵交換初期化メッセージ (kexInitMsg) の CiphersClientServerCiphersServerClient フィールドが、ハードコードされた supportedCiphers ではなく、ClientConfig または ServerConfigCryptoConfig から取得されるようになりました。これにより、ネゴシエートされる暗号アルゴリズムが設定に基づいて動的に決定されます。
  4. transport.go の変更:

    • packetSizeMultiple 定数が導入され、パディングの計算に使用されます。これは、暗号アルゴリズムによってブロックサイズが異なるため、より柔軟なパディング処理を可能にするための変更です。
    • readerwriter の初期化時に、初期の暗号器として noneCipher が設定されるようになりました。これは、鍵交換が完了するまでは暗号化が行われないためです。
    • setupKeys 関数が大幅に修正されました。以前はAESに特化していましたが、新しい cipherModes マップと cipherMode.createCipher メソッドを使用して、ネゴシエートされた暗号アルゴリズムに基づいて適切なストリーム暗号器を動的に生成するようになりました。これにより、複数の暗号アルゴリズムに対応できるようになりました。
  5. テストの追加 (cipher_test.go):

    • TestCipherReversal は、各暗号モードが正しく暗号化・復号化できることを検証します。データが暗号化され、その後正しく復号化されて元のデータに戻ることを確認します。
    • TestDefaultCiphersExist は、DefaultCipherOrder にリストされているすべての暗号アルゴリズムが cipherModes マップに存在し、正しく定義されていることを確認します。

これらの変更により、GoのSSHパッケージは、より多くの標準的な暗号アルゴリズムをサポートし、ユーザーが暗号設定を柔軟にカスタマイズできるようになり、セキュリティと相互運用性が向上しました。

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

src/pkg/exp/ssh/cipher.go (新規追加)

package ssh

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rc4"
)

// streamDump is used to dump the initial keystream for stream ciphers. It is a
// a write-only buffer, and not intended for reading so do not require a mutex.
var streamDump [512]byte

// noneCipher implements cipher.Stream and provides no encryption. It is used
// by the transport before the first key-exchange.
type noneCipher struct{}

func (c noneCipher) XORKeyStream(dst, src []byte) {
	copy(dst, src)
}

func newAESCTR(key, iv []byte) (cipher.Stream, error) {
	c, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return cipher.NewCTR(c, iv), nil
}

func newRC4(key, iv []byte) (cipher.Stream, error) {
	return rc4.NewCipher(key)
}

type cipherMode struct {
	keySize  int
	ivSize   int
	skip     int
	createFn func(key, iv []byte) (cipher.Stream, error)
}

func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
	if len(key) < c.keySize {
		panic("ssh: key length too small for cipher")
	}
	if len(iv) < c.ivSize {
		panic("ssh: iv too small for cipher")
	}

	stream, err := c.createFn(key[:c.keySize], iv[:c.ivSize])
	if err != nil {
		return nil, err
	}

	for remainingToDump := c.skip; remainingToDump > 0; {
		dumpThisTime := remainingToDump
		if dumpThisTime > len(streamDump) {
			dumpThisTime = len(streamDump)
		}
		stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
		remainingToDump -= dumpThisTime
	}

	return stream, nil
}

// Specifies a default set of ciphers and a preference order. This is based on
// OpenSSH's default client preference order, minus algorithms that are not
// implemented.
var DefaultCipherOrder = []string{
	"aes128-ctr", "aes192-ctr", "aes256-ctr",
	"arcfour256", "arcfour128",
}

var cipherModes = map[string]*cipherMode{
	// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
	// are defined in the order specified in the RFC.
	"aes128-ctr": &cipherMode{16, aes.BlockSize, 0, newAESCTR},
	"aes192-ctr": &cipherMode{24, aes.BlockSize, 0, newAESCTR},
	"aes256-ctr": &cipherMode{32, aes.BlockSize, 0, newAESCTR},

	// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
	// They are defined in the order specified in the RFC.
	"arcfour128": &cipherMode{16, 0, 1536, newRC4},
	"arcfour256": &cipherMode{32, 0, 1536, newRC4},
}

src/pkg/exp/ssh/common.go (変更箇所抜粋)

 // Cryptographic configuration common to both ServerConfig and ClientConfig.
 type CryptoConfig struct {
 	// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
 	// used.
 	Ciphers []string
 }
 
 func (c *CryptoConfig) ciphers() []string {
 	if c.Ciphers == nil {
 		return DefaultCipherOrder
 	}
 	return c.Ciphers
 }

src/pkg/exp/ssh/transport.go (変更箇所抜粋)

 func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error {
-	h := hashFunc.New()
-
-	blockSize := 16
-	keySize := 16
 	macKeySize := 20
 
-	iv := make([]byte, blockSize)
-	key := make([]byte, keySize)
+	cipherMode := cipherModes[c.cipherAlgo]
+
+	iv := make([]byte, cipherMode.ivSize)
+	key := make([]byte, cipherMode.keySize)
 	macKey := make([]byte, macKeySize)
 
+	h := hashFunc.New()
 	generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
 	generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
 	generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
 
 	c.mac = truncatingMAC{12, hmac.NewSHA1(macKey)}
-	aes, err := aes.NewCipher(key)
+
+	cipher, err := cipherMode.createCipher(key, iv)
 	if err != nil {
 		return err
 	}
-	c.cipher = cipher.NewCTR(aes, iv)
+
+	c.cipher = cipher
+
 	return nil
 }

コアとなるコードの解説

src/pkg/exp/ssh/cipher.go

このファイルは、SSHプロトコルで使用される様々な暗号アルゴリズムの実装と管理の中心となります。

  • noneCipher: 鍵交換が完了するまでの初期状態や、暗号化が不要な場合のプレースホルダーとして機能します。XORKeyStream メソッドは単にデータをコピーするだけで、暗号化は行いません。
  • newAESCTR: AESブロック暗号をCTRモードで動作させるためのストリーム暗号器を生成します。aes.NewCipher でAES暗号器を作成し、cipher.NewCTR でそれをCTRモードのストリーム暗号器にラップします。
  • newRC4: RC4ストリーム暗号器を生成します。
  • cipherMode 構造体: 各暗号アルゴリズムの特性を定義します。
    • keySize: 暗号鍵のバイト長。
    • ivSize: 初期ベクトル(IV)のバイト長。CTRモードではIVが重要です。
    • skip: ストリーム暗号(特にArcfour)で初期キーストリームを破棄するバイト数。RFC 4345で定義されるArcfourのセキュリティ強化のための重要な要素です。
    • createFn: 実際の cipher.Stream インスタンスを生成するための関数ポインタ。
  • cipherMode.createCipher メソッド: cipherMode の定義に基づいて、実際の暗号器を生成します。特に注目すべきは for remainingToDump := c.skip; ... のループです。これは skip フィールドが0より大きい場合に、生成されたストリーム暗号器の初期キーストリームを指定されたバイト数だけ「ダンプ」(破棄)する処理です。これにより、Arcfourの既知の脆弱性(初期キーストリームの偏り)を回避します。
  • DefaultCipherOrder: SSHクライアントがサーバーに提示する暗号アルゴリズムの優先順位リストです。OpenSSHの慣例に従い、よりセキュアでパフォーマンスの良いCTRモードのAESが優先され、次に改良されたArcfourが続きます。
  • cipherModes マップ: 各暗号アルゴリズム名(例: "aes128-ctr")と、それに対応する cipherMode インスタンスを関連付けます。これにより、SSHプロトコルネゴシエーションで合意されたアルゴリズム名から、そのアルゴリズムの具体的な実装と特性を動的に取得できるようになります。

src/pkg/exp/ssh/common.go

このファイルでは、クライアントとサーバーで共通の暗号設定をカプセル化する CryptoConfig 構造体が定義されています。

  • CryptoConfig 構造体:
    • Ciphers []string: ユーザーが明示的に許可する暗号アルゴリズムのリストを指定できます。このリストが nil の場合、DefaultCipherOrder が使用されます。これにより、SSH接続の暗号アルゴリズム選択に柔軟性を持たせることができます。
  • ciphers() メソッド: CryptoConfigCiphers フィールドが設定されている場合はその値を返し、設定されていない場合は DefaultCipherOrder を返します。

src/pkg/exp/ssh/transport.go

このファイルは、SSHトランスポート層の低レベルなデータ送受信と暗号化処理を扱います。

  • setupKeys 関数: 鍵交換が完了した後、実際にデータを暗号化・復号化するための鍵と暗号器を設定する重要な関数です。
    • 以前のバージョンではAESに特化していましたが、このコミットにより、cipherModes[c.cipherAlgo] を使用して、ネゴシエートされた暗号アルゴリズムに対応する cipherMode を動的に取得するようになりました。
    • 取得した cipherMode から ivSizekeySize を取得し、それに基づいてIVと鍵を生成します。
    • 最も重要な変更は、cipherMode.createCipher(key, iv) を呼び出して、ネゴシエートされたアルゴリズムに対応する cipher.Stream インスタンスを生成する点です。これにより、AES-CTRやArcfourなど、様々な暗号アルゴリズムを透過的に利用できるようになります。
    • 生成されたストリーム暗号器は c.cipher に設定され、以降のデータ送受信で暗号化・復号化に使用されます。

これらの変更により、GoのSSHパッケージは、より多くの標準的な暗号アルゴリズムをサポートし、ユーザーが暗号設定を柔軟にカスタマイズできるようになり、セキュリティと相互運用性が向上しました。

関連リンク

参考にした情報源リンク

  • 上記のRFCドキュメント
  • Go言語の標準ライブラリのドキュメント (crypto/aes, crypto/cipher, crypto/rc4)
  • SSHプロトコルに関する一般的な知識
  • OpenSSHの暗号アルゴリズムの選択に関する情報 (OpenSSHのmanページや設定ファイルなど)
  • CTRモードとArcfour(RC4)の動作原理に関する暗号学の知識
  • Go言語のコードベース (src/pkg/exp/ssh ディレクトリ内の他のファイル)

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

このコミットは、Go言語の実験的なSSHパッケージ(exp/ssh)において、RFC 4253、RFC 4344、RFC 4345で定義されている主要な暗号スイートのサポートを追加するものです。これにより、SSH接続における暗号化の選択肢が大幅に拡張され、より現代的でセキュアな暗号アルゴリズム(特にCTRモードのAESと改良されたArcfour)が利用可能になります。

コミット

commit 0e60804b4a65559613ceae03b8a61b959d0a1cba
Author: John Beisley <huin@google.com>
Date:   Fri Nov 18 12:56:57 2011 -0500

    exp/ssh: Add support for (most) of the ciphers from RFC4253, RFC4344 and RFC4345.
    
    R=dave, agl, taruti, rsc, r
    CC=golang-dev
    https://golang.org/cl/5342057

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

https://github.com/golang/go/commit/0e60804b4a65559613ceae03b8a61b959d0a1cba

元コミット内容

exp/ssh: Add support for (most) of the ciphers from RFC4253, RFC4344 and RFC4345.

変更の背景

このコミットが行われた2011年当時、Go言語の実験的なSSHパッケージ(exp/ssh)は、限られた暗号アルゴリズムしかサポートしていませんでした。特に、SSHプロトコルにおいて広く利用され、セキュリティが強化された暗号モード(例えばAESのCTRモードや改良されたArcfour)への対応が不足していました。

SSHプロトコルは、クライアントとサーバー間の安全な通信を確立するために、鍵交換、認証、そしてデータの暗号化と完全性保護の各フェーズで様々なアルゴリズムを使用します。暗号化アルゴリズムは、通信されるデータの機密性を保証する上で極めて重要です。

RFC 4253はSSHトランスポート層プロトコルの基本を定義しており、暗号化アルゴリズムのネゴシエーションメカニズムを含んでいます。RFC 4344とRFC 4345は、それぞれAESのCTRモードと改良されたArcfour(RC4)モードをSSHに導入し、既存の暗号アルゴリズムに対するセキュリティとパフォーマンスの改善を提供しました。

このコミットの背景には、GoのSSH実装がこれらの標準的な、かつ推奨される暗号アルゴリズムをサポートすることで、相互運用性を高め、より堅牢なセキュリティを提供する必要があったと考えられます。特に、AES-CTRはブロック暗号をストリーム暗号のように扱うことができ、並列処理に適しているため、パフォーマンス面でも有利です。また、Arcfourの改良版は、初期の脆弱性を回避するためのスキップ処理を導入しています。

前提知識の解説

SSH (Secure Shell) プロトコル

SSHは、ネットワークを介して安全にコンピュータを操作するためのプロトコルです。主にリモートログインやファイル転送(SCP, SFTP)に利用されます。SSHは、公開鍵暗号方式と共通鍵暗号方式を組み合わせて、認証、データの機密性、完全性、および認証を提供します。

SSHの接続確立プロセスは、大きく以下のフェーズに分けられます。

  1. トランスポート層プロトコル (Transport Layer Protocol): RFC 4253で定義されており、サーバー認証、機密性、データの完全性を提供する暗号化されたトンネルを確立します。このフェーズで鍵交換アルゴリズム、サーバーホスト鍵アルゴリズム、暗号化アルゴリズム(Cipher)、メッセージ認証コード(MAC)アルゴリズム、圧縮アルゴリズムがネゴシエートされます。
  2. ユーザー認証プロトコル (User Authentication Protocol): RFC 4252で定義されており、クライアントがサーバーに対して自身を認証します。パスワード認証、公開鍵認証、ホストベース認証などがあります。
  3. 接続プロトコル (Connection Protocol): RFC 4254で定義されており、確立されたセキュアなチャネル上で複数の論理チャネル(セッション、X11転送、ポート転送など)を多重化します。

暗号化アルゴリズム (Cipher)

SSHにおける暗号化アルゴリズムは、トランスポート層で交換されるデータを暗号化・復号化するために使用されます。

  • ブロック暗号: データを固定長のブロックに分割して処理します。例: AES (Advanced Encryption Standard)。
  • ストリーム暗号: データをビットまたはバイト単位で連続的に処理します。例: RC4 (Rivest Cipher 4)。

暗号モード

ブロック暗号は、そのままでは単一のブロックしか暗号化できません。連続するデータを暗号化するためには、特定の「モード」と組み合わせて使用されます。

  • CTR (Counter) モード: ブロック暗号をストリーム暗号のように動作させるモードです。カウンタ値を暗号化し、その結果を平文とXORすることで暗号文を生成します。各ブロックの暗号化が独立しているため、並列処理が可能で、高速な処理が期待できます。また、ランダムアクセスが可能で、パディングが不要という利点があります。
  • CBC (Cipher Block Chaining) モード: 各ブロックの暗号化が前のブロックの暗号文に依存するモードです。

RFC 4253: The Secure Shell (SSH) Transport Layer Protocol

SSHのトランスポート層プロトコルを定義する基本的なRFCです。鍵交換、暗号化、メッセージ認証、圧縮などのネゴシエーションと適用方法について記述されています。このRFCは、SSH接続のセキュリティ基盤を形成します。

RFC 4344: The Secure Shell (SSH) Transport Layer Protocol Cryptographic Message Syntax (CMS) for AES-CTR

このRFCは、SSHトランスポート層プロトコルにおけるAESブロック暗号のCTRモードの使用を定義しています。具体的には、aes128-ctr, aes192-ctr, aes256-ctr といった暗号スイートが導入され、それぞれの鍵長(128, 192, 256ビット)に対応します。CTRモードは、その特性からSSHのようなストリームベースのプロトコルに適しています。

RFC 4345: Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol

このRFCは、ストリーム暗号であるArcfour(RC4)の改良版をSSHに導入しています。従来のRC4には初期の出力ストリームに偏りがあるという既知の脆弱性がありましたが、このRFCで定義されるarcfour128arcfour256は、初期の1536バイトのキーストリームを破棄(スキップ)することで、この脆弱性を緩和しています。これにより、より安全にArcfourを使用できるようになります。

crypto/aes および crypto/cipher パッケージ (Go言語)

  • crypto/aes: AESブロック暗号の実装を提供します。aes.NewCipher関数でAES暗号器のインスタンスを作成できます。
  • crypto/cipher: 共通の暗号インターフェース(cipher.Block, cipher.Streamなど)と、様々な暗号モード(CTR, CBCなど)の実装を提供します。cipher.NewCTR関数は、ブロック暗号と初期ベクトル(IV)からCTRモードのストリーム暗号器を作成します。

crypto/rc4 パッケージ (Go言語)

  • crypto/rc4: RC4ストリーム暗号の実装を提供します。rc4.NewCipher関数でRC4暗号器のインスタンスを作成できます。

技術的詳細

このコミットの主要な技術的変更点は、GoのSSHパッケージに複数の新しい暗号アルゴリズムを統合し、それらを柔軟に設定できるようにしたことです。

  1. cipher.go の新規追加:

    • noneCipher 構造体: 暗号化を行わない(コピーのみ)cipher.Stream の実装。鍵交換前の初期状態や、暗号化が不要な場合に利用されます。
    • newAESCTR 関数: AESブロック暗号とCTRモードを組み合わせて cipher.Stream を生成します。crypto/aescrypto/cipher パッケージを利用します。
    • newRC4 関数: RC4ストリーム暗号から cipher.Stream を生成します。crypto/rc4 パッケージを利用します。
    • cipherMode 構造体: 各暗号アルゴリズムのメタデータ(鍵サイズ keySize、IVサイズ ivSize、スキップバイト数 skip、およびストリーム暗号器を生成する関数 createFn)をカプセル化します。
    • cipherMode.createCipher メソッド: cipherMode の情報に基づいて、実際の cipher.Stream インスタンスを生成します。特に、Arcfourのスキップ処理(RFC 4345で定義される1536バイトの初期キーストリーム破棄)を実装しています。
    • DefaultCipherOrder 変数: OpenSSHのデフォルトクライアントの優先順位に基づいた、サポートされる暗号アルゴリズムのデフォルトリストを定義します。これには aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128 が含まれます。
    • cipherModes マップ: 各暗号アルゴリズム名とそれに対応する cipherMode インスタンスをマッピングします。これにより、アルゴリズム名からその特性と生成関数を動的に取得できるようになります。
  2. ClientConfig および ServerConfig への CryptoConfig の追加:

    • common.goCryptoConfig 構造体が新設されました。この構造体は、クライアントとサーバーの両方で共通の暗号関連設定を保持します。
    • CryptoConfigCiphers フィールドを持ち、ユーザーが許可する暗号アルゴリズムのリストを文字列スライスで指定できるようにします。このフィールドが指定されない場合、DefaultCipherOrder が使用されます。
    • ClientConfigServerConfigCrypto フィールド(型は CryptoConfig)が追加され、SSH接続の暗号設定を外部から制御できるようになりました。
  3. 鍵交換メッセージ (kexInitMsg) の更新:

    • client.goserver.go において、鍵交換初期化メッセージ (kexInitMsg) の CiphersClientServerCiphersServerClient フィールドが、ハードコードされた supportedCiphers ではなく、ClientConfig または ServerConfigCryptoConfig から取得されるようになりました。これにより、ネゴシエートされる暗号アルゴリズムが設定に基づいて動的に決定されます。
  4. transport.go の変更:

    • packetSizeMultiple 定数が導入され、パディングの計算に使用されます。これは、暗号アルゴリズムによってブロックサイズが異なるため、より柔軟なパディング処理を可能にするための変更です。
    • readerwriter の初期化時に、初期の暗号器として noneCipher が設定されるようになりました。これは、鍵交換が完了するまでは暗号化が行われないためです。
    • setupKeys 関数が大幅に修正されました。以前はAESに特化していましたが、新しい cipherModes マップと cipherMode.createCipher メソッドを使用して、ネゴシエートされた暗号アルゴリズムに基づいて適切なストリーム暗号器を動的に生成するようになりました。これにより、複数の暗号アルゴリズムに対応できるようになりました。
  5. テストの追加 (cipher_test.go):

    • TestCipherReversal は、各暗号モードが正しく暗号化・復号化できることを検証します。データが暗号化され、その後正しく復号化されて元のデータに戻ることを確認します。
    • TestDefaultCiphersExist は、DefaultCipherOrder にリストされているすべての暗号アルゴリズムが cipherModes マップに存在し、正しく定義されていることを確認します。

これらの変更により、GoのSSHパッケージは、より多くの標準的な暗号アルゴリズムをサポートし、ユーザーが暗号設定を柔軟にカスタマイズできるようになり、セキュリティと相互運用性が向上しました。

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

src/pkg/exp/ssh/cipher.go (新規追加)

package ssh

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rc4"
)

// streamDump is used to dump the initial keystream for stream ciphers. It is a
// a write-only buffer, and not intended for reading so do not require a mutex.
var streamDump [512]byte

// noneCipher implements cipher.Stream and provides no encryption. It is used
// by the transport before the first key-exchange.
type noneCipher struct{}

func (c noneCipher) XORKeyStream(dst, src []byte) {
	copy(dst, src)
}

func newAESCTR(key, iv []byte) (cipher.Stream, error) {
	c, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	return cipher.NewCTR(c, iv), nil
}

func newRC4(key, iv []byte) (cipher.Stream, error) {
	return rc4.NewCipher(key)
}

type cipherMode struct {
	keySize  int
	ivSize   int
	skip     int
	createFn func(key, iv []byte) (cipher.Stream, error)
}

func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
	if len(key) < c.keySize {
		panic("ssh: key length too small for cipher")
	}
	if len(iv) < c.ivSize {
		panic("ssh: iv too small for cipher")
	}

	stream, err := c.createFn(key[:c.keySize], iv[:c.ivSize])
	if err != nil {
		return nil, err
	}

	for remainingToDump := c.skip; remainingToDump > 0; {
		dumpThisTime := remainingToDump
		if dumpThisTime > len(streamDump) {
			dumpThisTime = len(streamDump)
		}
		stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
		remainingToDump -= dumpThisTime
	}

	return stream, nil
}

// Specifies a default set of ciphers and a preference order. This is based on
// OpenSSH's default client preference order, minus algorithms that are not
// implemented.
var DefaultCipherOrder = []string{
	"aes128-ctr", "aes192-ctr", "aes256-ctr",
	"arcfour256", "arcfour128",
}

var cipherModes = map[string]*cipherMode{
	// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
	// are defined in the order specified in the RFC.
	"aes128-ctr": &cipherMode{16, aes.BlockSize, 0, newAESCTR},
	"aes192-ctr": &cipherMode{24, aes.BlockSize, 0, newAESCTR},
	"aes256-ctr": &cipherMode{32, aes.BlockSize, 0, newAESCTR},

	// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
	// They are defined in the order specified in the RFC.
	"arcfour128": &cipherMode{16, 0, 1536, newRC4},
	"arcfour256": &cipherMode{32, 0, 1536, newRC4},
}

src/pkg/exp/ssh/common.go (変更箇所抜粋)

 // Cryptographic configuration common to both ServerConfig and ClientConfig.
 type CryptoConfig struct {
 	// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
 	// used.
 	Ciphers []string
 }
 
 func (c *CryptoConfig) ciphers() []string {
 	if c.Ciphers == nil {
 		return DefaultCipherOrder
 	}
 	return c.Ciphers
 }

src/pkg/exp/ssh/transport.go (変更箇所抜粋)

 func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error {
-	h := hashFunc.New()
-
-	blockSize := 16
-	keySize := 16
 	macKeySize := 20
 
-	iv := make([]byte, blockSize)
-	key := make([]byte, keySize)
+	cipherMode := cipherModes[c.cipherAlgo]
+
+	iv := make([]byte, cipherMode.ivSize)
+	key := make([]byte, cipherMode.keySize)
 	macKey := make([]byte, macKeySize)
 
+	h := hashFunc.New()
 	generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
 	generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
 	generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
 
 	c.mac = truncatingMAC{12, hmac.NewSHA1(macKey)}
-	aes, err := aes.NewCipher(key)
+
+	cipher, err := cipherMode.createCipher(key, iv)
 	if err != nil {
 		return err
 	}
-	c.cipher = cipher.NewCTR(aes, iv)
+
+	c.cipher = cipher
+
 	return nil
 }

コアとなるコードの解説

src/pkg/exp/ssh/cipher.go

このファイルは、SSHプロトコルで使用される様々な暗号アルゴリズムの実装と管理の中心となります。

  • noneCipher: 鍵交換が完了するまでの初期状態や、暗号化が不要な場合のプレースホルダーとして機能します。XORKeyStream メソッドは単にデータをコピーするだけで、暗号化は行いません。
  • newAESCTR: AESブロック暗号をCTRモードで動作させるためのストリーム暗号器を生成します。aes.NewCipher でAES暗号器を作成し、cipher.NewCTR でそれをCTRモードのストリーム暗号器にラップします。
  • newRC4: RC4ストリーム暗号器を生成します。
  • cipherMode 構造体: 各暗号アルゴリズムの特性を定義します。
    • keySize: 暗号鍵のバイト長。
    • ivSize: 初期ベクトル(IV)のバイト長。CTRモードではIVが重要です。
    • skip: ストリーム暗号(特にArcfour)で初期キーストリームを破棄するバイト数。RFC 4345で定義されるArcfourのセキュリティ強化のための重要な要素です。
    • createFn: 実際の cipher.Stream インスタンスを生成するための関数ポインタ。
  • cipherMode.createCipher メソッド: cipherMode の定義に基づいて、実際の暗号器を生成します。特に注目すべきは for remainingToDump := c.skip; ... のループです。これは skip フィールドが0より大きい場合に、生成されたストリーム暗号器の初期キーストリームを指定されたバイト数だけ「ダンプ」(破棄)する処理です。これにより、Arcfourの既知の脆弱性(初期キーストリームの偏り)を回避します。
  • DefaultCipherOrder: SSHクライアントがサーバーに提示する暗号アルゴリズムの優先順位リストです。OpenSSHの慣例に従い、よりセキュアでパフォーマンスの良いCTRモードのAESが優先され、次に改良されたArcfourが続きます。
  • cipherModes マップ: 各暗号アルゴリズム名(例: "aes128-ctr")と、それに対応する cipherMode インスタンスを関連付けます。これにより、SSHプロトコルネゴシエーションで合意されたアルゴリズム名から、そのアルゴリズムの具体的な実装と特性を動的に取得できるようになります。

src/pkg/exp/ssh/common.go

このファイルでは、クライアントとサーバーで共通の暗号設定をカプセル化する CryptoConfig 構造体が定義されています。

  • CryptoConfig 構造体:
    • Ciphers []string: ユーザーが明示的に許可する暗号アルゴリズムのリストを指定できます。このリストが nil の場合、DefaultCipherOrder が使用されます。これにより、SSH接続の暗号アルゴリズム選択に柔軟性を持たせることができます。
  • ciphers() メソッド: CryptoConfigCiphers フィールドが設定されている場合はその値を返し、設定されていない場合は DefaultCipherOrder を返します。

src/pkg/exp/ssh/transport.go

このファイルは、SSHトランスポート層の低レベルなデータ送受信と暗号化処理を扱います。

  • setupKeys 関数: 鍵交換が完了した後、実際にデータを暗号化・復号化するための鍵と暗号器を設定する重要な関数です。
    • 以前のバージョンではAESに特化していましたが、このコミットにより、cipherModes[c.cipherAlgo] を使用して、ネゴシエートされた暗号アルゴリズムに対応する cipherMode を動的に取得するようになりました。
    • 取得した cipherMode から ivSizekeySize を取得し、それに基づいてIVと鍵を生成します。
    • 最も重要な変更は、cipherMode.createCipher(key, iv) を呼び出して、ネゴシエートされたアルゴリズムに対応する cipher.Stream インスタンスを生成する点です。これにより、AES-CTRやArcfourなど、様々な暗号アルゴリズムを透過的に利用できるようになります。
    • 生成されたストリーム暗号器は c.cipher に設定され、以降のデータ送受信で暗号化・復号化に使用されます。

これらの変更により、GoのSSHパッケージは、より多くの標準的な暗号アルゴリズムをサポートし、ユーザーが暗号設定を柔軟にカスタマイズできるようになり、セキュリティと相互運用性が向上しました。

関連リンク

参考にした情報源リンク

  • 上記のRFCドキュメント
  • Go言語の標準ライブラリのドキュメント (crypto/aes, crypto/cipher, crypto/rc4)
  • SSHプロトコルに関する一般的な知識
  • OpenSSHの暗号アルゴリズムの選択に関する情報 (OpenSSHのmanページや設定ファイルなど)
  • CTRモードとArcfour(RC4)の動作原理に関する暗号学の知識
  • Go言語のコードベース (src/pkg/exp/ssh ディレクトリ内の他のファイル)