HS 、RSA 、ES 、ED 四种签名算法:
一、算法对比
属性 |
HS |
RSA |
ES |
ED |
加密类型 |
对称加密 |
非对称加密 |
非对称加密 |
非对称加密 |
密钥长度 |
任意长度 |
私钥:2048+ 位 |
私钥:256+ 位 |
私钥:256 位(Ed25519) |
签名效率 |
高 |
较低 |
高 |
高 |
验证效率 |
高 |
较低 |
高 |
高 |
安全性 |
中 |
高 |
高 |
高 |
密钥分离 |
不支持 |
支持 |
支持 |
支持 |
典型场景 |
内部系统通信 |
安全性要求高的场景 |
移动设备和 IoT 场景 |
安全敏感的高效场景 |
二、构建过程
1. HS (HMAC-SHA) 密钥生成流程
步骤 |
操作代码 |
说明 |
1. 定义密钥长度 |
key := make([]byte, 32) |
定义对称密钥长度,常用 256 位(32 字节)。 |
2. 生成随机密钥 |
_, err := rand.Read(key) |
使用随机生成器填充密钥。 |
3. 保存到文件 |
os.WriteFile("hmac.key", key, 0644) |
将密钥以二进制文件形式保存到本地,确保权限控制。 |
2. RSA 密钥生成流程
步骤 |
操作代码 |
说明 |
1. 生成密钥对 |
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) |
使用 RSA 生成 2048 位密钥对。 |
2. 转换为 x509 格式 |
x509PrivateKey := x509.MarshalPKCS8PrivateKey(privateKey) |
将私钥封装为 PKCS8 格式。 |
|
x509PublicKey := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) |
将公钥封装为 PKIX 格式。注意这里传地址 |
3. 封装为 PEM 格式 |
privateBlock := &pem.Block{Type: "PRIVATE KEY", Bytes: x509PrivateKey} |
使用 PEM 格式封装私钥。 |
|
publicBlock := &pem.Block{Type: "PUBLIC KEY", Bytes: x509PublicKey} |
使用 PEM 格式封装公钥。 |
4. 保存到文件 |
os.OpenFile + pem.Encode |
将密钥分别写入 private.pem 和 public.pem 文件。 |
3. ES (Elliptic Curve) 密钥生成流程
步骤 |
操作代码 |
说明 |
1. 定义曲线 |
curve := elliptic.P256() |
选择椭圆曲线(如 P256)。 |
2. 生成密钥对 |
privateKey, err := ecdsa.GenerateKey(es.getCurve(), rand.Reader) |
生成私钥及公钥。 |
3. 转换为 x509 格式 |
x509PrivateKey := x509.MarshalECPrivateKey(privateKey) |
将私钥封装为 x509 格式。 |
4. 封装为 PEM 格式 |
privateBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: x509PrivateKey} |
使用 PEM 格式封装私钥。 |
|
publicBlock := &pem.Block{Type: "PUBLIC KEY", Bytes: x509PublicKey} |
使用 PEM 格式封装公钥。 |
5. 保存到文件 |
os.OpenFile + pem.Encode |
将密钥分别写入 ec_private.pem 和 ec_public.pem 文件。 |
4. ED (Ed25519) 密钥生成流程
步骤 |
操作代码 |
说明 |
1. 生成密钥对 |
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) |
使用 Ed25519 算法生成密钥对,密钥长度固定。 |
2. 转换为 PEM 格式 |
privateBlock := &pem.Block{Type: "PRIVATE KEY", Bytes: privateKey} |
使用 PEM 格式封装私钥。 |
|
publicBlock := &pem.Block{Type: "PUBLIC KEY", Bytes: publicKey} |
使用 PEM 格式封装公钥。 |
3. 保存到文件 |
os.OpenFile + pem.Encode |
将密钥分别写入 ed_private.pem 和 ed_public.pem 文件。 |
总结表格
加密方式 |
密钥类型 |
生成流程关键步骤 |
HS |
对称密钥 |
随机生成固定长度的密钥,直接保存到文件。 |
RSA |
公钥 + 私钥 |
生成密钥对 → 转换为 x509 → 封装为 PEM → 写入文件。 |
ES |
公钥 + 私钥 |
选择椭圆曲线 → 生成密钥对 → 转换为 x509 → 封装为 PEM → 写入文件。 |
ED |
公钥 + 私钥 |
直接生成 Ed25519 密钥对 → 封装为 PEM → 写入文件。 |
go案例实现
hs.go
go
复制代码
package secret
import (
"encoding/hex"
"math/rand"
"time"
)
// HsGenerator HS HS256、HS384、HS512 属于对称加密算法。
// 加密和验证使用相同的密钥。
type HsGenerator struct {
Length int
}
// Generate 生成一个指定长度随机密钥,将其编码为16进制字符串返回
// 生成过程,直接生成密钥
func (hs *HsGenerator) Generate() (*OutSecret, error) {
out := &OutSecret{}
length := 32
if hs.Length > 0 {
length = hs.Length
}
//rand.Seed(time.Now().UnixNano())
rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
rand.Read(b)
out.Secret = hex.EncodeToString(b)[:length]
return out, nil
}
rsa.go
go
复制代码
package secret
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"log"
"os"
)
type RsGenerator struct {
}
// Generate 生成RSA密钥对
// 步骤:
// 生成RSA私钥和公钥。
// 将私钥和公钥转换为X509格式。
// 将私钥和公钥封装为PEM格式并保存到指定路径。
// 返回包含私钥和公钥文件路径的对象。
func (rs *RsGenerator) Generate() (*OutSecret, error) {
out := &OutSecret{}
var err error
// 生成密钥对
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
// rsa.GenerateKey 用于生成指定位数rsa密钥对 rand.Reader提供加密安全随机数,1024位数
if err != nil {
log.Fatalln(err)
return nil, err
}
// x509格式封装
// x509是一种公私钥证书标准格式,定义公钥证书结构
// 用x509封装,保证一致性
x509PrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
log.Fatalln(err)
return nil, err
}
// ,privateKey.PublicKey 是一个 rsa.PublicKey 类型的值(不是指针),
// 因此你需要使用 & 来获取它的地址,以便将其传递给 x509.MarshalPKIXPublicKey 函数。
// 这里接收的是 &privateKey.PublicKey,表示获取 privateKey.PublicKey 的地址。
x509PublicKey, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
log.Fatalln(err)
return nil, err
}
// 表示 PEM 编码的数据块。block用于保存加密密钥、证书等二进制数据。
privateBlock := &pem.Block{
Type: "PRIVATE KEY", // 数据块类型,例如"PRIVATE KEY" 或 "PUBLIC KEY"
Bytes: x509PrivateKey, // 用x509封装的,实际的二进制数据
}
// 生成对应文件
privateKeyFile := KEY_PATH + "/rsa/private.pem"
privateFile, err := os.OpenFile(privateKeyFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalln(err)
return nil, err
}
defer privateFile.Close()
pem.Encode(privateFile, privateBlock) // pem.Encode将block数据块写入到io.Writer接口,这里就是文件
publicBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: x509PublicKey,
}
publicKeyFile := KEY_PATH + "/rsa/public.pem"
publicFile, err := os.OpenFile(publicKeyFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalln(err)
return nil, err
}
defer publicFile.Close()
pem.Encode(publicFile, publicBlock)
out.PrivateKeyFile = privateKeyFile
out.PublicKeyFile = publicKeyFile
return out, nil
}
ed.go
go
复制代码
package secret
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"log"
"os"
)
type EdGenerator struct {
}
func (ed *EdGenerator) Generate() (*OutSecret, error) {
out := &OutSecret{}
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Println(err)
return nil, err
}
// x509格式封装
x509PrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey) // ed25519的这里privateKey不是一个指针
if err != nil {
log.Println(err)
return nil, err
}
x509PublicKey, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
log.Println(err)
return nil, err
}
// 设置pem编码的数据块
privateBlock := &pem.Block{
Type: "PRIVATE_KEY",
Bytes: x509PrivateKey,
}
privateKeyFile := KEY_PATH + "/ed/private.pem"
privateFile, err := os.OpenFile(privateKeyFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalln(err)
return nil, err
}
defer privateFile.Close()
pem.Encode(privateFile, privateBlock) // pem编码
publicBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: x509PublicKey,
}
publicKeyFile := KEY_PATH + "/ed/public.pem"
publicFile, err := os.OpenFile(publicKeyFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalln(err)
return nil, err
}
defer publicFile.Close()
pem.Encode(publicFile, publicBlock)
out.PrivateKeyFile = privateKeyFile
out.PublicKeyFile = publicKeyFile
return out, nil
}
es.go
go
复制代码
package secret
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"log"
"os"
)
/*
为什么需要分类
在 ES 算法中,不同的签名方法(如 ES256、ES384、ES512)
对应不同的椭圆曲线和哈希函数。具体来说:
ES256 使用 P-256 曲线和 SHA-256 哈希函数。
ES384 使用 P-384 曲线和 SHA-384 哈希函数。
ES512 使用 P-521 曲线和 SHA-512 哈希函数。
这些不同的曲线和哈希函数提供了不同级别的安全性。
通过定义常量和类型 ESSigningMethodS,可以方便地管理和切换不同的签名方法,确保代码的灵活性和可扩展性
*/
const (
ES256 ESSigningMethodS = "ES256"
ES384 ESSigningMethodS = "ES384"
ES512 ESSigningMethodS = "ES512"
)
type ESSigningMethodS string
type EsGenerator struct {
SigningMethod ESSigningMethodS
}
func (es *EsGenerator) getCurve() elliptic.Curve {
switch es.SigningMethod {
case ES256:
return elliptic.P256()
case ES384:
return elliptic.P384()
case ES512:
return elliptic.P521()
default:
return elliptic.P256()
}
}
func (es *EsGenerator) Generate() (*OutSecret, error) {
out := &OutSecret{}
privateKey, err := ecdsa.GenerateKey(es.getCurve(), rand.Reader)
if err != nil {
log.Println(err)
return nil, err
}
// x509格式封装
x509PrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey) // privateKey是一个指针
if err != nil {
log.Println(err)
return nil, err
}
// ,privateKey.PublicKey 是一个 rsa.PublicKey 类型的值(不是指针),
// 因此你需要使用 & 来获取它的地址,以便将其传递给 x509.MarshalPKIXPublicKey 函数。
// 这里接收的是 &privateKey.PublicKey,表示获取 privateKey.PublicKey 的地址。
x509PublicKey, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
log.Println(err)
return nil, err
}
// 设置pem编码的数据块
privateBlock := &pem.Block{
Type: "PRIVATE_KEY",
Bytes: x509PrivateKey,
}
privateKeyFile := KEY_PATH + "/es/private.pem"
privateFile, err := os.OpenFile(privateKeyFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalln(err)
return nil, err
}
defer privateFile.Close()
pem.Encode(privateFile, privateBlock) // pem编码
publicBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: x509PublicKey,
}
publicKeyFile := KEY_PATH + "/es/public.pem"
publicFile, err := os.OpenFile(publicKeyFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalln(err)
return nil, err
}
defer publicFile.Close()
pem.Encode(publicFile, publicBlock)
out.PrivateKeyFile = privateKeyFile
out.PublicKeyFile = publicKeyFile
return out, nil
}
https://github.com/0voice