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

Go 语言标准库 —— crypto/elliptic 包(椭圆曲线)


🔹 概述

crypto/elliptic 包实现了标准的 NIST 椭圆曲线(P-224、P-256、P-384、P-521),这些曲线基于素数域。

⚠️ 重要说明:

  • 此包的直接使用已弃用
  • ✅ 应使用 crypto/ecdh 进行密钥交换
  • ✅ 应使用 crypto/ecdsa 进行数字签名
  • 📦 本包主要用于 crypto/ecdsacrypto/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/ecdhGenerateKey 方法
    • ECDSA:使用 crypto/ecdsaGenerateKey 函数
  • 说明:

    • 生成公钥/私钥对
    • 使用给定的随机数源生成私钥
  • 参数:

    • 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/ecdhPublicKey.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/ecdhNewPublicKey 方法
  • 说明:

    • 将未压缩格式的点转换为 (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 进行数字签名!