メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

挙動変更

PrivateKey.Signは非nilのSignerOptsが渡された場合にハッシュ長の一致を検証するようになった

crypto/ecdsa

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

crypto/ecdsaPrivateKey.Sign は、非nilの SignerOpts が渡された場合、digest の長さが opts.HashFunc().Size() と一致するかを検証するようになった。一致しない場合は "ecdsa: hash length does not match hash function" というエラーを返す。乱数源(random)が nil のとき(RFC 6979による決定的署名)は以前からこの検証が行われていたが、今回の変更で通常の署名経路(random が非nil)にも同じ検証が揃えられた。

使用例

Before

package main

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
)

func main() {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}

	digest := sha256.Sum256([]byte("hello, world"))

	// digestは32バイト(SHA-256)だが、optsはSHA-1(20バイト)を指定しており長さが一致しない。
	// 従来の通常署名経路(randomが非nil)ではこの不一致を検出せず、そのまま署名処理が進んでいた。
	sig, err := priv.Sign(rand.Reader, digest[:], crypto.SHA1)
	fmt.Println(len(sig), err)
}

After

package main

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
)

func main() {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}

	digest := sha256.Sum256([]byte("hello, world"))

	// digestの長さとoptsが示すハッシュ関数のサイズを一致させる。
	sig, err := priv.Sign(rand.Reader, digest[:], crypto.SHA256)
	if err != nil {
		panic(err)
	}
	fmt.Println(len(sig))
}

移行時の注意

crypto.Signer を直接実装・呼び出しているコードで、digest の長さと SignerOpts が示すハッシュ関数の出力サイズが一致しない呼び出しを行っている場合、この変更以降はエラーが返るようになる。これまでは不一致があっても署名処理がそのまま進んでいたため、誤ったハッシュ関数用の SignerOpts を渡しているコードがあれば、この変更によって初めて顕在化する可能性がある。呼び出し側は digest の長さを opts.HashFunc() が対応するハッシュ関数の出力長に合わせる必要がある。なお optsnil の場合は従来通り検証されない。

実装解説

検証は PrivateKey.Sign 内の opts != nil 分岐(該当行)に追加されている。randomnil の場合に呼ばれる決定的署名の内部関数 signRFC6979 には同種のハッシュ長検証が既に存在しており、今回の変更で通常の乱数ベース署名経路にも同じ検証ロジックが揃えられた。

関連リンク