新闻中心

Go语言中RSA-SHA数字签名与验证的正确实践

2025-11-14
浏览次数:
返回列表

Go语言中RSA-SHA数字签名与验证的正确实践

本文深入探讨了在go语言中使用rsa-sha算法进行数字签名和验证的正确方法。我们将详细介绍如何加载pem格式的rsa私钥和公钥,并基于`crypto/rsa`和`crypto/sha256`包实现数据的签名与验证过程。文章重点纠正了常见的验证逻辑错误,提供了结构清晰、可直接运行的示例代码,并强调了关键的安全实践,旨在帮助开发者构建健壮可靠的数字签名系统。

RSA数字签名概述

数字签名是保障数据完整性、认证性和不可否认性的重要密码学技术。它利用非对称加密算法(如RSA)和哈希函数(如SHA256)来创建。签名过程通常包括:对原始数据计算哈希值,然后使用发送方的私钥对哈希值进行加密(签名)。验证过程则相反:接收方使用发送方的公钥解密(验证)签名,得到原始哈希值,同时独立计算接收到数据的哈希值,比对两者是否一致。如果一致,则证明数据未被篡改且确实来自私钥的持有者。

Go语言中的加密库

Go标准库提供了强大的密码学支持,主要涉及以下几个包:

  • crypto/rsa: 实现了RSA算法,包括密钥生成、加密、解密、签名和验证。
  • crypto/sha256: 提供了SHA-256哈希算法的实现。
  • crypto/x509: 用于解析和处理X.509格式的公钥和私钥证书。
  • encoding/pem: 用于PEM(Privacy-Enhanced Mail)格式的编码和解码,这是存储加密密钥的常用文本格式。
  • crypto/rand: 提供了一个密码学安全的随机数生成器,在签名操作中是必不可少的。
  • encoding/base64: 用于将二进制数据编码为文本格式,便于传输和存储。

私钥加载与数据签名

在Go语言中,实现RSA签名首先需要加载PEM格式的私钥文件,然后使用该私钥对数据的哈希值进行签名。

私钥文件格式

RSA私钥通常以PEM格式存储,常见的有PKCS#1和PKCS#8两种格式。本教程中的示例使用PKCS#1格式,其PEM块类型为RSA PRIVATE KEY。

-----BEGIN RSA PRIVATE KEY-----
... (Base64 encoded private key data) ...
-----END RSA PRIVATE KEY-----

加载和解析私钥

通过encoding/pem解码PEM块,然后使用x509.ParsePKCS1PrivateKey解析PKCS#1格式的私钥。

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "errors"
    "fmt"
    "log"
    "os" // 使用 os.ReadFile 替代 ioutil.ReadFile
)

// Signer 接口定义了签名方法
type Signer interface {
    Sign(data []byte) ([]byte, error)
}

// rsaPrivateKey 结构体包装了 rsa.PrivateKey
type rsaPrivateKey struct {
    *rsa.PrivateKey
}

// Sign 方法实现了数据的RSA-SHA256签名
func (r *rsaPrivateKey) Sign(data []byte) ([]byte, error) {
    // 1. 计算原始数据的SHA256哈希值
    h := sha256.New()
    h.Write(data)
    hashed := h.Sum(nil)

    // 2. 使用rsa.SignPKCS1v15进行签名
    // rand.Reader 提供密码学安全的随机数源
    // crypto.SHA256 指明使用的哈希算法
    return rsa.SignPKCS1v15(rand.Reader, r.PrivateKey, crypto.SHA256, hashed)
}

// parsePrivateKey 解析PEM编码的私钥字节
func parsePrivateKey(pemBytes []byte) (Signer, error) {
    block, _ := pem.Decode(pemBytes)
    if block == nil {
        return nil, errors.New("ssh: no PEM block found")
    }

    var rawkey interface{}
    switch block.Type {
    case "RSA PRIVATE KEY":
        rsaPrivKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
        if err != nil {
            return nil, fmt.Errorf("failed to parse PKCS#1 private key: %w", err)
        }
        rawkey = rsaPrivKey
    // 可以添加对PKCS#8格式的支持
    // case "PRIVATE KEY":
    //  rsaPrivKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    //  if err != nil {
    //      return nil, fmt.Errorf("failed to parse PKCS#8 private key: %w", err)
    //  }
    //  if key, ok := rsaPrivKey.(*rsa.PrivateKey); ok {
    //      rawkey = key
    //  } else {
    //      return nil, fmt.Errorf("unsupported private key type in PKCS#8: %T", rsaPrivKey)
    //  }
    default:
        return nil, fmt.Errorf("ssh: unsupported private key type %q", block.Type)
    }
    return newSignerFromKey(rawkey)
}

// loadPrivateKey 从文件加载并解析私钥
func loadPrivateKey(path string) (Signer, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read private key file: %w", err)
    }
    return parsePrivateKey(data)
}

func newSignerFromKey(k interface{}) (Signer, error) {
    if t, ok := k.(*rsa.PrivateKey); ok {
        return &rsaPrivateKey{t}, nil
    }
    return nil, fmt.Errorf("ssh: unsupported key type %T for signing", k)
}

公钥加载与签名验证

签名验证是数字签名流程的关键一步,它需要加载公钥,然后使用公钥来验证签名的有效性。

公钥文件格式

RSA公钥通常以PEM格式存储,常见的有PKIX格式,其PEM块类型为PUBLIC KEY。

-----BEGIN PUBLIC KEY-----
... (Base64 encoded public key data) ...
-----END PUBLIC KEY-----

加载和解析公钥

通过encoding/pem解码PEM块,然后使用x509.ParsePKIXPublicKey解析PKIX格式的公钥。

Reachout.ai Reachout.ai

一个AI驱动的视频开发平台,专为忙碌的企业家和销售团队打造

Reachout.ai 142 查看详情 Reachout.ai

签名验证逻辑

核心问题在于:签名验证不是解密操作。 原始代码中的Unsign方法错误地使用了rsa.EncryptPKCS1v15,这实际上是加密操作,而非验证。正确的做法是使用rsa.VerifyPKCS1v15函数,它需要原始数据、哈希算法类型、原始数据的哈希值以及签名值作为输入。

// Verifier 接口定义了验证方法
type Verifier interface {
    Verify(message []byte, signature []byte) error
}

// rsaPublicKey 结构体包装了 rsa.PublicKey
type rsaPublicKey struct {
    *rsa.PublicKey
}

// Verify 方法实现了RSA-SHA256签名验证
func (r *rsaPublicKey) Verify(message []byte, signature []byte) error {
    // 1. 计算原始消息的SHA256哈希值
    h := sha256.New()
    h.Write(message)
    hashed := h.Sum(nil)

    // 2. 使用rsa.VerifyPKCS1v15进行验证
    // r.PublicKey 是用于验证的公钥
    // crypto.SHA256 指明签名时使用的哈希算法
    // hashed 是原始消息的哈希值
    // signature 是待验证的签名值
    return rsa.VerifyPKCS1v15(r.PublicKey, crypto.SHA256, hashed, signature)
}

// parsePublicKey 解析PEM编码的公钥字节
func parsePublicKey(pemBytes []byte) (Verifier, error) {
    block, _ := pem.Decode(pemBytes)
    if block == nil {
        return nil, errors.New("ssh: no PEM block found")
    }

    var rawkey interface{}
    switch block.Type {
    case "PUBLIC KEY":
        rsaPubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
        if err != nil {
            return nil, fmt.Errorf("failed to parse PKIX public key: %w", err)
        }
        if key, ok := rsaPubKey.(*rsa.PublicKey); ok {
            rawkey = key
        } else {
            return nil, fmt.Errorf("unsupported public key type in PKIX: %T", rsaPubKey)
        }
    default:
        return nil, fmt.Errorf("ssh: unsupported public key type %q", block.Type)
    }
    return newVerifierFromKey(rawkey)
}

// loadPublicKey 从文件加载并解析公钥
func loadPublicKey(path string) (Verifier, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read public key file: %w", err)
    }
    return parsePublicKey(data)
}

func newVerifierFromKey(k interface{}) (Verifier, error) {
    if t, ok := k.(*rsa.PublicKey); ok {
        return &rsaPublicKey{t}, nil
    }
    return nil, fmt.Errorf("ssh: unsupported key type %T for verification", k)
}

完整的Go语言实现示例

以下是一个整合了上述私钥签名和公钥验证逻辑的完整Go程序。它包括了密钥加载、数据签名、签名值Base64编码以及签名验证的全过程。

首先,请确保您有private.pem和public.pem文件在运行目录下。示例文件内容如下:

private.pem:

-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
-----END RSA PRIVATE KEY-----

public.pem:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
oYi+1hqp1fIekaxsyQIDAQAB
-----END PUBLIC KEY-----

main.go:

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "errors"
    "fmt"
    "log"
    "os"
)

// Signer 接口定义了签名方法
type Signer interface {
    Sign(data []byte) ([]byte, error)
}

// Verifier 接口定义了验证方法
type Verifier interface {
    Verify(message []byte, signature []byte) error
}

// rsaPrivateKey 结构体包装了 rsa.PrivateKey
type rsaPrivateKey struct {
    *rsa.PrivateKey
}

// Sign 方法实现了数据的RSA-SHA256签名
func (r *rsaPrivateKey) Sign(data []byte) ([]byte, error) {
    h := sha256.New()
    h.Write(data)
    hashed := h.Sum(nil)
    return rsa.SignPKCS1v15(rand.Reader, r.PrivateKey, crypto.SHA256, hashed)
}

// rsaPublicKey 结构体包装了 rsa.PublicKey
type rsaPublicKey struct {
    *rsa.PublicKey
}

// Verify 方法实现了RSA-SHA256签名验证
func (r *rsaPublicKey) Verify(message []byte, signature []byte) error {
    h := sha256.New()
    h.Write(message)
    hashed := h.Sum(nil)
    return rsa.VerifyPKCS1v15(r.PublicKey, crypto.SHA256, hashed, signature)
}

// parsePrivateKey 解析PEM编码的私钥字节
func parsePrivateKey(pemBytes []byte) (Signer, error) {
    block, _ := pem.Decode(pemBytes)
    if block == nil {
        return nil, errors.New("ssh: no PEM block found")
    }

    var rawkey interface{}
    switch block.Type {
    case "RSA PRIVATE KEY":
        rsaPrivKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
        if err != nil {
            return nil, fmt.Errorf("failed to parse PKCS#1 private key: %w", err)
        }
        rawkey = rsaPrivKey
    default:
        return nil, fmt.Errorf("ssh: unsupported private key type %q", block.Type)
    }
    return newSignerFromKey(rawkey)
}

// loadPrivateKey 从文件加载并解析私钥
func loadPrivateKey(path string) (Signer, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read private key file: %w", err)
    }
    return parsePrivateKey(data)
}

func newSignerFromKey(k interface{}) (Signer, error) {
    if t, ok := k.(*rsa.PrivateKey); ok {
        return &rsaPrivateKey{t}, nil
    }
    return nil, fmt.Errorf("ssh: unsupported key type %T for signing", k)
}

// parsePublicKey 解析PEM编码的公钥字节
func parsePublicKey(pemBytes []byte) (Verifier, error) {
    block, _ := pem.Decode(pemBytes)
    if block == nil {
        return nil, errors.New("ssh: no PEM block found")
    }

    var rawkey interface{}
    switch block.Type {
    case "PUBLIC KEY":
        rsaPubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
        if err != nil {
            return nil, fmt.Errorf("failed to parse PKIX public key: %w", err)
        }
        if key, ok := rsaPubKey.(*rsa.PublicKey); ok {
            rawkey = key
        } else {
                return nil, fmt.Errorf("unsupported public key type in PKIX: %T", rsaPubKey)
        }
    default:
        return nil, fmt.Errorf("ssh: unsupported public key type %q", block.Type)
    }
    return newVerifierFromKey(rawkey)
}

// loadPublicKey 从文件加载并解析公钥
func loadPublicKey(path string) (Verifier, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read public key file: %w", err)
    }
    return parsePublicKey(data)
}

func newVerifierFromKey(k interface{}) (Verifier, error) {
    if t, ok := k.(*rsa.PublicKey); ok {
        return &rsaPublicKey{t}, nil
    }
    return nil, fmt.Errorf("ssh: unsupported key type %T for verification", k)
}

func main() {
    // 1. 加载私钥进行签名
    signer, err := loadPrivateKey("private.pem")
    if err != nil {
        log.Fatalf("failed to load private key: %v", err)
    }

    messageToSign := "date: Thu, 05 Jan 2012 21:31:40 GMT"

以上就是Go语言中RSA-SHA数字签名与验证的正确实践的详细内容,更多请关注其它相关文章!


# git  # js  # cdn  # switch  # ai  # qq  # 字节  # 编码  # go语言  # go  # 奇门遁甲关键词排名  # 炸串店怎样推广营销产品  # 攀枝花网站的优化  # 全网营销线上推广陈经理  # 河北招商网站推广介绍  # 网站优化定做  # SEO目录排版  # 莞城seo培训  # 郑州网站建设九零后  # seo网站推广佛山  # 几个  # 这是  # 是一个  # 常以  # 资源管理  # 原始数据  # 实现了  # 装了  # 公钥  # 加载  # crypto  # yy  # 标准库 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: taptap防沉迷怎么解除 taptap解除健康系统限制说明【2025最新】  outlook中文官网入口地址 outlook官方中文版直达首页链接  Lar*el DB::listen 事件中的查询执行时间单位解析  css滚动区域卡顿如何改善_css滚动问题用will-change优化渲染  Win10如何恢复误删的快捷方式_Win10重建常用软件快捷方式  React中useState与局部变量:理解组件状态管理与渲染机制  KFC早餐时段怎么领特惠代码_KFC早餐订餐优惠代码获取与使用说明  优化 Python 函数中的条件逻辑:解决 if-else 嵌套与参数选择问题  为什么我的微信朋友圈看不到别人的更新_微信朋友圈更新显示异常解决方法  DLsite中文平台入口 DLsite官网内容在线查看  在Typer应用中优雅地处理和重组任意命令行参数  今日头条怎么同步内容到抖音_今日头条内容同步到抖音教程  J*aScript教程:根据元素文本内容动态设置背景色  绝地鸭卫平a核爆刀流玩法攻略  Odoo 16:在表单视图中基于当前记录动态修改Tree视图属性  如何在J*a中实现统一对象行为接口_项目大型化时的接口规范化  《北京人工智能产业白皮书(2025)》发布:全年核心产值预计突破 4500 亿元  Golang如何通过reflect获取匿名字段方法_Golang reflect匿名字段方法访问技巧  J*aScript DOM操作:高效清空列表元素的策略与实践  TikTok搜索不到用户发布内容怎么办 TikTok用户内容搜索优化方法  Fabric模组开发:自定义物品与物品组的现代管理方法  大麦的“候补”是什么意思 大麦候补购票规则【详解】  怎么在html里运行vbs脚本_html中运行vbs脚本方法【教程】  ACG动漫手机版官网入口 手机ACG动漫APP在线观看正版  向日葵客户端怎么进行远程CentOS控制_向日葵客户端远程CentOS控制操作教程  TikTok评论显示延迟如何处理 TikTok评论刷新优化方法  PDF文件体积过大处理_PDF压缩技巧详解  妖精动漫免费平台 妖精动漫官网资源观看网址  离线运行Go语言之旅:本地部署与GOPATH配置指南  Python中如何避免重复条件判断:利用数据结构实现动态逻辑  蛙漫官方正版入口 蛙漫网页在线全集免费观看  sublime如何优雅地处理行尾空格_sublime自动清理多余空白字符配置  J*aScript中在Map循环中检测并处理空数组元素  4399网页游戏电脑版全新入口 4399电脑端在线玩指南  在python-socketio事件处理器中安全访问Flask应用上下文  将HTML动态表格多行数据保存到Google Sheet的教程  马斯克:Optimus 人形机器人复数形式为 Optimi  Go语言中Map存储的结构体如何调用指针方法:深入解析与实践  漫蛙2正版漫画站 漫蛙2网页版快速访问入口  印象笔记如何设提醒任务防漏执行_印象笔记设提醒任务防漏执行【任务提醒】  C++如何操作注册表_Windows平台下C++读写注册表的API函数详解  mysql如何设置表访问权限_mysql表访问权限配置  J*aScript 字符串标签转换:使用正则表达式高效替换  知乎APP怎么管理已购盐选内容_知乎APP盐选内容购买记录与查看方法  如何使用Node.js csv 包按条件移除含空字段的CSV记录  谷歌google账号怎么注册账号 谷歌账号注册官方流程  如何高效处理PHP中的Excel数据导入导出?PortPHP/Spreadsheet助你轻松搞定!  Pygame教程:解决用户输入与游戏状态更新不同步问题  J*aScript动态修改指定div内所有a标签样式指南  Golang如何实现简单的Web表单_Golang表单提交与验证处理方法 

搜索