全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-690-7320

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

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数字签名与验证的正确实践的详细内容,更多请关注其它相关文章!


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


相关文章: C++如何实现线程池_C++11手动实现一个简单的固定大小线程池  解决Bootstrap卡片顶部边距导致背景图下移的问题  提升Kafka消费者健壮性:会话超时处理与消息处理语义  J*aScript类型检查_j*ascript代码规范  Lar*el头像管理:图片缩放与旧文件删除的最佳实践  Yandex官方入口网址 Yandex俄罗斯搜索引擎最新在线地址  J*aScript中在Map循环中检测并处理空数组元素  PHP表单提交后函数重复执行的解决方案:管理$_POST数据  C++如何实现一个装饰器模式_C++设计模式之动态地给对象添加额外职责  如何有效阻止外部脚本意外修改内联样式的高度属性  J*aScript DOM操作:高效清空列表元素的策略与实践  反效果?《战地6》免费试玩开启后玩家数不升反降  2025-2030年全球乘用车销量预测:新能源成增长主力  漫蛙MANWA漫画主页官方入口 漫蛙漫画最新在线阅读地址  俄罗斯浏览器官网直达链接 俄罗斯浏览器最新在线入口导航  12306怎么选座位选到安静区_12306选座安静区域选择策略  Win11怎么关闭快速启动_Win11彻底关机设置教程  零跑汽车11月交付量达70327台 实现连续9个月正增长  谷歌推RCS信息存档功能:公司可监控员工私密信息!  sublime如何配置Python开发环境_将sublime打造成轻量级Python IDE  新手怎么开始学化妆 零基础化妆入门教程  Yandex官网免登录入口_俄罗斯Yandex搜索引擎一键访问  Walmart退货API集成指南:PHP cURL实现与常见问题解析  火狐浏览器占用内存高卡顿怎么办 火狐浏览器性能优化设置技巧  初次安装JDK时环境变量如何正确配置_J*A_HOME与PATH设置规则讲解  JUnit5/Mockito:优雅测试内部依赖与异常处理的实践  css滚动区域卡顿如何改善_css滚动问题用will-change优化渲染  c++如何使用TBB库进行任务并行_c++ Intel线程构建模块  Node.js CSV 数据处理:基于字段值条件过滤整条记录的策略  KFC早餐时段怎么领特惠代码_KFC早餐订餐优惠代码获取与使用说明  msn官网入口地址手机版 msn官方网站手机最新链接  SteamMachine定价或为699美元 大家想入手吗?  CSS自定义字体样式被系统字体替换怎么办_font-face方式指定font-display控制渲染策略  Win11 BitLocker密码忘了怎么办 Win11找回BitLocker恢复密钥方法【解决】  照顾宝贝2小游戏免费秒玩入口  Mudbox图层蒙版怎么用_Mudbox图层蒙版数字雕刻应用技巧  谷歌浏览器浏览体验优化_谷歌浏览器新版直连永久可用提示  mcjs网页版流畅运行 mcjs低配电脑畅玩入口  如何在复杂的电商平台中优雅地管理共享资源并确保正确重定向,使用spryker-shop/resource-share-page模块助你一臂之力  excel如何生成目录 excel一键生成工作表目录超链接  ExcelARRAYTOTEXT函数怎么自定义分隔符输出数组文本_ARRAYTOTEXT实现动态生成SQL语句  Adobe PDF表单中利用J*aScript解析与格式化日期组件的教程  QQ邮箱电脑版登录入口_QQ邮箱官方网站登录平台  顺丰快件物流信息 官方网站查询入口  J*aScript中正确使用querySelectorAll与复杂CSS选择器  Lar*el Excel导入时生成自定义递增ID的策略与实践  Win10怎么设置静态IP地址 Win10手动配置IP地址步骤【指南】  Bilibili动漫最新防封地址发布-Bilibili动漫2025年最稳正版入口推荐  Angular Material 垂直步进器:实现底部到顶部排序的教程  MAC怎么在地图App里使用“四处看看”_MAC体验部分城市的3D实景街景 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。