Go 语言标准库 —— crypto/elliptic 包(椭圆曲线)
🔹 概述
crypto/elliptic 包实现了标准的 NIST 椭圆曲线(P-224、P-256、P-384、P-521),这些曲线基于素数域。
⚠️ 重要说明:
- 此包的直接使用已弃用
- ✅ 应使用
crypto/ecdh进行密钥交换 - ✅ 应使用
crypto/ecdsa进行数字签名 - 📦 本包主要用于
crypto/ecdsa和crypto/ecdh的内部实现 - 🔧 自定义曲线不保证安全性
主要功能:
- 提供 NIST 标准曲线实现
- 椭圆曲线基本运算(点加、倍点、标量乘法)
- 点的序列化/反序列化
- 常量时间实现(防侧信道攻击)
支持的曲线:
- P-224 (secp224r1) - 轻量级
- P-256 (secp256r1, prime256v1) - 常用
- P-384 (secp384r1) - 高安全性
- P-521 (secp521r1) - 最高安全性
🔹 核心类型
Curve 接口
type Curve interface {
Params() *CurveParams
IsOnCurve(x, y *big.Int) bool
Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int)
Double(x1, y1 *big.Int) (x, y *big.Int)
ScalarMult(x1, y1 *big.Int, k []byte) (x, y *big.Int)
ScalarBaseMult(k []byte) (x, y *big.Int)
}
-
说明:
- 表示短 Weierstrass 形式的椭圆曲线(a=-3)
- 提供曲线基本运算接口
- ⚠️ 不推荐直接使用此接口
-
方法:
Params() *CurveParams- 获取曲线参数IsOnCurve(x, y *big.Int) bool- 检查点是否在曲线上Add(...)- 点加运算Double(...)- 倍点运算ScalarMult(...)- 标量乘法(任意点)ScalarBaseMult(...)- 标量乘法(基点)
-
注意事项:
- ⚠️ 输入不在曲线上时行为未定义
- ⚠️ 无穷远点 (0, 0) 不被认为在曲线上
- ✅ P224/P256/P384/P521 返回的曲线使用常量时间算法
CurveParams 类型
type CurveParams struct {
Name string
P *big.Int // 域的素数
N *big.Int // 曲线的阶
B *big.Int // 曲线参数 b
Gx *big.Int // 基点 X 坐标
Gy *big.Int // 基点 Y 坐标
BitSize int // 曲线位数
}
-
说明:
- 包含椭圆曲线的参数
- 提供通用的(非恒定时间)曲线实现
- ⚠️ 此类型的通用实现已弃用
-
字段:
Name- 曲线名称P- 素数域的模数N- 曲线的阶(基点生成的子群大小)B- 曲线方程 y² = x³ + ax + b 中的 b(a=-3)Gx, Gy- 基点坐标BitSize- 曲线的位数
-
方法(已弃用):
Params()- 返回曲线参数IsOnCurve()- 检查点是否在曲线上Add()- 点加Double()- 倍点ScalarMult()- 标量乘法ScalarBaseMult()- 基点标量乘法
-
注意事项:
- ⚠️ 不保证安全性
- ⚠️ 不推荐用于安全应用
- ✅ 仅用于获取曲线参数
🔹 曲线函数
P224 - NIST P-224 曲线
elliptic.P224() Curve
-
说明:
- NIST P-224 曲线(FIPS 186-3, section D.2.2)
- 也称为 secp224r1
- 224 位安全性
-
特点:
- 常量时间实现
- 多次调用返回相同值(可用于相等性检查)
- 适用于资源受限环境
-
示例:
package main import ( "crypto/elliptic" "fmt" ) func main() { // 获取 P-224 曲线 curve := elliptic.P224() params := curve.Params() fmt.Printf("曲线名称:%s\n", params.Name) fmt.Printf "域大小:%d 位\n", params.BitSize) fmt.Printf("P: %s\n", params.P.Text(16)) fmt.Printf("N: %s\n", params.N.Text(16)) }
P256 - NIST P-256 曲线(推荐)
elliptic.P256() Curve
-
说明:
- NIST P-256 曲线(FIPS 186-3, section D.2.3)
- 也称为 secp256r1 或 prime256v1
- 256 位安全性
- ✅ 最常用的 NIST 曲线
-
特点:
- 常量时间实现
- 广泛支持
- 性能和安全性的良好平衡
-
示例:
package main import ( "crypto/elliptic" "fmt" ) func main() { // 获取 P-256 曲线 curve := elliptic.P256() params := curve.Params() fmt.Printf("曲线名称:%s\n", params.Name) fmt.Printf("域大小:%d 位\n", params.BitSize) fmt.Printf("P: %s\n", params.P.Text(16)) fmt.Printf("N: %s\n", params.N.Text(16)) fmt.Printf("基点 Gx: %s\n", params.Gx.Text(16)) fmt.Printf("基点 Gy: %s\n", params.Gy.Text(16)) }
P384 - NIST P-384 曲线
elliptic.P384() Curve
- 说明:
- NIST P-384 曲线(FIPS 186-3, section D.2.4)
- 也称为 secp384r1
- 384 位安全性
- 特点:
- 常量时间实现
- 更高安全性
- 适用于高安全需求场景
P521 - NIST P-521 曲线
elliptic.P521() Curve
- 说明:
- NIST P-521 曲线(FIPS 186-3, section D.2.5)
- 也称为 secp521r1
- 521 位安全性
- 特点:
- 常量时间实现
- 最高安全性
- 密钥较大,性能较差
🔹 核心函数(已弃用)
GenerateKey - 生成密钥对(已弃用)
elliptic.GenerateKey(curve Curve, rand io.Reader) (priv []byte, x, y *big.Int, err error)
-
⚠️ 已弃用
- ECDH:使用
crypto/ecdh的GenerateKey方法 - ECDSA:使用
crypto/ecdsa的GenerateKey函数
- ECDH:使用
-
说明:
- 生成公钥/私钥对
- 使用给定的随机数源生成私钥
-
参数:
curve Curve- 椭圆曲线rand io.Reader- 随机数源
-
返回值:
priv []byte- 私钥字节x, y *big.Int- 公钥坐标error- 错误信息
-
示例(不推荐,仅供参考):
package main import ( "crypto/elliptic" "crypto/rand" "fmt" ) func main() { curve := elliptic.P256() // 生成密钥对(已弃用) priv, x, y, err := elliptic.GenerateKey(curve, rand.Reader) if err != nil { fmt.Println("错误:", err) return } fmt.Printf("私钥:%x\n", priv) fmt.Printf("公钥 X: %s\n", x.Text(16)) fmt.Printf("公钥 Y: %s\n", y.Text(16)) }
Marshal - 序列化点(已弃用)
elliptic.Marshal(curve Curve, x, y *big.Int) []byte
-
⚠️ 已弃用
- 使用
crypto/ecdh的PublicKey.Bytes()方法
- 使用
-
说明:
- 将曲线上的点转换为未压缩格式(SEC 1 v2.0, Section 2.3.3)
- 返回格式:
0x04 || X || Y
-
参数:
curve Curve- 椭圆曲线x, y *big.Int- 点坐标
-
返回值:
[]byte- 序列化的点
-
示例(不推荐,仅供参考):
package main import ( "crypto/elliptic" "crypto/rand" "encoding/hex" "fmt" ) func main() { curve := elliptic.P256() priv, x, y, _ := elliptic.GenerateKey(curve, rand.Reader) _ = priv // 忽略私钥 // 序列化公钥(已弃用) data := elliptic.Marshal(curve, x, y) fmt.Printf("未压缩公钥:%s\n", hex.EncodeToString(data)) fmt.Printf("长度:%d 字节\n", len(data)) }
MarshalCompressed - 压缩序列化点
elliptic.MarshalCompressed(curve Curve, x, y *big.Int) []byte
-
说明:
- 将曲线上的点转换为压缩格式(SEC 1 v2.0, Section 2.3.3)
- 返回格式:
0x02/0x03 || X(根据 Y 的奇偶性) - ✅ 此函数未弃用
-
参数:
curve Curve- 椭圆曲线x, y *big.Int- 点坐标
-
返回值:
[]byte- 压缩序列化的点
-
示例:
package main import ( "crypto/elliptic" "crypto/rand" "encoding/hex" "fmt" ) func main() { curve := elliptic.P256() priv, x, y, _ := elliptic.GenerateKey(curve, rand.Reader) _ = priv // 压缩序列化(未弃用) compressed := elliptic.MarshalCompressed(curve, x, y) fmt.Printf("压缩公钥:%s\n", hex.EncodeToString(compressed)) fmt.Printf("长度:%d 字节\n", len(compressed)) // 与未压缩对比 uncompressed := elliptic.Marshal(curve, x, y) fmt.Printf("未压缩长度:%d 字节\n", len(uncompressed)) }
Unmarshal - 反序列化点(已弃用)
elliptic.Unmarshal(curve Curve, data []byte) (x, y *big.Int)
-
⚠️ 已弃用
- 使用
crypto/ecdh的NewPublicKey方法
- 使用
-
说明:
- 将未压缩格式的点转换为 (x, y) 坐标
- 验证点是否在曲线上
-
参数:
curve Curve- 椭圆曲线data []byte- 序列化的点(必须以 0x04 开头)
-
返回值:
x, y *big.Int- 点坐标(失败时 x=nil)
-
示例(不推荐,仅供参考):
package main import ( "crypto/elliptic" "crypto/rand" "encoding/hex" "fmt" ) func main() { curve := elliptic.P256() _, x, y, _ := elliptic.GenerateKey(curve, rand.Reader) // 序列化 data := elliptic.Marshal(curve, x, y) fmt.Printf("序列化:%s\n", hex.EncodeToString(data)) // 反序列化(已弃用) x2, y2 := elliptic.Unmarshal(curve, data) if x2 == nil { fmt.Println("反序列化失败") return } fmt.Printf("反序列化成功\n") fmt.Printf("X 匹配:%v\n", x.Cmp(x2) == 0) fmt.Printf("Y 匹配:%v\n", y.Cmp(y2) == 0) }
UnmarshalCompressed - 反序列化压缩点
elliptic.UnmarshalCompressed(curve Curve, data []byte) (x, y *big.Int)
-
说明:
- 将压缩格式的点转换为 (x, y) 坐标
- 验证点是否在曲线上
- ✅ 此函数未弃用
-
参数:
curve Curve- 椭圆曲线data []byte- 压缩序列化的点(必须以 0x02 或 0x03 开头)
-
返回值:
x, y *big.Int- 点坐标(失败时 x=nil)
-
示例:
package main import ( "crypto/elliptic" "crypto/rand" "encoding/hex" "fmt" ) func main() { curve := elliptic.P256() _, x, y, _ := elliptic.GenerateKey(curve, rand.Reader) // 压缩序列化 compressed := elliptic.MarshalCompressed(curve, x, y) fmt.Printf("压缩:%s\n", hex.EncodeToString(compressed)) // 压缩反序列化(未弃用) x2, y2 := elliptic.UnmarshalCompressed(curve, compressed) if x2 == nil { fmt.Println("反序列化失败") return } fmt.Printf("反序列化成功\n") fmt.Printf("X 匹配:%v\n", x.Cmp(x2) == 0) fmt.Printf("Y 匹配:%v\n", y.Cmp(y2) == 0) }
🔹 完整示例
1. 获取曲线参数
package main
import (
"crypto/elliptic"
"fmt"
)
func printCurveInfo(curve elliptic.Curve, name string) {
params := curve.Params()
fmt.Printf("\n=== %s ===\n", name)
fmt.Printf("曲线名称:%s\n", params.Name)
fmt.Printf("域大小:%d 位\n", params.BitSize)
fmt.Printf("P (模数): %s...\n", params.P.Text(16)[:16])
fmt.Printf("N (阶): %s...\n", params.N.Text(16)[:16])
fmt.Printf("B (参数): %s...\n", params.B.Text(16)[:16])
fmt.Printf("基点 Gx: %s...\n", params.Gx.Text(16)[:16])
fmt.Printf("基点 Gy: %s...\n", params.Gy.Text(16)[:16])
}
func main() {
fmt.Println("NIST 椭圆曲线参数")
printCurveInfo(elliptic.P224(), "NIST P-224")
printCurveInfo(elliptic.P256(), "NIST P-256")
printCurveInfo(elliptic.P384(), "NIST P-384")
printCurveInfo(elliptic.P521(), "NIST P-521")
}
2. 点运算示例
package main
import (
"crypto/elliptic"
"crypto/rand"
"fmt"
"math/big"
)
func main() {
curve := elliptic.P256()
params := curve.Params()
// 生成随机私钥
priv, x, y, _ := elliptic.GenerateKey(curve, rand.Reader)
fmt.Printf("私钥:%x\n", priv)
fmt.Printf("公钥:(%s, %s)\n", x.Text(16), y.Text(16))
// 检查点是否在曲线上
onCurve := curve.IsOnCurve(x, y)
fmt.Printf("点在曲线上:%v\n", onCurve)
// 点加(不推荐,仅演示)
x2, y2 := curve.Double(x, y)
fmt.Printf("倍点:(%s, %s)\n", x2.Text(16), y2.Text(16))
// 标量乘法(不推荐,仅演示)
k := big.NewInt(2)
x3, y3 := curve.ScalarMult(x, y, k.Bytes())
fmt.Printf("2P: (%s, %s)\n", x3.Text(16), y3.Text(16))
// 基点标量乘法
x4, y4 := curve.ScalarBaseMult(priv)
fmt.Printf("从私钥计算公钥:(%s, %s)\n", x4.Text(16), y4.Text(16))
fmt.Printf("公钥匹配:%v\n", x.Cmp(x4) == 0 && y.Cmp(y4) == 0)
}
3. 点的序列化与反序列化
package main
import (
"crypto/elliptic"
"crypto/rand"
"encoding/hex"
"fmt"
)
func main() {
curve := elliptic.P256()
_, x, y, _ := elliptic.GenerateKey(curve, rand.Reader)
fmt.Println("=== 点的序列化 ===")
// 未压缩格式(已弃用)
uncompressed := elliptic.Marshal(curve, x, y)
fmt.Printf("未压缩:%s\n", hex.EncodeToString(uncompressed))
fmt.Printf("长度:%d 字节\n", len(uncompressed))
// 压缩格式(未弃用)
compressed := elliptic.MarshalCompressed(curve, x, y)
fmt.Printf("\n压缩:%s\n", hex.EncodeToString(compressed))
fmt.Printf("长度:%d 字节\n", len(compressed))
// 反序列化
fmt.Println("\n=== 反序列化 ===")
// 未压缩反序列化(已弃用)
x1, y1 := elliptic.Unmarshal(curve, uncompressed)
if x1 != nil {
fmt.Printf("未压缩反序列化成功\n")
fmt.Printf("X 匹配:%v\n", x.Cmp(x1) == 0)
}
// 压缩反序列化(未弃用)
x2, y2 := elliptic.UnmarshalCompressed(curve, compressed)
if x2 != nil {
fmt.Printf("压缩反序列化成功\n")
fmt.Printf("X 匹配:%v\n", x.Cmp(x2) == 0)
fmt.Printf("Y 匹配:%v\n", y.Cmp(y2) == 0)
}
// 错误处理
fmt.Println("\n=== 错误处理 ===")
invalidData := []byte{0x04, 0x00, 0x01} // 无效数据
x3, y3 := elliptic.Unmarshal(curve, invalidData)
if x3 == nil {
fmt.Printf("未压缩反序列化失败(预期)\n")
}
invalidCompressed := []byte{0x02, 0x00, 0x01} // 无效数据
x4, y4 := elliptic.UnmarshalCompressed(curve, invalidCompressed)
if x4 == nil {
fmt.Printf("压缩反序列化失败(预期)\n")
}
}
4. 迁移到 crypto/ecdh
package main
import (
"crypto/ecdh"
"crypto/elliptic"
"crypto/rand"
"encoding/hex"
"fmt"
)
func main() {
fmt.Println("=== 旧方法(已弃用)===")
oldMethod()
fmt.Println("\n=== 新方法(推荐)===")
newMethod()
}
// 旧方法(已弃用)
func oldMethod() {
curve := elliptic.P256()
// 生成密钥对(已弃用)
priv, x, y, _ := elliptic.GenerateKey(curve, rand.Reader)
// 序列化(已弃用)
data := elliptic.Marshal(curve, x, y)
fmt.Printf("私钥:%x\n", priv)
fmt.Printf("公钥序列化:%s\n", hex.EncodeToString(data))
}
// 新方法(推荐)
func newMethod() {
curve := ecdh.P256()
// 生成密钥对(推荐)
privateKey, _ := curve.GenerateKey(rand.Reader)
publicKey := privateKey.PublicKey()
// 序列化(推荐)
privateBytes := privateKey.Bytes()
publicBytes := publicKey.Bytes()
fmt.Printf("私钥:%x\n", privateBytes)
fmt.Printf("公钥序列化:%s\n", hex.EncodeToString(publicBytes))
}
5. 曲线相等性检查
package main
import (
"crypto/elliptic"
"fmt"
)
func main() {
// 多次调用返回相同值(可用于相等性检查)
curve1 := elliptic.P256()
curve2 := elliptic.P256()
curve3 := elliptic.P384()
fmt.Printf("P256 == P256: %v\n", curve1 == curve2)
fmt.Printf("P256 == P384: %v\n", curve1 == curve3)
// 可用于 switch 语句
curve := elliptic.P256()
switch curve {
case elliptic.P224():
fmt.Println("P-224 曲线")
case elliptic.P256():
fmt.Println("P-256 曲线")
case elliptic.P384():
fmt.Println("P-384 曲线")
case elliptic.P521():
fmt.Println("P-521 曲线")
default:
fmt.Println("未知曲线")
}
}
🔹 注意事项和最佳实践
1. 弃用说明
- ⚠️ 直接使用此包已弃用
- ✅ ECDH 密钥交换 - 使用
crypto/ecdh - ✅ ECDSA 签名 - 使用
crypto/ecdsa - ✅ 自定义曲线 - 使用第三方模块
// ❌ 不推荐 - 直接使用 elliptic
priv, x, y, _ := elliptic.GenerateKey(elliptic.P256(), rand.Reader)
data := elliptic.Marshal(elliptic.P256(), x, y)
// ✅ 推荐 - 使用 ecdh
privateKey, _ := ecdh.P256().GenerateKey(rand.Reader)
publicBytes := privateKey.PublicKey().Bytes()
// ✅ 推荐 - 使用 ecdsa
ecdsaKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
2. 曲线选择
- ✅ P-256 - 通用场景(推荐)
- ⚠️ P-384 - 高安全需求
- ⚠️ P-521 - 特殊需求
- ⚠️ P-224 - 资源受限环境
// 推荐 - P-256
curve := elliptic.P256()
// 高安全需求 - P-384
curve := elliptic.P384()
3. 序列化格式
- ✅ 压缩格式 - 更小(未弃用)
- ⚠️ 未压缩格式 - 已弃用
- 📏 大小对比:
- P-256 未压缩:65 字节(0x04 + 32 + 32)
- P-256 压缩:33 字节(0x02/0x03 + 32)
// 推荐 - 压缩格式
compressed := elliptic.MarshalCompressed(curve, x, y)
// 不推荐 - 未压缩格式(已弃用)
uncompressed := elliptic.Marshal(curve, x, y)
4. 常量时间实现
- ✅ P224/P256/P384/P521 使用常量时间算法
- ⚠️ CurveParams 的通用实现非常量时间
- 🔒 防止侧信道攻击
// 安全 - 使用 P256() 返回的曲线
curve := elliptic.P256()
result := curve.ScalarBaseMult(k) // 常量时间
// 不安全 - CurveParams 的通用实现
params := &elliptic.CurveParams{...}
result := params.ScalarBaseMult(k) // 非常量时间
5. 点验证
- ✅ 反序列化时自动验证点在曲线上
- ⚠️ 手动运算时不验证
- 🛡️ 防止无效点攻击
// 安全 - 反序列化自动验证
x, y := elliptic.UnmarshalCompressed(curve, data)
if x == nil {
// 点无效
}
// 手动验证
if !curve.IsOnCurve(x, y) {
// 点不在曲线上
}
🔥 总结
核心类型
| 类型 | 说明 | 状态 | 用途 |
|---|---|---|---|
| Curve | 椭圆曲线接口 | ⚠️ 弃用直接使用 | ecdsa/ecdh 内部使用 |
| CurveParams | 曲线参数 | ⚠️ 通用实现弃用 | 获取曲线参数 |
支持的曲线
| 曲线 | 安全性 | 性能 | 推荐度 | 使用场景 |
|---|---|---|---|---|
| P-224 | 中 | 最优 | ⚠️ 轻量级 | 资源受限设备 |
| P-256 | 高 | 好 | ✅ 强烈推荐 | 通用场景 |
| P-384 | 很高 | 中 | ⚠️ 高安全 | 政府、金融 |
| P-521 | 极高 | 差 | ⚠️ 特殊需求 | 最高安全要求 |
核心函数
| 函数 | 说明 | 状态 | 替代方案 |
|---|---|---|---|
| P224/P256/P384/P521() | 获取曲线 | ✅ 可用 | - |
| GenerateKey() | 生成密钥对 | ❌ 已弃用 | crypto/ecdh, crypto/ecdsa |
| Marshal() | 未压缩序列化 | ❌ 已弃用 | ecdh.PublicKey.Bytes() |
| MarshalCompressed() | 压缩序列化 | ✅ 可用 | - |
| Unmarshal() | 未压缩反序列化 | ❌ 已弃用 | ecdh.Curve.NewPublicKey() |
| UnmarshalCompressed() | 压缩反序列化 | ✅ 可用 | - |
主要特点
- 标准曲线 👉 NIST P-224/256/384/521
- 常量时间 👉 防侧信道攻击(P224/256/384/521)
- 素数域 👉 基于素数域的椭圆曲线
- 短 Weierstrass 形式 👉 a=-3 的曲线
- 相等性保证 👉 多次调用返回相同值
使用场景
- crypto/ecdsa 👉 ECDSA 签名的基础
- crypto/ecdh 👉 ECDH 密钥交换的基础
- 曲线参数查询 👉 获取曲线参数信息
- 点的序列化 👉 压缩格式序列化(未弃用)
最佳实践
- ✅ 使用 crypto/ecdh 进行密钥交换
- ✅ 使用 crypto/ecdsa 进行数字签名
- ✅ 优先使用 P-256 曲线
- ✅ 使用压缩格式序列化点
- ✅ 验证点是否在曲线上
- ⚠️ 避免直接使用 Curve 接口
- ⚠️ 避免使用 CurveParams 的通用实现
- ❌ 不要使用自定义曲线(不保证安全性)
迁移指南
| 旧 API(已弃用) | 新 API(推荐) |
|---|---|
elliptic.GenerateKey() | ecdh.Curve.GenerateKey() |
elliptic.Marshal() | ecdh.PublicKey.Bytes() |
elliptic.Unmarshal() | ecdh.Curve.NewPublicKey() |
curve.ScalarBaseMult() | ecdh 内部实现 |
curve.ScalarMult() | ecdh 内部实现 |
⚠️ crypto/elliptic 包的直接使用已弃用,请使用 crypto/ecdh 进行密钥交换,使用 crypto/ecdsa 进行数字签名!