[インデックス 11062] ファイルの概要
このコミットは、Go言語のcrypto/openpgp
パッケージにおけるDSA署名の検証と生成に関する重要な修正を導入しています。具体的には、DSA署名においてハッシュ値の切り詰め(truncation)が適切に行われるように変更されました。これにより、SHA-1よりも長いハッシュ関数(例: SHA-512)が使用された場合でも、OpenPGPのDSA署名がFIPS 186-3標準に準拠して正しく処理されるようになります。
コミット
crypto/openpgp: truncate hashes before checking DSA signatures.
I didn't believe that OpenPGP allowed > SHA-1 with DSA, but it does and
so we need to perform hash truncation.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5510044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f9427364951a1e4b2ef6ad7d0ec9a6dfbb2dee54
元コミット内容
commit f9427364951a1e4b2ef6ad7d0ec9a6dfbb2dee54
Author: Adam Langley <agl@golang.org>
Date: Mon Jan 9 16:57:51 2012 -0500
crypto/openpgp: truncate hashes before checking DSA signatures.
I didn't believe that OpenPGP allowed > SHA-1 with DSA, but it does and
so we need to perform hash truncation.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5510044
--
src/pkg/crypto/openpgp/packet/public_key.go | 5 +++++
src/pkg/crypto/openpgp/packet/signature.go | 9 ++++++++-\n src/pkg/crypto/openpgp/read_test.go | 12 ++++++++++++\n src/pkg/crypto/openpgp/write_test.go | 2 +-\n 4 files changed, 26 insertions(+), 2 deletions(-)\n\ndiff --git a/src/pkg/crypto/openpgp/packet/public_key.go b/src/pkg/crypto/openpgp/packet/public_key.go\nindex 9aa30e0c15..28d7d5420d 100644\n--- a/src/pkg/crypto/openpgp/packet/public_key.go\n+++ b/src/pkg/crypto/openpgp/packet/public_key.go\n@@ -291,6 +291,11 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro\n \t\treturn nil\n \tcase PubKeyAlgoDSA:\n \t\tdsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)\n+\t\t// Need to truncate hashBytes to match FIPS 186-3 section 4.6.\n+\t\tsubgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8\n+\t\tif len(hashBytes) > subgroupSize {\n+\t\t\thashBytes = hashBytes[:subgroupSize]\n+\t\t}\n \t\tif !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {\n \t\t\treturn error_.SignatureError(\"DSA verification failure\")\n \t\t}\ndiff --git a/src/pkg/crypto/openpgp/packet/signature.go b/src/pkg/crypto/openpgp/packet/signature.go\nindex 1cdc1ee0f0..d32b12b1ab 100644\n--- a/src/pkg/crypto/openpgp/packet/signature.go\n+++ b/src/pkg/crypto/openpgp/packet/signature.go\n@@ -443,7 +443,14 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) {\n \t\tsig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand.Reader, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)\n \t\tsig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))\n \tcase PubKeyAlgoDSA:\n-\t\tr, s, err := dsa.Sign(rand.Reader, priv.PrivateKey.(*dsa.PrivateKey), digest)\n+\t\tdsaPriv := priv.PrivateKey.(*dsa.PrivateKey)\n+\n+\t\t// Need to truncate hashBytes to match FIPS 186-3 section 4.6.\n+\t\tsubgroupSize := (dsaPriv.Q.BitLen() + 7) / 8\n+\t\tif len(digest) > subgroupSize {\n+\t\t\tdigest = digest[:subgroupSize]\n+\t\t}\n+\t\tr, s, err := dsa.Sign(rand.Reader, dsaPriv, digest)\n \t\tif err == nil {\n \t\t\tsig.DSASigR.bytes = r.Bytes()\n \t\t\tsig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))\ndiff --git a/src/pkg/crypto/openpgp/read_test.go b/src/pkg/crypto/openpgp/read_test.go\nindex e8a6bf5992..1be900b157 100644\n--- a/src/pkg/crypto/openpgp/read_test.go\n+++ b/src/pkg/crypto/openpgp/read_test.go\n@@ -7,6 +7,7 @@ package openpgp\n import (\n \t\"bytes\"\n \terror_ \"crypto/openpgp/error\"\n+\t_ \"crypto/sha512\"\n \t\"encoding/hex\"\n \t\"io\"\n \t\"io/ioutil\"\n@@ -77,6 +78,15 @@ func TestReadDSAKey(t *testing.T) {\n \t}\n }\n \n+func TestDSAHashTruncatation(t *testing.T) {\n+\t// dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo\n+\t// SHA512 in order to require DSA hash truncation to verify correctly.\n+\t_, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512))\n+\tif err != nil {\n+\t\tt.Error(err)\n+\t}\n+}\n+\n func TestGetKeyById(t *testing.T) {\n \tkring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))\n \n@@ -358,3 +368,5 @@ AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL\n VrM0m72/jnpKo04=\n =zNCn\n -----END PGP PRIVATE KEY BLOCK-----`\n+\n+const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003`\ndiff --git a/src/pkg/crypto/openpgp/write_test.go b/src/pkg/crypto/openpgp/write_test.go\nindex 02fa5b75bf..7df02e7bd1 100644\n--- a/src/pkg/crypto/openpgp/write_test.go\n+++ b/src/pkg/crypto/openpgp/write_test.go\n@@ -222,7 +222,7 @@ func TestEncryption(t *testing.T) {\n \n \t\tif test.isSigned {\n \t\t\tif md.SignatureError != nil {\n-\t\t\t\tt.Errorf(\"#%d: signature error: %s\", i, err)\n+\t\t\t\tt.Errorf(\"#%d: signature error: %s\", i, md.SignatureError)\n \t\t\t}\n \t\t\tif md.Signature == nil {\n \t\t\t\tt.Error(\"signature missing\")\n```
## 変更の背景
このコミットの背景には、OpenPGPにおけるDSA署名の扱いに関する誤解と、それによって生じる潜在的な互換性の問題がありました。当初、コミットの作者はOpenPGPがDSA署名にSHA-1よりも長いハッシュ関数(例: SHA-256, SHA-512)の使用を許可していないと考えていました。しかし、実際にはOpenPGPはより長いハッシュ関数をDSAと組み合わせて使用することを許可しており、その場合、デジタル署名標準(DSS)であるFIPS 186-3の規定に従ってハッシュ値を適切に切り詰める(truncate)必要がありました。
この切り詰めが行われないと、SHA-512のような長いハッシュ関数で生成されたDSA署名が、Goの`crypto/openpgp`パッケージで正しく検証できない、あるいは正しく生成できないという問題が発生します。この修正は、OpenPGPの仕様とFIPS 186-3の要件に準拠し、より広範なハッシュアルゴリズムとの互換性を確保するために導入されました。
## 前提知識の解説
### OpenPGP (Open Pretty Good Privacy)
OpenPGPは、電子メールの暗号化、デジタル署名、およびデータの完全性保護のための標準規格です。RFC 4880で定義されており、GnuPG (GNU Privacy Guard) などの多くの実装が存在します。OpenPGPは公開鍵暗号方式を利用し、データの機密性、認証、非否認性を提供します。
### DSA (Digital Signature Algorithm)
DSAは、米国国立標準技術研究所(NIST)によって開発されたデジタル署名アルゴリズムです。FIPS 186(Digital Signature Standard, DSS)の一部として標準化されています。DSAは、公開鍵暗号の原理に基づき、メッセージの認証と完全性を保証するために使用されます。署名生成と検証のプロセスには、大きな素数やモジュラー算術が関与します。
### ハッシュ関数 (Hash Function)
ハッシュ関数は、任意の長さの入力データ(メッセージ)を受け取り、固定長の短い出力(ハッシュ値、メッセージダイジェスト、または単にダイジェスト)を生成する一方向性の関数です。デジタル署名においては、署名対象のメッセージ全体ではなく、そのハッシュ値に対して署名が行われます。これにより、署名処理の効率が向上し、メッセージのわずかな変更でもハッシュ値が大きく変わるため、改ざんを検出できます。SHA-1、SHA-256、SHA-512などが代表的なハッシュ関数です。
### FIPS 186-3 (Digital Signature Standard)
FIPS 186-3は、米国連邦情報処理標準(FIPS)の一部であり、デジタル署名アルゴリズム(DSA、RSA、ECDSA)に関する仕様を定めています。この標準は、デジタル署名の生成と検証のプロセス、および関連するパラメータの選択に関するガイドラインを提供します。
特に、**FIPS 186-3のセクション4.6**は、DSAにおけるハッシュ値の切り詰め(truncation)について規定しています。このセクションによると、DSA署名に使用されるハッシュ関数の出力長が、DSAのパラメータである`q`(サブグループの位数)のビット長よりも長い場合、ハッシュ値の左端から`q`のビット長に相当する部分のみを使用する必要があります。例えば、`q`が160ビット(SHA-1の出力長と同じ)であるDSA鍵でSHA-512(512ビット出力)を使用する場合、SHA-512の出力の最初の160ビットのみがDSA署名計算に使用されます。この切り詰めは、DSAアルゴリズムの数学的要件を満たすために不可欠です。
### ハッシュ値の切り詰め (Hash Truncation)
デジタル署名アルゴリズム、特にDSAでは、署名計算に使用されるハッシュ値の長さが、鍵の特定のパラメータ(DSAの場合は`q`のビット長)と一致する必要があります。ハッシュ関数の出力がこの必要な長さよりも長い場合、余分なビットを破棄して、必要な長さに切り詰めるプロセスが「ハッシュ値の切り詰め」です。これはセキュリティを損なうものではなく、アルゴリズムの仕様に準拠するための必須ステップです。
## 技術的詳細
このコミットが解決しようとしている技術的な問題は、Goの`crypto/openpgp`パッケージが、DSA署名においてハッシュ値の切り詰めを適切に処理していなかった点にあります。
DSA署名では、署名対象のメッセージのハッシュ値が計算されます。このハッシュ値は、DSAの署名生成および検証プロセスにおいて、鍵のパラメータである`q`(サブグループの位数)のビット長に適合する必要があります。FIPS 186-3のセクション4.6で明確に規定されているように、ハッシュ関数の出力が`q`のビット長を超える場合、ハッシュ値は`q`のビット長に合わせて切り詰められなければなりません。
従来のGoの`crypto/openpgp`の実装では、この切り詰めが考慮されていませんでした。これは、SHA-1(160ビット出力)がDSAの一般的な`q`のビット長(160ビット)と一致していたため、問題が顕在化しにくかったためと考えられます。しかし、OpenPGPがSHA-256やSHA-512といったより長いハッシュ関数をDSAと共に使用することを許可しているため、これらのハッシュ関数が使用された場合に、ハッシュ値が切り詰められずにそのままDSAの署名関数に渡されていました。
その結果、以下のような問題が発生していました。
1. **署名検証の失敗**: SHA-512などの長いハッシュ関数で生成されたDSA署名が、Goの実装で検証に失敗する。これは、署名生成側がFIPS 186-3に従ってハッシュを切り詰めているのに対し、Goの検証側が切り詰めずにハッシュを比較しようとするため、不一致が生じるためです。
2. **署名生成の不正確さ**: Goの実装でDSA署名を生成する際に、長いハッシュ値が切り詰められずに使用されると、生成される署名が他のOpenPGP実装と互換性がなくなる可能性があります。
このコミットは、署名生成時と検証時の両方で、FIPS 186-3セクション4.6に従ってハッシュ値を`q`のビット長に切り詰めるロジックを追加することで、この問題を解決しています。これにより、Goの`crypto/openpgp`パッケージは、より広範なハッシュアルゴリズムとDSAの組み合わせに対応し、他のOpenPGP実装との相互運用性が向上します。
## コアとなるコードの変更箇所
このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。
1. `src/pkg/crypto/openpgp/packet/public_key.go`
* `PublicKey`構造体の`VerifySignature`メソッド内、`PubKeyAlgoDSA`ケース。
* DSA署名検証時にハッシュ値を切り詰めるロジックが追加されました。
2. `src/pkg/crypto/openpgp/packet/signature.go`
* `Signature`構造体の`Sign`メソッド内、`PubKeyAlgoDSA`ケース。
* DSA署名生成時にハッシュ値を切り詰めるロジックが追加されました。
3. `src/pkg/crypto/openpgp/read_test.go`
* `TestDSAHashTruncatation`という新しいテスト関数が追加されました。
* SHA-512で生成されたDSA鍵リングを使用して、ハッシュ切り詰めが正しく機能するかを検証します。
* `crypto/sha512`パッケージがインポートされました。
4. `src/pkg/crypto/openpgp/write_test.go`
* `TestEncryption`関数内のエラーメッセージのフォーマットが修正されました。これは本質的な変更ではありません。
## コアとなるコードの解説
### `src/pkg/crypto/openpgp/packet/public_key.go` の変更
`PublicKey.VerifySignature`メソッドは、与えられた署名が公開鍵によって検証可能であるかをチェックします。DSA鍵の場合、以下のコードが追加されました。
```go
dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
if len(hashBytes) > subgroupSize {
hashBytes = hashBytes[:subgroupSize]
}
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
return error_.SignatureError("DSA verification failure")
}
dsaPublicKey.Q.BitLen()
: DSA公開鍵のパラメータQ
のビット長を取得します。Q
はDSAのサブグループの位数を定義する素数であり、ハッシュ値はこのビット長に合わせる必要があります。(dsaPublicKey.Q.BitLen() + 7) / 8
: ビット長をバイト長に変換する計算です。例えば、160ビットは20バイトになります。この計算は、ビット長が8の倍数でない場合でも正しく切り上げを行うための一般的な方法です。if len(hashBytes) > subgroupSize
: 計算されたsubgroupSize
(バイト単位)よりも現在のhashBytes
の長さが長い場合、切り詰めが必要であることを示します。hashBytes = hashBytes[:subgroupSize]
:hashBytes
スライスをsubgroupSize
の長さに切り詰めます。これにより、FIPS 186-3セクション4.6の要件が満たされます。
この変更により、検証プロセスに渡されるハッシュ値が常にDSAアルゴリズムの要件に合致するようになり、SHA-512のような長いハッシュ関数で生成された署名も正しく検証できるようになります。
src/pkg/crypto/openpgp/packet/signature.go
の変更
Signature.Sign
メソッドは、メッセージの署名を生成します。DSA鍵の場合、以下のコードが追加されました。
dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
if len(digest) > subgroupSize {
digest = digest[:subgroupSize]
}
r, s, err := dsa.Sign(rand.Reader, dsaPriv, digest)
dsaPriv.Q.BitLen()
: 署名生成に使用されるDSA秘密鍵のパラメータQ
のビット長を取得します。(dsaPriv.Q.BitLen() + 7) / 8
: 同様に、ビット長をバイト長に変換します。if len(digest) > subgroupSize
: 計算されたsubgroupSize
よりも現在のdigest
(ハッシュ値)の長さが長い場合、切り詰めが必要であることを示します。digest = digest[:subgroupSize]
:digest
スライスをsubgroupSize
の長さに切り詰めます。
この変更により、署名生成時に使用されるハッシュ値もFIPS 186-3の要件に従って切り詰められるため、生成されるDSA署名が標準に準拠し、他のOpenPGP実装との互換性が確保されます。
src/pkg/crypto/openpgp/read_test.go
の変更
新しいテストケースTestDSAHashTruncatation
が追加され、SHA-512ハッシュアルゴリズムを使用して生成されたDSA鍵リングが正しく読み取られ、検証されることを確認します。これは、GnuPGで--cert-digest-algo SHA512
オプションを使用して生成された鍵リングを使用しており、ハッシュ切り詰めロジックが正しく機能していることを実証するためのものです。
func TestDSAHashTruncatation(t *testing.T) {
// dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo
// SHA512 in order to require DSA hash truncation to verify correctly.
_, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512))
if err != nil {
t.Error(err)
}
}
また、このテストでSHA-512を使用するために、_ "crypto/sha512"
がインポートされています。
これらの変更は、Goのcrypto/openpgp
パッケージがDSA署名に関してより堅牢で標準準拠したものになることを保証します。
関連リンク
- Go CL 5510044: https://golang.org/cl/5510044
参考にした情報源リンク
- FIPS 186-3, Digital Signature Standard (DSS), Section 4.6: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-3.pdf (NISTの公式ドキュメント)
- DSA (Digital Signature Algorithm) - Wikipedia: https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E7%BD%B2%E5%90%8D%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0
- OpenPGP - Wikipedia: https://ja.wikipedia.org/wiki/OpenPGP
- Hash function - Wikipedia: https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0