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

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

このコミットは、Go言語の標準ライブラリ crypto/x509 パッケージに、楕円曲線 (EC) 秘密鍵をASN.1 DER形式でエンコード(マーシャル)する機能を追加するものです。具体的には、MarshalECPrivateKey 関数が導入され、既存のEC秘密鍵のパース(デコード)機能と対をなす形で、鍵のシリアライズ機能を提供します。

コミット

commit 90352972660db161a04c80ea1bc5f832613592a9
Author: Adam Langley <agl@golang.org>
Date:   Thu Jun 20 12:14:16 2013 -0400

    crypto/x509: add function to marshal EC private keys.
    
    This complements the parsing function that we already have.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/10426043

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

https://github.com/golang/go/commit/90352972660db161a04c80ea1bc5f832613592a9

元コミット内容

crypto/x509 パッケージにEC秘密鍵をマーシャルする関数を追加します。これは、既に存在するパース関数を補完するものです。

変更の背景

Go言語の crypto/x509 パッケージには、既にASN.1 DER形式でエンコードされたEC秘密鍵をパース(デコード)する ParseECPrivateKey 関数が存在していました。しかし、Goの ecdsa.PrivateKey 型のEC秘密鍵オブジェクトを、標準的なASN.1 DER形式にエンコードして出力する(マーシャルする)機能が欠けていました。

この機能の欠如は、Goプログラム内で生成または操作されたEC秘密鍵を、ファイルに保存したり、他のシステムに転送したり、あるいは異なるプログラミング言語で書かれたアプリケーションと相互運用したりする際に問題となります。標準的な形式で出力できなければ、Goの外部で鍵を再利用することが困難になります。

このコミットは、このギャップを埋めるために MarshalECPrivateKey 関数を追加し、EC秘密鍵のパースとマーシャルの両方向の操作を可能にすることで、crypto/x509 パッケージのEC秘密鍵に関する機能の完全性を高めることを目的としています。これにより、GoのEC秘密鍵の取り扱いがより柔軟かつ実用的になります。

前提知識の解説

1. 楕円曲線暗号 (ECC: Elliptic Curve Cryptography)

楕円曲線暗号は、公開鍵暗号の一種で、従来のRSA暗号などと比較して、より短い鍵長で同等のセキュリティ強度を実現できるという特徴があります。これは、楕円曲線上の離散対数問題の困難性に基づいています。EC秘密鍵は、この楕円曲線暗号システムにおいて、署名の生成や鍵の導出などに使用される秘密の情報です。

2. crypto/x509 パッケージ

Go言語の crypto/x509 パッケージは、X.509証明書とPKIX (Public Key Infrastructure X.509) 関連の標準を実装しています。これには、証明書のパース、検証、生成、そして公開鍵および秘密鍵の標準形式(PKCS#1、PKCS#8、SEC1など)のエンコード・デコード機能が含まれます。このコミットで扱われるEC秘密鍵の形式は、SEC1標準に準拠しています。

3. ecdsa パッケージ

Go言語の crypto/ecdsa パッケージは、楕円曲線デジタル署名アルゴリズム (ECDSA) を実装しています。このパッケージは、EC秘密鍵と公開鍵の構造体 (ecdsa.PrivateKey, ecdsa.PublicKey) を定義し、署名の生成と検証のための関数を提供します。MarshalECPrivateKey 関数は、この ecdsa.PrivateKey 型のオブジェクトを入力として受け取ります。

4. elliptic パッケージ

Go言語の crypto/elliptic パッケージは、楕円曲線の基本的な操作(点のスカラー倍算、点の加算など)を実装しています。また、NIST標準曲線(P-256, P-384, P-521など)やSecp256k1などの一般的な楕円曲線の定義も提供します。MarshalECPrivateKey 関数では、公開鍵のバイト列を生成するために elliptic.Marshal 関数が使用されます。

5. ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法です。異なるシステム間でデータを交換する際に、データの形式を明確に定義するために使用されます。暗号化や通信プロトコルにおいて、鍵、証明書、署名などの構造を定義する際によく用いられます。

6. DER (Distinguished Encoding Rules)

DERは、ASN.1で記述されたデータ構造をバイト列にエンコードするための、厳密で一意なルールセットです。ASN.1のエンコーディングルールには複数ありますが、DERは特に暗号化の分野で広く採用されており、同じデータ構造は常に同じバイト列にエンコードされることが保証されます。EC秘密鍵の標準的な表現も、このASN.1 DER形式で定義されています。

7. SEC1

SEC1 (Standards for Efficient Cryptography Group 1) は、楕円曲線暗号に関する標準仕様を定めた文書です。EC秘密鍵のASN.1 DERエンコーディング形式もこのSEC1で定義されており、crypto/x509 パッケージの sec1.go ファイルはこの標準に関連する処理を扱っています。

技術的詳細

このコミットで追加された MarshalECPrivateKey 関数は、ecdsa.PrivateKey 型のEC秘密鍵オブジェクトを受け取り、それをASN.1 DER形式のバイト列に変換します。

関数の内部では、以下のステップが実行されます。

  1. 曲線OIDの取得:

    • 入力された ecdsa.PrivateKey オブジェクトから、その鍵が使用している楕円曲線 (key.Curve) に対応するオブジェクト識別子 (OID: Object Identifier) を取得します。
    • これは oidFromNamedCurve ヘルパー関数によって行われます。OIDは、特定の楕円曲線を一意に識別するための国際標準の数値です。
    • もし未知の曲線が指定された場合(対応するOIDが見つからない場合)、エラーが返されます。
  2. 公開鍵のバイト列生成:

    • EC秘密鍵には対応する公開鍵が含まれています(key.X, key.Y)。
    • これらの公開鍵の座標 (key.X, key.Y) を elliptic.Marshal 関数を使用してバイト列に変換します。このバイト列は、ASN.1の BIT STRING 型としてエンコードされます。
  3. ASN.1構造体の構築:

    • EC秘密鍵のASN.1構造は、SEC1標準で定義されている ECPrivateKey 構造体に従います。
    • この構造体は、バージョン番号、秘密鍵の値(key.D.Bytes())、名前付き曲線のOID、そして公開鍵のビット列を含みます。
    • Goの asn1 パッケージで定義された ecPrivateKey 構造体(コミットには含まれていませんが、既存のコードで定義されているはずです)にこれらの値をマッピングします。
  4. ASN.1 DERエンコード:

    • 構築された ecPrivateKey 構造体を asn1.Marshal 関数に渡すことで、ASN.1 DER形式のバイト列にエンコードします。
    • asn1.Marshal は、Goの構造体のタグ情報(asn1:"...")に基づいて、適切なASN.1タイプとエンコーディングルールを適用し、最終的なDERバイト列を生成します。

このプロセスにより、Goの内部表現である ecdsa.PrivateKey オブジェクトが、他のシステムやアプリケーションと互換性のある標準的なASN.1 DER形式に変換されます。

テストファイル sec1_test.go では、TestParseECPrivateKey 関数が拡張され、既存の ParseECPrivateKey でデコードした鍵を、新しく追加された MarshalECPrivateKey でエンコードし直し、元のDERバイト列と一致するかどうかを検証しています。これにより、マーシャルとパースの往復変換が正しく機能することが保証されます。

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

src/pkg/crypto/x509/sec1.go

--- a/src/pkg/crypto/x509/sec1.go
+++ b/src/pkg/crypto/x509/sec1.go
@@ -33,6 +33,20 @@ func ParseECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error) {
 	return parseECPrivateKey(nil, der)
 }
 
+// MarshalECPrivateKey marshals an EC private key into ASN.1, DER format.
+func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
+	oid, ok := oidFromNamedCurve(key.Curve)
+	if !ok {
+		return nil, errors.New("x509: unknown elliptic curve")
+	}
+	return asn1.Marshal(ecPrivateKey{
+		Version:       1,
+		PrivateKey:    key.D.Bytes(),
+		NamedCurveOID: oid,
+		PublicKey:     asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)},
+	})
+}
+
 // parseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
 // The OID for the named curve may be provided from another source (such as
 // the PKCS8 container) - if it is provided then use this instead of the OID

src/pkg/crypto/x509/sec1_test.go

--- a/src/pkg/crypto/x509/sec1_test.go
+++ b/src/pkg/crypto/x509/sec1_test.go
@@ -5,6 +5,7 @@
 package x509
 
 import (
+	"bytes"
 	"encoding/hex"
 	"testing"
 )
@@ -15,8 +16,15 @@ var ecPrivateKeyHex = `3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4d
 
 func TestParseECPrivateKey(t *testing.T) {
 	derBytes, _ := hex.DecodeString(ecPrivateKeyHex)
-	_, err := ParseECPrivateKey(derBytes)
+	key, err := ParseECPrivateKey(derBytes)
 	if err != nil {
 		t.Errorf("failed to decode EC private key: %s", err)
 	}
+	serialized, err := MarshalECPrivateKey(key)
+	if err != nil {
+		t.Fatalf("failed to encode EC private key: %s", err)
+	}
+	if !bytes.Equal(serialized, derBytes) {
+		t.Fatalf("serialized key differs: got %x, want %x", serialized, derBytes)
+	}
 }

コアとなるコードの解説

src/pkg/crypto/x509/sec1.go の変更

  • MarshalECPrivateKey 関数の追加:
    • この関数は *ecdsa.PrivateKey 型の引数 key を受け取ります。
    • まず、oidFromNamedCurve(key.Curve) を呼び出して、秘密鍵が使用している楕円曲線に対応するASN.1 OIDを取得します。もし対応するOIDが見つからない場合は、"x509: unknown elliptic curve" というエラーを返します。
    • 次に、asn1.Marshal を使用して、ecPrivateKey 構造体をASN.1 DER形式にエンコードします。
    • ecPrivateKey 構造体は、以下のフィールドで初期化されます。
      • Version: 1: SEC1で定義されているEC秘密鍵のバージョン番号。
      • PrivateKey: key.D.Bytes(): 秘密鍵の整数値 D をバイト列に変換したもの。
      • NamedCurveOID: oid: 楕円曲線のOID。
      • PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)}: 公開鍵のX座標とY座標を elliptic.Marshal でバイト列に変換し、それを asn1.BitString でラップしたもの。これはASN.1の BIT STRING 型としてエンコードされます。

src/pkg/crypto/x509/sec1_test.go の変更

  • TestParseECPrivateKey 関数の拡張:
    • 既存のテスト関数が拡張され、ParseECPrivateKey でデコードした key オブジェクトを、新しく追加された MarshalECPrivateKey 関数に渡して再エンコードする処理が追加されました。
    • serialized, err := MarshalECPrivateKey(key): デコードした鍵をマーシャルします。エラーが発生した場合はテストを失敗させます。
    • if !bytes.Equal(serialized, derBytes): マーシャルされたバイト列 (serialized) が、元のDERバイト列 (derBytes) と完全に一致するかどうかを bytes.Equal で比較します。一致しない場合は、テストを失敗させ、期待値と実際の値を出力します。

これらの変更により、Goの crypto/x509 パッケージは、EC秘密鍵のパースとマーシャルの両方をサポートするようになり、より完全な鍵管理機能を提供できるようになりました。

関連リンク

参考にした情報源リンク