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

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

このコミットは、Go言語の crypto/x509 パッケージに EncryptPEMBlock 関数を実装し、PEM (Privacy-Enhanced Mail) 形式で暗号化されたブロックを生成する機能を追加します。また、既存の DecryptPEMBlock 関数における初期化ベクトル (IV) のサイズが不正な場合にパニックを起こさないように修正を加えています。これにより、PEM形式の秘密鍵などのデータを、指定された暗号アルゴリズムとパスワードを用いて安全に暗号化し、PEMブロックとして出力できるようになります。

コミット

  • コミットハッシュ: 791fb978ddde3bbe0117ad0eab2746d66b43d965
  • Author: Roger Peppe rogpeppe@gmail.com
  • Date: Mon Nov 12 15:31:23 2012 +0000

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

https://github.com/golang/go/commit/791fb978ddde3bbe0117ad0eab2746d66b43d965

元コミット内容

    crypto/x509: implement EncryptPEMBlock
    
    Arbitrary decisions: order of the arguments and the
    fact it takes a block-type argument (rather than
    leaving to user to fill it in later); I'm happy whatever
    colour we want to paint it.
    
    We also change DecryptPEMBlock so that it won't
    panic when the IV has the wrong size.
    
    R=agl, agl
    CC=golang-dev
    https://golang.org/cl/6820114

変更の背景

この変更の主な背景は、Go言語の crypto/x509 パッケージにおいて、PEM形式のデータを暗号化する標準的な機能が不足していたことです。既存の DecryptPEMBlock 関数はPEMブロックの復号化をサポートしていましたが、対応する暗号化機能がありませんでした。これにより、アプリケーションが秘密鍵などの機密情報をPEM形式で安全に保存する際に、外部ライブラリに依存するか、独自の実装を行う必要がありました。

EncryptPEMBlock の追加により、Go標準ライブラリ内でPEM形式の暗号化が完結できるようになり、開発者はよりセキュアで一貫性のある方法で鍵管理を行うことが可能になります。また、DecryptPEMBlock におけるIVサイズの不正によるパニックの修正は、堅牢性の向上と、より予測可能なエラーハンドリングを提供することを目的としています。これは、セキュリティ関連のコードにおいては特に重要であり、不正な入力に対してもサービスがクラッシュしないようにするための改善です。

前提知識の解説

PEM (Privacy-Enhanced Mail) 形式

PEMは、公開鍵証明書、秘密鍵、CRL (Certificate Revocation List) などのX.509関連データをASCII形式で表現するためのエンコーディング標準です。通常、-----BEGIN XXX----------END XXX----- のようなヘッダーとフッターで囲まれたBase64エンコードされたデータで構成されます。これにより、バイナリデータをテキストベースのシステム(メール、Webページなど)で安全に転送・保存できます。

X.509 証明書

X.509は、公開鍵証明書の標準フォーマットを定義するITU-Tの標準です。PKI (公開鍵基盤) において、エンティティ(ユーザー、サーバーなど)の公開鍵と、その公開鍵が属するエンティティの識別情報を結びつけるために使用されます。

対称鍵暗号アルゴリズム (DES, 3DES, AES)

  • DES (Data Encryption Standard): 56ビットの鍵長を持つブロック暗号で、かつて広く使われましたが、現在では鍵長が短いため安全ではないとされています。
  • 3DES (Triple DES): DESを3回適用することでセキュリティを強化したブロック暗号です。DESよりも安全ですが、AESに比べて処理が重いです。
  • AES (Advanced Encryption Standard): 現在最も広く使われているブロック暗号の一つで、128ビット、192ビット、256ビットの鍵長をサポートします。高速で安全性が高く、多くのプロトコルやアプリケーションで採用されています。

CBC (Cipher Block Chaining) モード

CBCは、ブロック暗号の運用モードの一つです。各ブロックの暗号化は、前のブロックの暗号文と現在の平文ブロックをXOR演算してから暗号化することで行われます。これにより、同じ平文ブロックが連続しても異なる暗号文が生成され、パターンが漏洩するのを防ぎます。CBCモードでは、最初のブロックを暗号化するために「初期化ベクトル (IV)」と呼ばれるランダムな値が必要です。IVは暗号文と一緒に送信されますが、予測可能であってはなりません。

初期化ベクトル (IV)

IVは、CBCモードなどのブロック暗号運用モードで使用されるランダムなビット列です。同じ鍵で同じ平文を複数回暗号化しても、異なる暗号文が生成されるようにするために使用されます。IVは秘密である必要はありませんが、予測不可能である必要があります。

PKCS#5 (Password-Based Cryptography Standard)

PKCS#5は、パスワードから暗号鍵を導出するための標準を定義しています。PEM形式の暗号化では、ユーザーが指定したパスワードから、実際にデータを暗号化するための鍵を生成する際にこの標準が参照されることがあります。特に、鍵導出関数 (KDF) としてPBKDF1やPBKDF2が定義されています。このコミットでは、RFC 1423で定義されている鍵導出メカニズムが使用されています。

RFC 1423 (Privacy Enhancement for Internet Electronic Mail: Part III: Algorithms, Modes, and Identifiers)

RFC 1423は、インターネット電子メールのプライバシー強化のためのアルゴリズム、モード、識別子を定義しています。このRFCは、PEM形式の暗号化において、どのような暗号アルゴリズムを使用し、どのように鍵を導出し、どのようにデータをエンコードするかについて詳細を規定しています。特に、パスワードベースの暗号化における鍵導出方法や、DEK-Info ヘッダーのフォーマットなどが関連します。

Go言語の crypto/x509 パッケージ

Go言語の crypto/x509 パッケージは、X.509証明書、CRL、OCSP (Online Certificate Status Protocol) などの解析と生成をサポートします。このパッケージは、TLS/SSL通信やコード署名など、様々なセキュリティ関連のアプリケーションで利用されます。このコミットで追加された EncryptPEMBlock は、このパッケージの一部として、PEM形式の暗号化機能を提供します。

技術的詳細

このコミットは、主に src/pkg/crypto/x509/pem_decrypt.go ファイルに EncryptPEMBlock 関数を追加し、関連するデータ構造とヘルパー関数を修正しています。

EncryptPEMBlock 関数の追加

EncryptPEMBlock 関数は、以下の引数を受け取ります。

  • rand io.Reader: 暗号論的に安全な乱数を生成するためのリーダー(例: crypto/rand.Reader)。IVの生成に使用されます。
  • blockType string: PEMブロックのタイプ(例: "RSA PRIVATE KEY")。
  • data []byte: 暗号化する元のデータ(DERエンコードされた秘密鍵など)。
  • password []byte: 暗号化に使用するパスワード。
  • alg PEMCipher: 使用する暗号アルゴリズム(PEMCipherDES, PEMCipher3DES, ``PEMCipherAES128, PEMCipherAES192, PEMCipherAES256`)。

この関数は、以下の手順でPEMブロックを暗号化します。

  1. アルゴリズムの選択: alg 引数に基づいて、rfc1423Algos スライスから対応する暗号アルゴリズム (rfc1423Algo 構造体) を選択します。
  2. IVの生成: 選択されたアルゴリズムのブロックサイズに基づいて、ランダムな初期化ベクトル (IV) を生成します。io.ReadFull を使用して rand リーダーから乱数を読み込みます。
  3. 鍵の導出: パスワードとIVの最初の8バイト(RFC 1423の鍵導出メカニズムに従う)を使用して、暗号化鍵を導出します。これは ciph.deriveKey メソッドによって行われます。
  4. ブロック暗号の初期化: 導出された鍵と選択されたアルゴリズムの cipherFunc を使用して、cipher.Block インターフェースを実装するブロック暗号(例: DES, AES)を初期化します。
  5. CBCエンクリプタの作成: 初期化されたブロック暗号と生成されたIVを使用して、cipher.NewCBCEncrypter でCBCモードのエンクリプタを作成します。
  6. パディング: 暗号化するデータがブロックサイズの倍数になるように、PKCS#7パディングを適用します。パディングバイトの値は、追加されたパディングのバイト数になります。
  7. データの暗号化: CBCエンクリプタの CryptBlocks メソッドを使用して、パディングされたデータを暗号化します。
  8. PEMブロックの構築: 暗号化されたデータ、ブロックタイプ、および Proc-TypeDEK-Info ヘッダーを含む pem.Block 構造体を構築して返します。DEK-Info ヘッダーには、使用された暗号アルゴリズム名とIVの16進数エンコードが含まれます。

DecryptPEMBlock の修正

DecryptPEMBlock 関数は、IVのサイズが不正な場合にパニックを起こすのではなく、エラーを返すように修正されました。具体的には、IVの長さが期待されるブロックサイズと一致しない場合に、errors.New("x509: incorrect IV size") を返すようになります。これにより、不正なPEMブロックが入力された場合でも、プログラムがクラッシュすることなく、エラーハンドリングが可能になります。

rfc1423Algos の変更とヘルパー関数の追加

  • rfc1423Algos は、以前の map[string]rfc1423Algo から []rfc1423Algo スライスに変更されました。これにより、PEMCipher 型によるインデックスアクセスや、名前による検索が容易になります。
  • PEMCipher という新しい型が定義され、DES, 3DES, AES-128, AES-192, AES-256 の各暗号アルゴリズムに対応する定数が追加されました。
  • rfc1423Algo 構造体には、cipher (PEMCipher型) と blockSize フィールドが追加されました。
  • cipherByName(name string) *rfc1423AlgocipherByKey(key PEMCipher) *rfc1423Algo という2つのヘルパー関数が追加され、それぞれ名前または PEMCipher 値に基づいて rfc1423Algo を検索できるようになりました。

これらの変更により、暗号化アルゴリズムの管理がより構造化され、EncryptPEMBlockDecryptPEMBlock の両方で共通のアルゴリズム定義を使用できるようになりました。

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

src/pkg/crypto/x509/pem_decrypt.go

--- a/src/pkg/crypto/x509/pem_decrypt.go
+++ b/src/pkg/crypto/x509/pem_decrypt.go
@@ -16,23 +16,64 @@ import (
 	"encoding/hex"
 	"encoding/pem"
 	"errors"
+	"io"
 	"strings"
 )
 
-// rfc1423Algos represents how to create a block cipher for a decryption mode.
+type PEMCipher int
+
+// Possible values for the EncryptPEMBlock encryption algorithm.
+const (
+	_ PEMCipher = iota
+	PEMCipherDES
+	PEMCipher3DES
+	PEMCipherAES128
+	PEMCipherAES192
+	PEMCipherAES256
+)
+
+// rfc1423Algo holds a method for enciphering a PEM block.
 type rfc1423Algo struct {
-	cipherFunc func([]byte) (cipher.Block, error)
+	cipher     PEMCipher
+	name       string
+	cipherFunc func(key []byte) (cipher.Block, error)
 	keySize    int
+	blockSize  int
 }
 
-// rfc1423Algos is a mapping of encryption algorithm to an rfc1423Algo that can
-// create block ciphers for that mode.
-var rfc1423Algos = map[string]rfc1423Algo{
-	"DES-CBC":      {des.NewCipher, 8},
-	"DES-EDE3-CBC": {des.NewTripleDESCipher, 24},
-	"AES-128-CBC":  {aes.NewCipher, 16},
-	"AES-192-CBC":  {aes.NewCipher, 24},
-	"AES-256-CBC":  {aes.NewCipher, 32},
+// rfc1423Algos holds a slice of the possible ways to encrypt a PEM
+// block.  The ivSize numbers were taken from the OpenSSL source.
+var rfc1423Algos = []rfc1423Algo{{
+	cipher:     PEMCipherDES,
+	name:       "DES-CBC",
+	cipherFunc: des.NewCipher,
+	keySize:    8,
+	blockSize:  des.BlockSize,
+}, {
+	cipher:     PEMCipher3DES,
+	name:       "DES-EDE3-CBC",
+	cipherFunc: des.NewTripleDESCipher,
+	keySize:    24,
+	blockSize:  des.BlockSize,
+}, {
+	cipher:     PEMCipherAES128,
+	name:       "AES-128-CBC",
+	cipherFunc: aes.NewCipher,
+	keySize:    16,
+	blockSize:  aes.BlockSize,
+}, {
+	cipher:     PEMCipherAES192,
+	name:       "AES-192-CBC",
+	cipherFunc: aes.NewCipher,
+keySize:    24,
+	blockSize:  aes.BlockSize,
+}, {
+	cipher:     PEMCipherAES256,
+	name:       "AES-256-CBC",
+	cipherFunc: aes.NewCipher,
+	keySize:    32,
+	blockSize:  aes.BlockSize,
+}}
  
 // deriveKey uses a key derivation function to stretch the password into a key
 @@ -51,7 +92,6 @@ func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
 		digest = hash.Sum(digest[:0])
 		copy(out[i:], digest)
 	}
-
 	return out
 }
  
@@ -81,16 +121,16 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
 	}
  
 	mode, hexIV := dek[:idx], dek[idx+1:]
-	ciph, ok := rfc1423Algos[mode]
-	if !ok {
+	ciph := cipherByName(mode)
+	if ciph == nil {
 		return nil, errors.New("x509: unknown encryption mode")
 	}
 	iv, err := hex.DecodeString(hexIV)
 	if err != nil {
 		return nil, err
 	}
-	if len(iv) < 8 {
-		return nil, errors.New("x509: not enough bytes in IV")
+	if len(iv) != ciph.blockSize {
+		return nil, errors.New("x509: incorrect IV size")
 	}
  
 	// Based on the OpenSSL implementation. The salt is the first 8 bytes
@@ -112,15 +152,14 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
 	//	[x y 7 7 7 7 7 7 7]
 	// If we detect a bad padding, we assume it is an invalid password.
 	dlen := len(data)
-	blockSize := block.BlockSize()
-	if dlen == 0 || dlen%blockSize != 0 {
+	if dlen == 0 || dlen%ciph.blockSize != 0 {
 		return nil, errors.New("x509: invalid padding")
 	}
 	last := int(data[dlen-1])
 	if dlen < last {
 		return nil, IncorrectPasswordError
 	}
-	if last == 0 || last > blockSize {
+	if last == 0 || last > ciph.blockSize {
 		return nil, IncorrectPasswordError
 	}
 	for _, val := range data[dlen-last:] {
@@ -130,3 +169,65 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
 	}
 	return data[:dlen-last], nil
 }
+
+// EncryptPEMBlock returns a PEM block of the specified type holding the
+// given DER-encoded data encrypted with the specified algorithm and
+// password.
+func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
+	ciph := cipherByKey(alg)
+	if ciph == nil {
+		return nil, errors.New("x509: unknown encryption mode")
+	}
+	iv := make([]byte, ciph.blockSize)
+	if _, err := io.ReadFull(rand, iv); err != nil {
+		return nil, errors.New("x509: cannot generate IV: " + err.Error())
+	}
+	// The salt is the first 8 bytes of the initialization vector,
+	// matching the key derivation in DecryptPEMBlock.
+	key := ciph.deriveKey(password, iv[:8])
+	block, err := ciph.cipherFunc(key)
+	if err != nil {
+		return nil, err
+	}
+	enc := cipher.NewCBCEncrypter(block, iv)
+	pad := ciph.blockSize - len(data)%ciph.blockSize
+	encrypted := make([]byte, len(data), len(data)+pad)
+	// We could save this copy by encrypting all the whole blocks in
+	// the data separately, but it doesn't seem worth the additional
+	// code.
+	copy(encrypted, data)
+	// See RFC 1423, section 1.1
+	for i := 0; i < pad; i++ {
+		encrypted = append(encrypted, byte(pad))
+	}
+	enc.CryptBlocks(encrypted, encrypted)
+
+	return &pem.Block{
+		Type: blockType,
+		Headers: map[string]string{
+			"Proc-Type": "4,ENCRYPTED",
+			"DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
+		},
+		Bytes: encrypted,
+	}, nil
+}
+
+func cipherByName(name string) *rfc1423Algo {
+	for i := range rfc1423Algos {
+		alg := &rfc1423Algos[i]
+		if alg.name == name {
+			return alg
+		}
+	}
+	return nil
+}
+
+func cipherByKey(key PEMCipher) *rfc1423Algo {
+	for i := range rfc1423Algos {
+		alg := &rfc1423Algos[i]
+		if alg.cipher == key {
+			return alg
+		}
+	}
+	return nil
+}

src/pkg/crypto/x509/pem_decrypt_test.go

--- a/src/pkg/crypto/x509/pem_decrypt_test.go
+++ b/src/pkg/crypto/x509/pem_decrypt_test.go
@@ -5,34 +5,79 @@
 package x509
  
 import (
+	"bytes"
+	"crypto/rand"
+	"encoding/base64"
 	"encoding/pem"
 	"testing"
 )
  
 func TestDecrypt(t *testing.T) {
-	for _, data := range testData {
+	for i, data := range testData {
+		t.Logf("test %d. %s", i, data.kind)
 		block, rest := pem.Decode(data.pemData)
 		if len(rest) > 0 {
-			t.Error(data.kind, "extra data")
+			t.Error("extra data")
 		}
 		der, err := DecryptPEMBlock(block, data.password)
 		if err != nil {
-			t.Error(data.kind, err)
+			t.Error("decrypt failed: ", err)
 			continue
 		}
 		if _, err := ParsePKCS1PrivateKey(der); err != nil {
-			t.Error(data.kind, "Invalid private key")
+			t.Error("invalid private key: ", err)
+		}
+		plainDER, err := base64.StdEncoding.DecodeString(data.plainDER)
+		if err != nil {
+			t.Fatal("cannot decode test DER data: ", err)
+		}
+		if !bytes.Equal(der, plainDER) {
+			t.Error("data mismatch")
 		}
 	}
 }
  
+func TestEncrypt(t *testing.T) {
+	for i, data := range testData {
+		t.Logf("test %d. %s", i, data.kind)
+		plainDER, err := base64.StdEncoding.DecodeString(data.plainDER)
+		if err != nil {
+			t.Fatal("cannot decode test DER data: ", err)
+		}
+		password := []byte("kremvax1")
+		block, err := EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", plainDER, password, data.kind)
+		if err != nil {
+			t.Error("encrypt: ", err)
+			continue
+		}
+		if !IsEncryptedPEMBlock(block) {
+			t.Error("PEM block does not appear to be encrypted")
+		}
+		if block.Type != "RSA PRIVATE KEY" {
+			t.Errorf("unexpected block type; got %q want %q", block.Type, "RSA PRIVATE KEY")
+		}
+		if block.Headers["Proc-Type"] != "4,ENCRYPTED" {
+			t.Errorf("block does not have correct Proc-Type header")
+		}
+		der, err := DecryptPEMBlock(block, password)
+		if err != nil {
+			t.Error("decrypt: ", err)
+			continue
+		}
+		if !bytes.Equal(der, plainDER) {
+			t.Errorf("data mismatch")
+		}
+	}
+}
+
 var testData = []struct {
-	kind     string
+	kind     PEMCipher
 	password []byte
 	pemData  []byte
+	plainDER string
 }{
 	{
-		kind:     "DES-CBC",
+		kind:     PEMCipherDES,
 		password: []byte("asdf"),
 		pemData: []byte(`
  -----BEGIN RSA PRIVATE KEY-----
@@ -47,9 +92,17 @@ XOH9VfTjb52q/I8Suozq9coVQwg4tXfIoYUdT//O+mB7zJb9HI9Ps77b9TxDE6Gm
  4C9brwZ3zg2vqXcwwV6QRZMtyll9rOpxkbw6NPlpfBqkc3xS51bbxivbO/Nve4KD
  r12ymjFNF4stXCfJnNqKoZ50BHmEEUDu5Wb0fpVn82XrGw7CYc4iug==
  -----END RSA PRIVATE KEY-----`),
+		plainDER: `
+MIIBPAIBAAJBAPASZe+tCPU6p80AjHhDkVsLYa51D35e/YGa8QcZyooeZM8EHozo
+KD0fNiKI+53bHdy07N+81VQ8/ejPcRoXPlsCAwEAAQJBAMTxIuSq27VpR+zZ7WJf
+c6fvv1OBvpMZ0/d1pxL/KnOAgq2rD5hDtk9b0LGhTPgQAmrrMTKuSeGoIuYE+gKQ
+QvkCIQD+GC1m+/do+QRurr0uo46Kx1LzLeSCrjBk34wiOp2+dwIhAPHfTLRXS2fv
+7rljm0bYa4+eDZpz+E8RcXEgzhhvcQQ9AiAI5eHZJGOyml3MXnQjiPi55WcDOw0w
+glcRgT6QCEtz2wIhANSyqaFtosIkHKqrDUGfz/bb5tqMYTAnBruVPaf/WEOBAiEA
+9xORWeRG1tRpso4+dYy4KdDkuLPIO01KY6neYGm3BCM=`,
 	},
 	{
-		kind:     "DES-EDE3-CBC",
+		kind:     PEMCipher3DES,
 		password: []byte("asdf"),
 		pemData: []byte(`
  -----BEGIN RSA PRIVATE KEY-----
@@ -64,9 +117,17 @@ ldw5w7WC7d13x2LsRkwo8ZrDKgIV+Y9GNvhuCCkTzNP0V3gNeJpd201HZHR+9n3w
  3z0VjR/MGqsfcy1ziEWMNOO53At3zlG6zP05aHMnMcZoVXadEK6L1gz++inSSDCq
  gI0UJP4e3JVB7AkgYymYAwiYALAkoEIuanxoc50njJk=\
  -----END RSA PRIVATE KEY-----`),
+		plainDER: `
+MIIBOwIBAAJBANOCXKdoNS/iP/MAbl9cf1/SF3P+Ns7ZeNL27CfmDh0O6Zduaax5
+NBiumd2PmjkaCu7lQ5JOibHfWn+xJsc3kw0CAwEAAQJANX/W8d1Q/sCqzkuAn4xl
+B5a7qfJWaLHndu1QRLNTRJPn0Ee7OKJ4H0QKOhQM6vpjRrz+P2u9thn6wUxoPsef
+QQIhAP/jCkfejFcy4v15beqKzwz08/tslVjF+Yq41eJGejmxAiEA05pMoqfkyjcx
+fyvGhpoOyoCp71vSGUfR2I9CR65oKh0CIC1Msjs66LlfJtQctRq6bCEtFCxEcsP+
+eEjYo/Sk6WphAiEAxpgWPMJeU/shFT28gS+tmhjPZLpEoT1qkVlC14u0b3ECIQDX
+tZZZxCtPAm7shftEib0VU77Lk8MsXJcx2C4voRsjEw==`,
 	},
 	{
-		kind:     "AES-128-CBC",
+		kind:     PEMCipherAES128,
 		password: []byte("asdf"),
 		pemData: []byte(`
  -----BEGIN RSA PRIVATE KEY-----
@@ -81,9 +142,17 @@ GZbBpf1jDH/pr0iGonuAdl2PCCZUiy+8eLsD2tyviHUkFLOB+ykYoJ5t8ngZ/B6D
  080LzLHPCrXKdlr/f50yhNWq08ZxMWQFkui+FDHPDUaEELKAXV8/5PDxw80Rtybo
  AVYoCVIbZXZCuCO81op8UcOgEpTtyU5Lgh3Mw5scQL0=\
  -----END RSA PRIVATE KEY-----`),
+		plainDER: `
+MIIBOgIBAAJBAMBlj5FxYtqbcy8wY89d/S7n0+r5MzD9F63BA/Lpl78vQKtdJ5dT
+cDGh/rBt1ufRrNp0WihcmZi7Mpl/3jHjiWECAwEAAQJABNOHYnKhtDIqFYj1OAJ3
+k3GlU0OlERmIOoeY/cL2V4lgwllPBEs7r134AY4wMmZSBUj8UR/O4SNO668ElKPE
+cQIhAOuqY7/115x5KCdGDMWi+jNaMxIvI4ETGwV40ykGzqlzAiEA0P9oEC3m9tHB
+kbpjSTxaNkrXxDgdEOZz8X0uOUUwHNsCIAwzcSCiGLyYJTULUmP1ESERfW1mlV78
+XzzESaJpIM/zAiBQkSTcl9VhcJreQqvjn5BnPZLP4ZHS4gPwJAGdsj5J4QIhAOVR
+B3WlRNTXR2WsJ5JdByezg9xzdXzULqmga0OE339a`,
 	},
 	{
-		kind:     "AES-192-CBC",
+		kind:     PEMCipherAES192,
 		password: []byte("asdf"),
 		pemData: []byte(`
  -----BEGIN RSA PRIVATE KEY-----
@@ -98,9 +167,17 @@ ReUtTw8exmKsY4gsSjhkg5uiw7/ZB1Ihto0qnfQJgjGc680qGkT1d6JfvOfeYAk6
  xn5RqS/h8rYAYm64KnepfC9vIujo4NqpaREDmaLdX5MJPQ+SlytITQvgUsUq3q/t
  Ss85xjQEZH3hzwjQqdJvmA4hYP6SUjxYpBM+02xZ1Xw=\
  -----END RSA PRIVATE KEY-----`),
+		plainDER: `
+MIIBOwIBAAJBAMGcRrZiNNmtF20zyS6MQ7pdGx17aFDl+lTl+qnLuJRUCMUG05xs
+OmxmL/O1Qlf+bnqR8Bgg65SfKg21SYuLhiMCAwEAAQJBAL94uuHyO4wux2VC+qpj
+IzPykjdU7XRcDHbbvksf4xokSeUFjjD3PB0Qa83M94y89ZfdILIqS9x5EgSB4/lX
+qNkCIQD6cCIqLfzq/lYbZbQgAAjpBXeQVYsbvVtJrPrXJAlVVQIhAMXpDKMeFPMn
+J0g2rbx1gngx0qOa5r5iMU5w/noN4W2XAiBjf+WzCG5yFvazD+dOx3TC0A8+4x3P
+uZ3pWbaXf5PNuQIgAcdXarvhelH2w2piY1g3BPeFqhzBSCK/yLGxR82KIh8CIQDD
++qGKsd09NhQ/G27y/DARzOYtml1NvdmCQAgsDIIOLA==`,
 	},
 	{
-		kind:     "AES-256-CBC",
+		kind:     PEMCipherAES256,
 		password: []byte("asdf"),
 		pemData: []byte(`
  -----BEGIN RSA PRIVATE KEY-----
@@ -115,11 +192,19 @@ Pz3RZScwIuubzTGJ1x8EzdffYOsdCa9Mtgpp3L136+23dOd6L/qK2EG2fzrJSHs/\n sv5Z/KwlX+3MDEpPQpUwGPlGGdLnjI3UZ+cjgqBcoMiNc6HfgbBgYJSU6aDSHuCk\n clCwByxWkBNgJ2GrkwNrF26v+bGJJJNR4SKouY1jQf0=\n -----END RSA PRIVATE KEY-----`),
+		plainDER: `
+MIIBOgIBAAJBAKy3GFkstoCHIEeUU/qO8207m8WSrjksR+p9B4tf1w5k+2O1V/GY
+AQ5WFCApItcOkQe/I0yZZJk/PmCqMzSxrc8CAwEAAQJAOCAz0F7AW9oNelVQSP8F
+Sfzx7O1yom+qWyAQQJF/gFR11gpf9xpVnnyu1WxIRnDUh1LZwUsjwlDYb7MB74id
+oQIhANPcOiLwOPT4sIUpRM5HG6BF1BI7L77VpyGVk8xNP7X/AiEA0LMHZtk4I+lJ
+nClgYp4Yh2JZ1Znbu7IoQMCEJCjwKDECIGd8Dzm5tViTkUW6Hs3Tlf73nNs65duF
+aRnSglss8I3pAiEAonEnKruawgD8RavDFR+fUgmQiPz4FnGGeVgfwpGG1JECIBYq
+PXHYtPqxQIbD2pScR5qum7iGUh11lEUPkmt+2uqS`,
 	},
 	{
 		// generated with:
 		// openssl genrsa -aes128 -passout pass:asdf -out server.orig.key 128
-		kind:     "AES-128-CBC",
+		kind:     PEMCipherAES128,
 		password: []byte("asdf"),
 		pemData: []byte(`
  -----BEGIN RSA PRIVATE KEY-----
@@ -130,5 +215,9 @@ DEK-Info: AES-128-CBC,74611ABC2571AF11B1BF9B69E62C89E7
  eND9l7C9meCirWovjj9QWVHrXyugFuDIqgdhQ8iHTgCfF3lrmcttVrbIfMDw+smD
  hTP8O1mS/MHl92NE0nhv0w==
  -----END RSA PRIVATE KEY-----`),
+		plainDER: `
+MGMCAQACEQC6ssxmYuauuHGOCDAI54RdAgMBAAECEQCWIn6Yv2O+kBcDF7STctKB
+AgkA8SEfu/2i3g0CCQDGNlXbBHX7kQIIK3Ww5o0cYbECCQDCimPb0dYGsQIIeQ7A
+jryIst8=`,
 	},
 }

コアとなるコードの解説

pem_decrypt.go の変更点

  • PEMCipher 型と定数の追加:
    • type PEMCipher int が定義され、PEMCipherDES から PEMCipherAES256 までの定数が追加されました。これにより、暗号アルゴリズムを型安全に指定できるようになりました。
  • rfc1423Algo 構造体の拡張:
    • cipher PEMCipher フィールドが追加され、各アルゴリズムの PEMCipher 値を保持するようになりました。
    • name string フィールドが追加され、PEMヘッダーで使用されるアルゴリズム名(例: "DES-CBC")を保持するようになりました。
    • blockSize int フィールドが追加され、各暗号アルゴリズムのブロックサイズを保持するようになりました。これは、パディング計算やIVサイズの検証に利用されます。
  • rfc1423Algos 変数の変更:
    • 以前の map から []rfc1423Algo スライスに変更されました。これにより、アルゴリズムの定義がより構造化され、新しい cipherByName および cipherByKey 関数による検索が可能になりました。
  • DecryptPEMBlock の修正:
    • ciph, ok := rfc1423Algos[mode] の代わりに ciph := cipherByName(mode) を使用するように変更されました。
    • if len(iv) < 8 のチェックが if len(iv) != ciph.blockSize に変更されました。これにより、IVのサイズが正確にブロックサイズと一致するかどうかを検証し、不正なサイズの場合はパニックではなくエラーを返すようになりました。
    • パディングチェックの blockSizeciph.blockSize に変更され、選択されたアルゴリズムのブロックサイズが正しく使用されるようになりました。
  • EncryptPEMBlock 関数の追加:
    • この関数は、PEM形式でデータを暗号化するための主要なエントリポイントです。
    • cipherByKey(alg) を使用して、指定された PEMCipher に対応するアルゴリズム定義を取得します。
    • io.ReadFull(rand, iv) を使用して、暗号論的に安全なランダムなIVを生成します。
    • ciph.deriveKey(password, iv[:8]) でパスワードとIVの最初の8バイトから鍵を導出します。
    • cipher.NewCBCEncrypter(block, iv) でCBCモードのエンクリプタを作成します。
    • pad := ciph.blockSize - len(data)%ciph.blockSize でPKCS#7パディングの長さを計算し、データをパディングします。
    • enc.CryptBlocks(encrypted, encrypted) でデータを暗号化します。
    • 最終的に、pem.Block 構造体を構築し、Proc-TypeDEK-Info ヘッダーを設定して返します。
  • cipherByName および cipherByKey ヘルパー関数の追加:
    • これらの関数は、rfc1423Algos スライスを走査して、それぞれアルゴリズム名または PEMCipher 値に基づいて rfc1423Algo 構造体を見つけるために使用されます。

pem_decrypt_test.go の変更点

  • TestEncrypt 関数の追加:
    • EncryptPEMBlock 関数の動作を検証するための新しいテストケースが追加されました。
    • 既存の testData を利用して、各暗号アルゴリズムでデータを暗号化し、その後 DecryptPEMBlock で復号化して、元のデータと一致するかどうかを確認します。
    • IsEncryptedPEMBlock やPEMブロックのヘッダーが正しく設定されているかも検証します。
  • testData 構造体の変更:
    • kind フィールドが string から新しい PEMCipher 型に変更されました。
    • plainDER string フィールドが追加され、テストで使用する平文のDERエンコードされたデータ(Base64エンコード済み)を保持するようになりました。これにより、暗号化と復号化のテストで元のデータとの比較が容易になります。
  • TestDecrypt の修正:
    • t.Logf を使用して、どのテストデータが実行されているかをログに出力するようになりました。
    • plainDER フィールドを使用して、復号化されたデータが元の平文データと一致するかどうかを bytes.Equal で検証するようになりました。

これらの変更により、PEM形式の暗号化機能がGo言語の標準ライブラリに統合され、より安全で堅牢な鍵管理が可能になりました。

関連リンク

参考にした情報源リンク