Go 语言标准库 —— crypto/ecdsa 包(椭圆曲线数字签名算法)
🔹 概述
crypto/ecdsa 包实现了椭圆曲线数字签名算法(ECDSA),定义在 FIPS 186-5 标准中。
主要功能:
- ECDSA 数字签名生成
- ECDSA 签名验证
- 支持 NIST P-224、P-256、P-384、P-521 曲线
- 与 crypto/ecdh 互操作
- 常量时间实现(防侧信道攻击)
重要说明:
- ✅ ECDSA 是现代数字签名的标准
- 🔒 用于证书签名、代码签名、区块链等
- 🔑 私钥签名,公钥验证
- 📦 签名是随机的(每次不同)
- ⚠️ 需要先对消息进行哈希
支持的曲线:
- P-224 - 轻量级应用
- P-256 (secp256r1) - 常用,推荐
- P-384 (secp384r1) - 更高安全性
- P-521 (secp521r1) - 最高安全性
核心概念:
- 私钥(PrivateKey) - 用于签名,必须保密
- 公钥(PublicKey) - 用于验证签名,可以公开
- 签名(Signature) - 由 (r, s) 组成
- 哈希(Hash) - 消息的摘要(SHA-256 等)
🔹 核心类型
PublicKey 类型
type PublicKey struct {
elliptic.Curve
X, Y *big.Int
}
-
说明:
- 表示 ECDSA 公钥
- 包含曲线参数和椭圆曲线上的点 (X, Y)
-
字段:
Curve elliptic.Curve- 椭圆曲线X *big.Int- X 坐标Y *big.Int- Y 坐标
-
方法:
Bytes() ([]byte, error)- 返回公钥的字节编码ECDH() (*ecdh.PublicKey, error)- 转换为 ECDH 公钥Equal(x crypto.PublicKey) bool- 比较公钥
-
注意事项:
- ✅ 公钥可以公开分享
- ⚠️ 需要验证公钥的真实性
- ✅ 可编码为 DER/PEM 格式
PrivateKey 类型
type PrivateKey struct {
PublicKey
D *big.Int
}
-
说明:
- 表示 ECDSA 私钥
- 嵌入 PublicKey,包含公钥信息
-
字段:
PublicKey- 嵌入的公钥D *big.Int- 私钥标量(保密)
-
方法:
Bytes() ([]byte, error)- 返回私钥的字节编码ECDH() (*ecdh.PrivateKey, error)- 转换为 ECDH 私钥Equal(x crypto.PrivateKey) bool- 比较私钥Public() crypto.PublicKey- 获取公钥Sign(...) ([]byte, error)- 实现 crypto.Signer 接口
-
注意事项:
- ⚠️ 私钥必须严格保密
- ✅ 可安全存储(需加密)
- ✅ 可与 X.509 证书互操作
🔹 核心函数
GenerateKey - 生成密钥对
ecdsa.GenerateKey(c elliptic.Curve, r io.Reader) (*PrivateKey, error)
-
说明:
- 生成随机的 ECDSA 密钥对
- 使用加密安全的随机数生成器
-
参数:
c elliptic.Curve- 椭圆曲线(如 elliptic.P256())r io.Reader- 随机数源(使用 crypto/rand.Reader)
-
返回值:
*PrivateKey- 私钥error- 错误信息
-
示例(完整):
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/hex" "fmt" ) func main() { // 生成 P-256 密钥对 privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { fmt.Println("错误:", err) return } // 获取公钥 publicKey := &privateKey.PublicKey // 输出密钥信息 fmt.Printf("曲线:%s\n", privateKey.Curve.Params().Name) fmt.Printf("私钥 D: %s\n", privateKey.D.Text(16)) fmt.Printf("公钥 X: %s\n", publicKey.X.Text(16)) fmt.Printf("公钥 Y: %s\n", publicKey.Y.Text(16)) // 字节编码 privateBytes, _ := privateKey.Bytes() publicBytes, _ := publicKey.Bytes() fmt.Printf("私钥字节:%s\n", hex.EncodeToString(privateBytes)) fmt.Printf("公钥字节:%s\n", hex.EncodeToString(publicBytes)) } -
注意事项:
- ✅ 必须使用
crypto/rand.Reader - ❌ 不要使用
math/rand - ✅ Go 1.26+ 强制使用安全随机源
- ✅ 必须使用
SignASN1 - 签名(推荐)
ecdsa.SignASN1(r io.Reader, priv *PrivateKey, hash []byte) ([]byte, error)
-
说明:
- 对哈希值进行签名
- 返回 ASN.1 DER 编码的签名
- ✅ 推荐使用此函数
-
参数:
r io.Reader- 随机数源(Go 1.26+ 可忽略)priv *PrivateKey- 私钥hash []byte- 消息的哈希值
-
返回值:
[]byte- ASN.1 DER 编码的签名error- 错误信息
-
示例(完整签名流程):
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" ) func main() { // 生成密钥对 privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // 准备消息 message := []byte("Hello, ECDSA!") // 计算消息哈希 hash := sha256.Sum256(message) // 签名 signature, err := ecdsa.SignASN1(rand.Reader, privateKey, hash[:]) if err != nil { fmt.Println("签名失败:", err) return } fmt.Printf("消息:%s\n", string(message)) fmt.Printf("哈希:%s\n", hex.EncodeToString(hash[:])) fmt.Printf("签名:%s\n", hex.EncodeToString(signature)) } -
注意事项:
- ⚠️ 必须先对消息进行哈希
- ✅ 签名是随机的(每次不同)
- ✅ Go 1.26+ 自动使用安全随机源
VerifyASN1 - 验证签名(推荐)
ecdsa.VerifyASN1(pub *PublicKey, hash []byte, sig []byte) bool
-
说明:
- 验证 ASN.1 DER 编码的签名
- ✅ 推荐使用此函数
-
参数:
pub *PublicKey- 公钥hash []byte- 消息的哈希值sig []byte- ASN.1 DER 编码的签名
-
返回值:
bool- 签名是否有效
-
示例(完整验证流程):
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" ) func main() { // 生成密钥对 privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) publicKey := &privateKey.PublicKey // 准备消息 message := []byte("Hello, ECDSA!") hash := sha256.Sum256(message) // 签名 signature, _ := ecdsa.SignASN1(rand.Reader, privateKey, hash[:]) fmt.Printf("签名:%s\n", hex.EncodeToString(signature)) // 验证签名 valid := ecdsa.VerifyASN1(publicKey, hash[:], signature) fmt.Printf("签名验证:%v\n", valid) // 篡改消息 tamperedMessage := []byte("Tampered message!") tamperedHash := sha256.Sum256(tamperedMessage) valid = ecdsa.VerifyASN1(publicKey, tamperedHash[:], signature) fmt.Printf("篡改后验证:%v\n", valid) } -
注意事项:
- ⚠️ 哈希必须与签名时使用相同的哈希函数
- ⚠️ 验证不保证时间常量(可能有时序攻击风险)
- ✅ 返回 false 表示签名无效
Sign - 签名(返回 r, s)
ecdsa.Sign(r io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)
-
说明:
- 对哈希值进行签名
- 返回签名的 r, s 分量
- ⚠️ 大多数应用应使用 SignASN1
-
参数:
r io.Reader- 随机数源priv *PrivateKey- 私钥hash []byte- 消息的哈希值
-
返回值:
r *big.Int- 签名 r 分量s *big.Int- 签名 s 分量error- 错误信息
-
示例:
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "fmt" ) func main() { privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) message := []byte("Hello!") hash := sha256.Sum256(message) // 签名(返回 r, s) r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash[:]) if err != nil { fmt.Println("签名失败:", err) return } fmt.Printf("r: %s\n", r.Text(16)) fmt.Printf("s: %s\n", s.Text(16)) }
Verify - 验证签名(使用 r, s)
ecdsa.Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
- 说明:
- 验证签名(使用 r, s 分量)
- ⚠️ 大多数应用应使用 VerifyASN1
- 参数:
pub *PublicKey- 公钥hash []byte- 消息的哈希值r *big.Int- 签名 r 分量s *big.Int- 签名 s 分量
- 返回值:
bool- 签名是否有效
ParseRawPrivateKey - 解析原始私钥
ecdsa.ParseRawPrivateKey(curve elliptic.Curve, data []byte) (*PrivateKey, error)
-
说明:
- 从原始字节解析私钥
- 按照 SEC 1 v2.0 标准
-
参数:
curve elliptic.Curve- 椭圆曲线data []byte- 私钥字节(固定长度,大端序)
-
返回值:
*PrivateKey- 私钥error- 错误信息
-
示例:
package main import ( "crypto/ecdsa" "crypto/elliptic" "encoding/hex" "fmt" ) func main() { // 假设已有私钥字节(32 字节,P-256) keyHex := "5745638975638576385763857638576385763857638576385763857638576385" keyBytes, _ := hex.DecodeString(keyHex) // 解析私钥 privateKey, err := ecdsa.ParseRawPrivateKey(elliptic.P256(), keyBytes) if err != nil { fmt.Println("解析失败:", err) return } fmt.Printf("私钥解析成功\n") fmt.Printf("D: %s\n", privateKey.D.Text(16)) }
ParseUncompressedPublicKey - 解析未压缩公钥
ecdsa.ParseUncompressedPublicKey(curve elliptic.Curve, data []byte) (*PublicKey, error)
-
说明:
- 从未压缩格式解析公钥
- 按照 SEC 1 v2.0 标准(X9.62 未压缩格式)
-
参数:
curve elliptic.Curve- 椭圆曲线data []byte- 公钥字节(0x04 前缀 + X + Y)
-
返回值:
*PublicKey- 公钥error- 错误信息
-
示例:
package main import ( "crypto/ecdsa" "crypto/elliptic" "encoding/hex" "fmt" ) func main() { // 未压缩公钥格式:0x04 + X(32 字节) + Y(32 字节) pubHex := "04" + "5745638975638576385763857638576385763857638576385763857638576385" + "638576385763857638576385763857638576385763857638576385763857638" pubBytes, _ := hex.DecodeString(pubHex) // 解析公钥 publicKey, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), pubBytes) if err != nil { fmt.Println("解析失败:", err) return } fmt.Printf("公钥解析成功\n") fmt.Printf("X: %s\n", publicKey.X.Text(16)) fmt.Printf("Y: %s\n", publicKey.Y.Text(16)) }
🔹 完整示例
1. 基本签名和验证
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
// 生成密钥对
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
fmt.Println("密钥生成失败:", err)
return
}
publicKey := &privateKey.PublicKey
// 准备消息
message := []byte("This is a test message")
fmt.Printf("原始消息:%s\n", string(message))
// 计算哈希
hash := sha256.Sum256(message)
fmt.Printf("消息哈希:%s\n", hex.EncodeToString(hash[:]))
// 签名
signature, err := ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
if err != nil {
fmt.Println("签名失败:", err)
return
}
fmt.Printf("签名:%s\n", hex.EncodeToString(signature))
// 验证签名
valid := ecdsa.VerifyASN1(publicKey, hash[:], signature)
fmt.Printf("签名验证结果:%v\n", valid)
// 验证失败场景(篡改消息)
tamperedMessage := []byte("Tampered message")
tamperedHash := sha256.Sum256(tamperedMessage)
valid = ecdsa.VerifyASN1(publicKey, tamperedHash[:], signature)
fmt.Printf("篡改后验证:%v\n", valid)
}
2. 使用不同曲线
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
)
func signWithCurve(curve elliptic.Curve, name string) {
fmt.Printf("\n=== %s ===\n", name)
// 生成密钥对
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
fmt.Println("错误:", err)
return
}
// 准备消息
message := []byte("Test message")
hash := sha256.Sum256(message)
// 签名
signature, err := ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
if err != nil {
fmt.Println("签名失败:", err)
return
}
// 验证
valid := ecdsa.VerifyASN1(&privateKey.PublicKey, hash[:], signature)
fmt.Printf("私钥大小:%d 位\n", privateKey.D.BitLen())
fmt.Printf("签名长度:%d 字节\n", len(signature))
fmt.Printf("验证结果:%v\n", valid)
}
func main() {
fmt.Println("ECDSA 不同曲线对比")
signWithCurve(elliptic.P224(), "P-224")
signWithCurve(elliptic.P256(), "P-256 (推荐)")
signWithCurve(elliptic.P384(), "P-384")
signWithCurve(elliptic.P521(), "P-521")
}
3. 密钥序列化与持久化
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"os"
)
// 保存私钥为 PEM 格式
func savePrivateKeyPEM(privateKey *ecdsa.PrivateKey, filename string) error {
// 转换为 DER 格式
derBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return err
}
// 编码为 PEM
pemBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: derBytes,
}
// 写入文件
return os.WriteFile(filename, pem.EncodeToMemory(pemBlock), 0600)
}
// 加载私钥
func loadPrivateKeyPEM(filename string) (*ecdsa.PrivateKey, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("PEM 解码失败")
}
return x509.ParseECPrivateKey(block.Bytes)
}
// 保存公钥为 PEM 格式
func savePublicKeyPEM(publicKey *ecdsa.PublicKey, filename string) error {
derBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return err
}
pemBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: derBytes,
}
return os.WriteFile(filename, pem.EncodeToMemory(pemBlock), 0644)
}
// 加载公钥
func loadPublicKeyPEM(filename string) (*ecdsa.PublicKey, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("PEM 解码失败")
}
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
return pubKey.(*ecdsa.PublicKey), nil
}
func main() {
// 生成密钥对
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
publicKey := &privateKey.PublicKey
// 保存密钥
savePrivateKeyPEM(privateKey, "private.pem")
savePublicKeyPEM(publicKey, "public.pem")
// 加载密钥
loadedPrivate, _ := loadPrivateKeyPEM("private.pem")
loadedPublic, _ := loadPublicKeyPEM("public.pem")
// 验证密钥匹配
fmt.Printf("私钥匹配:%v\n", privateKey.Equal(loadedPrivate))
fmt.Printf("公钥匹配:%v\n", publicKey.Equal(loadedPublic))
// 测试签名
message := []byte("Persistent key test")
hash := sha256.Sum256(message)
signature, _ := ecdsa.SignASN1(rand.Reader, loadedPrivate, hash[:])
valid := ecdsa.VerifyASN1(loadedPublic, hash[:], signature)
fmt.Printf("签名验证:%v\n", valid)
// 输出 PEM 内容
pemData, _ := os.ReadFile("private.pem")
fmt.Printf("\n私钥 PEM:\n%s\n", string(pemData))
}
4. 确定性签名(RFC 6979)
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
// 生成密钥对
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// 准备消息
message := []byte("Deterministic signature test")
hash := sha256.Sum256(message)
// 普通签名(随机)
sig1, _ := ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
sig2, _ := ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
fmt.Println("=== 随机签名 ===")
fmt.Printf("签名 1: %s\n", hex.EncodeToString(sig1))
fmt.Printf("签名 2: %s\n", hex.EncodeToString(sig2))
fmt.Printf("签名相同:%v\n", string(sig1) == string(sig2))
// 确定性签名(RFC 6979)
// 使用 crypto.Signer 接口和 nil random
opts := &crypto.SignerOpts{
Hash: crypto.SHA256,
}
sig3, _ := privateKey.Sign(nil, hash[:], opts)
sig4, _ := privateKey.Sign(nil, hash[:], opts)
fmt.Println("\n=== 确定性签名 (RFC 6979) ===")
fmt.Printf("签名 1: %s\n", hex.EncodeToString(sig3))
fmt.Printf("签名 2: %s\n", hex.EncodeToString(sig4))
fmt.Printf("签名相同:%v\n", string(sig3) == string(sig4))
// 验证
valid := ecdsa.VerifyASN1(&privateKey.PublicKey, hash[:], sig3)
fmt.Printf("验证结果:%v\n", valid)
}
5. 实际应用 - 代码签名
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"time"
)
type SignedData struct {
Data string `json:"data"`
Timestamp int64 `json:"timestamp"`
Signature string `json:"signature"`
}
type CodeSigner struct {
privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey
}
func NewCodeSigner() (*CodeSigner, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
return &CodeSigner{
privateKey: privateKey,
publicKey: &privateKey.PublicKey,
}, nil
}
func (cs *CodeSigner) Sign(data string) (*SignedData, error) {
// 创建带时间戳的数据
signedData := &SignedData{
Data: data,
Timestamp: time.Now().Unix(),
}
// 序列化
jsonData, err := json.Marshal(signedData)
if err != nil {
return nil, err
}
// 计算哈希
hash := sha256.Sum256(jsonData)
// 签名
signature, err := ecdsa.SignASN1(rand.Reader, cs.privateKey, hash[:])
if err != nil {
return nil, err
}
signedData.Signature = base64.StdEncoding.EncodeToString(signature)
return signedData, nil
}
func (cs *CodeSigner) Verify(signedData *SignedData) (bool, error) {
// 临时移除签名
signature, _ := base64.StdEncoding.DecodeString(signedData.Signature)
tempData := &SignedData{
Data: signedData.Data,
Timestamp: signedData.Timestamp,
}
// 序列化
jsonData, err := json.Marshal(tempData)
if err != nil {
return false, err
}
// 计算哈希
hash := sha256.Sum256(jsonData)
// 验证
return ecdsa.VerifyASN1(cs.publicKey, hash[:], signature), nil
}
func main() {
signer, _ := NewCodeSigner()
// 签名代码
code := "package main; func main() { println(\"Hello\") }"
signed, _ := signer.Sign(code)
// 输出签名数据
output, _ := json.MarshalIndent(signed, "", " ")
fmt.Printf("签名数据:\n%s\n", string(output))
// 验证签名
valid, _ := signer.Verify(signed)
fmt.Printf("\n签名验证:%v\n", valid)
// 篡改数据
signed.Data = "tampered code"
valid, _ = signer.Verify(signed)
fmt.Printf("篡改后验证:%v\n", valid)
}
6. 与 ECDH 互操作
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
)
func main() {
// 生成 ECDSA 密钥对
ecdsaKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// 转换为 ECDH 密钥
ecdhPrivate, err := ecdsaKey.ECDH()
if err != nil {
fmt.Println("转换失败:", err)
return
}
ecdhPublic, err := ecdsaKey.PublicKey.ECDH()
if err != nil {
fmt.Println("转换失败:", err)
return
}
fmt.Printf("ECDSA 私钥已转换为 ECDH 私钥\n")
fmt.Printf("ECDH 私钥字节长度:%d\n", len(ecdhPrivate.Bytes()))
fmt.Printf("ECDH 公钥字节长度:%d\n", len(ecdhPublic.Bytes()))
// 现在可以使用 ECDH 进行密钥交换
ecdsaKey2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
ecdhKey2, _ := ecdsaKey2.ECDH()
// ECDH 密钥交换
shared1, _ := ecdhPrivate.ECDH(ecdhKey2.PublicKey())
shared2, _ := ecdhKey2.ECDH(ecdhPublic)
fmt.Printf("共享密钥 1: %x\n", shared1)
fmt.Printf("共享密钥 2: %x\n", shared2)
fmt.Printf("共享密钥匹配:%v\n", string(shared1) == string(shared2))
}
🔹 注意事项和最佳实践
1. 曲线选择
-
✅ 推荐使用 P-256
- 安全性高(128 位安全)
- 性能好
- 广泛支持
-
⚠️ P-384 / P-521
- 更高安全性
- 性能较差
- 特殊场景使用
// 推荐
privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// 高安全需求
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
2. 哈希函数选择
-
✅ 推荐使用 SHA-256
- 与 P-256 配合良好
- 安全性高
- 性能好
-
⚠️ 避免使用 MD5 / SHA-1
- 已不安全
- 不应再使用
// 正确
hash := sha256.Sum256(message)
// 错误
hash := md5.Sum(message) // 不安全
3. 随机数生成
- ✅ 必须使用
crypto/rand.Reader - ❌ 不要使用
math/rand - ✅ Go 1.26+ 强制使用安全随机源
// 正确
signature, _ := ecdsa.SignASN1(crypto/rand.Reader, privateKey, hash[:])
// 错误
signature, _ := ecdsa.SignASN1(mathRand.New(mathRand.NewSource(1)), privateKey, hash[:])
4. 签名格式
-
✅ 使用 ASN.1 DER 编码
SignASN1()/VerifyASN1()- 标准格式
- 互操作性好
-
⚠️ 避免直接使用 r, s
- 需要手动编码
- 容易出错
// 推荐
signature, _ := ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
valid := ecdsa.VerifyASN1(publicKey, hash[:], signature)
// 不推荐(除非特殊需求)
r, s, _ := ecdsa.Sign(rand.Reader, privateKey, hash[:])
valid := ecdsa.Verify(publicKey, hash[:], r, s)
5. 密钥存储
- ✅ 私钥加密存储
- ✅ 使用 PEM 格式(配合 x509)
- ✅ 使用密钥管理服务(KMS)
- ❌ 不要硬编码私钥
// 正确 - PEM 格式存储
derBytes, _ := x509.MarshalECPrivateKey(privateKey)
pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: derBytes})
// 错误 - 硬编码
privateKey := "hardcoded_key" // 不安全
6. 错误处理
- ✅ 检查所有错误
- ✅ 验证签名结果
- ✅ 处理无效密钥
signature, err := ecdsa.SignASN1(rand.Reader, privateKey, hash[:])
if err != nil {
return nil, err
}
valid := ecdsa.VerifyASN1(publicKey, hash[:], signature)
if !valid {
return fmt.Errorf("签名验证失败")
}
🔥 总结
核心类型
| 类型 | 说明 | 用途 |
|---|---|---|
| PublicKey | ECDSA 公钥 | 验证签名 |
| PrivateKey | ECDSA 私钥 | 生成签名 |
核心函数
| 函数 | 说明 | 返回值 | 推荐度 |
|---|---|---|---|
| GenerateKey() | 生成密钥对 | (*PrivateKey, error) | ✅ 必需 |
| SignASN1() | 签名(ASN.1) | ([]byte, error) | ✅ 强烈推荐 |
| VerifyASN1() | 验证签名 | bool | ✅ 强烈推荐 |
| Sign() | 签名(r, s) | (r, s, error) | ⚠️ 特殊需求 |
| Verify() | 验证(r, s) | bool | ⚠️ 特殊需求 |
支持的曲线
| 曲线 | 安全性 | 性能 | 推荐度 | 使用场景 |
|---|---|---|---|---|
| P-224 | 中 | 最优 | ⚠️ 轻量级 | 资源受限设备 |
| P-256 | 高 | 好 | ✅ 强烈推荐 | 通用场景 |
| P-384 | 很高 | 中 | ⚠️ 高安全 | 政府、金融 |
| P-521 | 极高 | 差 | ⚠️ 特殊需求 | 最高安全要求 |
主要特点
- 数字签名 👉 私钥签名,公钥验证
- 随机签名 👉 每次签名不同(更安全)
- 常量时间 👉 防侧信道攻击
- 标准兼容 👉 FIPS 186-5、SEC 1
- ECDH 互操作 👉 可转换为 ECDH 密钥
使用场景
- 证书签名 👉 X.509 证书
- 代码签名 👉 软件完整性验证
- 区块链 👉 比特币、以太坊等
- JWT 签名 👉 身份认证令牌
- 文档签名 👉 PDF、邮件签名
最佳实践
- ✅ 优先使用 P-256 曲线
- ✅ 使用 SHA-256 哈希
- ✅ 使用 SignASN1/VerifyASN1
- ✅ 使用 crypto/rand.Reader
- ✅ 加密存储私钥
- ✅ 验证公钥真实性
- ⚠️ 定期轮换密钥
- ⚠️ 防止私钥泄露
安全建议
- 🔒 使用硬件安全模块(HSM)
- 🔒 实施密钥轮换策略
- 🔒 记录签名操作日志
- 🔒 进行安全审计
- 🔒 防止侧信道攻击
🔹 与 crypto/ecdsa 和 crypto/ecdh 的关系
| 特性 | ECDSA | ECDH |
|---|---|---|
| 用途 | 数字签名 | 密钥交换 |
| 操作 | 签名/验证 | 计算共享密钥 |
| 密钥格式 | 兼容 | 可互相转换 |
| 典型应用 | 证书、区块链 | TLS 握手、VPN |
crypto/ecdsa 包提供了安全、标准的 ECDSA 数字签名实现,推荐使用 P-256 曲线和 SignASN1/VerifyASN1 函数!