[インデックス 14408] ファイルの概要
このコミットは、Go言語のcrypto/x509
パッケージに、SEC1形式の楕円曲線(EC)秘密鍵と、PKCS#8でカプセル化されたEC秘密鍵のパース(解析)機能を追加するものです。これにより、Goの標準ライブラリがより広範な暗号鍵フォーマットに対応できるようになり、相互運用性が向上します。
コミット
- コミットハッシュ:
63315c0af1df2bb3fc443860c2cb70a4a60380b5
- 作者: Joel Sing jsing@google.com
- 日付: 2012年11月15日 木曜日 03:39:00 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/63315c0af1df2bb3fc443860c2cb70a4a60380b5
元コミット内容
crypto/x509: add support for SEC1/EC private keys.
Add support for parsing SEC1 EC private keys and PKCS8 encapsulated
EC private key structures.
R=agl
CC=golang-dev
https://golang.org/cl/6767045
変更の背景
Go言語のcrypto/x509
パッケージは、X.509証明書や公開鍵・秘密鍵の処理を扱うための標準ライブラリです。このコミット以前は、楕円曲線暗号(ECC)の秘密鍵の主要なフォーマットであるSEC1形式や、それらを汎用的な秘密鍵コンテナであるPKCS#8形式でカプセル化したものを直接パースする機能が不足していました。
現代の暗号通信において、ECCはRSAに比べて短い鍵長で同等のセキュリティ強度を提供できるため、TLS/SSL、デジタル署名、暗号化など、様々な場面で広く利用されています。そのため、Go言語がこれらの鍵フォーマットをネイティブにサポートすることは、Goでセキュアなアプリケーションを開発する上で不可欠でした。この変更は、Goの暗号ライブラリの機能性と相互運用性を向上させることを目的としています。
前提知識の解説
1. 楕円曲線暗号 (ECC)
楕円曲線暗号(Elliptic Curve Cryptography, ECC)は、楕円曲線上の点の演算を利用した公開鍵暗号方式です。RSA暗号と比較して、より短い鍵長で同等のセキュリティ強度を実現できるため、計算資源が限られた環境(モバイルデバイスなど)や、通信帯域を節約したい場合に有利です。ECCは、デジタル署名(ECDSA)、鍵共有(ECDH)、暗号化などに応用されます。
2. SEC1 (Standards for Efficient Cryptography)
SEC1は、Standards for Efficient Cryptography Group (SECG) によって定義された、楕円曲線暗号に関する標準規格です。特に、楕円曲線秘密鍵の構造をASN.1 (Abstract Syntax Notation One) で定義しており、そのフォーマットはRFC 5915「Elliptic Curve Private Key Structure」で標準化されています。SEC1形式の秘密鍵は、通常、バージョン、秘密鍵の値(オクテット列)、楕円曲線ドメインパラメータ(通常は名前付き曲線OID)、およびオプションで公開鍵の情報を含みます。
3. PKCS#8 (Public-Key Cryptography Standards #8)
PKCS#8は、RSA Securityによって定義されたPublic-Key Cryptography Standards (PKCS) の一つで、秘密鍵情報を格納するための汎用的な構文を定めています。RFC 5208「PKCS #8: Private-Key Information Syntax Specification」で標準化されています。PKCS#8は、特定のアルゴリズムに依存しないため、RSA、DSA、ECなど、様々な種類の秘密鍵を統一的な形式で扱うことができます。
PKCS#8の主要な構造はPrivateKeyInfo
で、これには以下の要素が含まれます。
version
: 構造のバージョン。privateKeyAlgorithm
: 秘密鍵のアルゴリズムを識別するAlgorithmIdentifier
。これにはアルゴリズムのオブジェクト識別子(OID)と、そのアルゴリズムに固有のパラメータが含まれます。privateKey
: 実際の秘密鍵データ。これはオクテット列として格納され、その内容はprivateKeyAlgorithm
によって解釈されます。
PKCS#8は、秘密鍵を暗号化して格納する機能もサポートしており、パスワードベースの暗号化(PBE)と組み合わせて利用されることが多いです。
4. ASN.1 (Abstract Syntax Notation One)
ASN.1は、データ構造を記述するための標準的な記法であり、通信プロトコルや暗号化において広く使用されています。X.509証明書、PKCS標準、LDAPなど、多くのセキュリティ関連の標準がASN.1で定義されています。ASN.1で定義されたデータ構造は、通常、DER (Distinguished Encoding Rules) やPEM (Privacy-Enhanced Mail) などのエンコーディングルールに従ってバイト列に変換されます。
5. OID (Object Identifier)
OIDは、オブジェクトを一意に識別するための階層的な命名システムです。暗号化の文脈では、アルゴリズム、曲線、証明書ポリシーなどを識別するために使用されます。例えば、楕円曲線公開鍵アルゴリズムは特定のOID (1.2.840.10045.2.1
) で識別され、特定の名前付き曲線(例: secp256r1
)もそれぞれ固有のOIDを持っています。
6. crypto/x509
パッケージ (Go言語)
Go言語の標準ライブラリであるcrypto/x509
パッケージは、X.509証明書、証明書署名要求(CSR)、証明書失効リスト(CRL)、および符号化された公開鍵と秘密鍵のパースと生成を実装しています。このパッケージは、TLS/SSL通信やデジタル署名など、Goアプリケーションにおける暗号機能の基盤を提供します。
技術的詳細
このコミットの主要な目的は、crypto/x509
パッケージがSEC1形式およびPKCS#8でカプセル化されたEC秘密鍵をパースできるようにすることです。
PKCS#8におけるEC秘密鍵のパース
pkcs8.go
ファイルでは、ParsePKCS8PrivateKey
関数が変更されています。この関数は、PKCS#8形式のDERエンコードされた秘密鍵バイト列を受け取り、その内容を解析してGoの適切な秘密鍵構造体(例: *rsa.PrivateKey
や*ecdsa.PrivateKey
)に変換します。
変更点として、privKey.Algo.Algorithm
(秘密鍵のアルゴリズムを識別するOID)がoidPublicKeyECDSA
(楕円曲線公開鍵アルゴリズムのOID)と一致する場合の処理が追加されました。この場合、PKCS#8構造内のパラメータ部分から名前付き曲線OIDを抽出し、実際のEC秘密鍵データ(privKey.PrivateKey
)を新しいparseECPrivateKey
関数に渡して解析します。
SEC1形式EC秘密鍵のパース
新しく追加されたsec1.go
ファイルには、SEC1形式のEC秘密鍵をパースするためのロジックが実装されています。
-
ecPrivateKey
構造体: この構造体は、RFC 5915およびSEC1で定義されているASN.1のECPrivateKey
構造をGoの構造体として表現しています。type ecPrivateKey struct { Version int PrivateKey []byte NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` }
Version
: 秘密鍵構造のバージョン。SEC1では通常1
です。PrivateKey
: 実際のEC秘密鍵の値(整数)をバイト列として格納します。NamedCurveOID
: 使用されている楕円曲線を識別するOID。ASN.1のOPTIONAL
フィールドですが、RFC 5915では必須とされています。explicit,tag:0
は、ASN.1のタグ付けに関する指定です。PublicKey
: 対応する公開鍵。これもOPTIONAL
フィールドです。explicit,tag:1
は、ASN.1のタグ付けに関する指定です。
-
ParseECPrivateKey
関数: この関数は、SEC1形式のDERエンコードされたEC秘密鍵バイト列を受け取り、*ecdsa.PrivateKey
構造体に変換します。内部的にはparseECPrivateKey
を呼び出します。 -
parseECPrivateKey
関数: この内部関数が実際のパースロジックを担います。- まず、入力されたDERバイト列を
ecPrivateKey
構造体にアンマーシャル(ASN.1デコード)します。 privKey.Version
が期待されるバージョン(ecPrivKeyVersion = 1
)と異なる場合はエラーを返します。- 楕円曲線の特定:
- 呼び出し元(PKCS#8パースなど)から
namedCurveOID
が提供されている場合は、そのOIDを使用します。 - 提供されていない場合は、
ecPrivateKey
構造体自身に含まれるprivKey.NamedCurveOID
を使用します。 namedCurveFromOID
関数(既存の関数)を使って、OIDに対応するelliptic.Curve
インターフェースの実装を取得します。不明な曲線OIDの場合はエラーとなります。
- 呼び出し元(PKCS#8パースなど)から
- 秘密鍵の値の検証と設定:
privKey.PrivateKey
(秘密鍵のバイト列)をmath/big.Int
に変換します。- 秘密鍵の値が曲線の位数
N
以上である場合(無効な秘密鍵)はエラーを返します。 - 新しい
ecdsa.PrivateKey
構造体を作成し、パースした曲線と秘密鍵の値D
を設定します。
- 公開鍵の導出:
curve.ScalarBaseMult(privKey.PrivateKey)
を呼び出して、秘密鍵から対応する公開鍵のX座標とY座標を計算し、priv.X
とpriv.Y
に設定します。これにより、完全なecdsa.PrivateKey
構造体が構築されます。
- まず、入力されたDERバイト列を
テストの追加
pkcs8_test.go
とsec1_test.go
には、それぞれPKCS#8でカプセル化されたEC秘密鍵とSEC1形式のEC秘密鍵のパースをテストするための新しいテストケースが追加されています。これらのテストは、openssl
コマンドで生成されたサンプル鍵データを使用しており、実装が正しく機能することを確認します。
コアとなるコードの変更箇所
このコミットでは、以下の4つのファイルが変更されています。
-
src/pkg/crypto/x509/pkcs8.go
:ParsePKCS8PrivateKey
関数に、PKCS#8でカプセル化されたEC秘密鍵をパースするロジックが追加されました。oidPublicKeyECDSA
(楕円曲線公開鍵アルゴリズムのOID)を識別し、parseECPrivateKey
関数を呼び出す分岐が追加されています。- 参照ドキュメントとしてRFC5208が追記されました。
-
src/pkg/crypto/x509/pkcs8_test.go
:TestPKCS8
テスト関数が修正され、RSA秘密鍵のテストに加えて、PKCS#8でカプセル化されたEC秘密鍵のパーステストが追加されました。- 新しいテストデータ
pkcs8ECPrivateKeyHex
が定義されています。
-
src/pkg/crypto/x509/sec1.go
:- 新規ファイルとして追加されました。
- SEC1形式のEC秘密鍵のASN.1構造を表現する
ecPrivateKey
構造体が定義されています。 - SEC1形式のEC秘密鍵をパースする
ParseECPrivateKey
関数と、その内部で利用されるparseECPrivateKey
関数が実装されています。 - RFC5915およびSEC1の参照がコメントとして記載されています。
-
src/pkg/crypto/x509/sec1_test.go
:- 新規ファイルとして追加されました。
TestParseECPrivateKey
テスト関数が実装され、SEC1形式のEC秘密鍵のパースを検証します。- テストデータ
ecPrivateKeyHex
が定義されています。
コアとなるコードの解説
src/pkg/crypto/x509/pkcs8.go
の変更点
// ParsePKCS8PrivateKey parses an unencrypted, PKCS#8 private key. See
// http://www.rsa.com/rsalabs/node.asp?id=2130 and RFC5208.
func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
var privKey pkcs8
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("crypto/x509: failed to parse PKCS#8 private key: " + err.Error())
}
switch {
case privKey.Algo.Algorithm.Equal(oidPublicKeyRSA):
key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
if err != nil {
return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
}
return key, nil
case privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA): // ★追加された部分
bytes := privKey.Algo.Parameters.FullBytes
namedCurveOID := new(asn1.ObjectIdentifier)
if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil {
namedCurveOID = nil
}
key, err = parseECPrivateKey(namedCurveOID, privKey.PrivateKey)
if err != nil {
return nil, errors.New("crypto/x509: failed to parse EC private key embedded in PKCS#8: " + err.Error())
}
return key, nil
default:
return nil, fmt.Errorf("crypto/x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm)
}
}
この変更により、ParsePKCS8PrivateKey
関数は、PKCS#8構造内のアルゴリズム識別子(privKey.Algo.Algorithm
)がECDSA(oidPublicKeyECDSA
)である場合に、新しく追加されたparseECPrivateKey
関数を呼び出すようになりました。privKey.Algo.Parameters.FullBytes
から名前付き曲線OIDを抽出し、それをparseECPrivateKey
に渡すことで、正しい楕円曲線が特定されます。
src/pkg/crypto/x509/sec1.go
の新規追加
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"crypto/ecdsa"
"crypto/elliptic"
"encoding/asn1"
"errors"
"fmt"
"math/big"
)
const ecPrivKeyVersion = 1
// ecPrivateKey reflects an ASN.1 Elliptic Curve Private Key Structure.
// References:
// RFC5915
// SEC1 - http://www.secg.org/download/aid-780/sec1-v2.pdf
// Per RFC5915 the NamedCurveOID is marked as ASN.1 OPTIONAL, however in
// most cases it is not.
type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
// ParseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
func ParseECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error) {
return parseECPrivateKey(nil, der)
}
// 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
// that may exist in the EC private key structure.
func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *ecdsa.PrivateKey, err error) {
var privKey ecPrivateKey
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("crypto/x509: failed to parse EC private key: " + err.Error())
}
if privKey.Version != ecPrivKeyVersion {
return nil, fmt.Errorf("crypto/x509: unknown EC private key version %d", privKey.Version)
}
var curve elliptic.Curve
if namedCurveOID != nil {
curve = namedCurveFromOID(*namedCurveOID)
} else {
curve = namedCurveFromOID(privKey.NamedCurveOID)
}
if curve == nil {
return nil, errors.New("crypto/x509: unknown elliptic curve")
}
k := new(big.Int).SetBytes(privKey.PrivateKey)
if k.Cmp(curve.Params().N) >= 0 {
return nil, errors.New("crypto/x509: invalid elliptic curve private key value")
}
priv := new(ecdsa.PrivateKey)
priv.Curve = curve
priv.D = k
priv.X, priv.Y = curve.ScalarBaseMult(privKey.PrivateKey) // 秘密鍵から公開鍵を導出
return priv, nil
}
このファイルは、SEC1形式のEC秘密鍵のパースロジックを完全にカプセル化しています。ecPrivateKey
構造体はASN.1の定義をGoで表現し、parseECPrivateKey
関数はDERバイト列をこの構造体にアンマーシャルし、バージョンチェック、曲線の特定、秘密鍵の値の検証、そして最終的に公開鍵の導出を行って、Goの*ecdsa.PrivateKey
オブジェクトを返します。特に、curve.ScalarBaseMult(privKey.PrivateKey)
は、秘密鍵から対応する公開鍵を計算する重要なステップです。
関連リンク
- Go CL 6767045: https://golang.org/cl/6767045
参考にした情報源リンク
- RFC 5915: Elliptic Curve Private Key Structure: https://www.rfc-editor.org/rfc/rfc5915
- RFC 5208: PKCS #8: Private-Key Information Syntax Specification Version 1.2: https://www.rfc-editor.org/rfc/rfc5208
- SEC1 - Elliptic Curve Cryptography: http://www.secg.org/sec1-v2.pdf
- Go
crypto/x509
package documentation: https://pkg.go.dev/crypto/x509 - PKCS#8 - Wikipedia: https://en.wikipedia.org/wiki/PKCS_8
- ASN.1 - Wikipedia: https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
- Elliptic Curve Cryptography - Wikipedia: https://en.wikipedia.org/wiki/Elliptic-curve_cryptography