讲师中心 微信公众号
AI工具推荐 视频效率加速

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

胖婷姑娘_3105

胖婷姑娘_3105

发布时间:2025-11-14 19:45:00

|

894人浏览过

|

来源于php中文网

原创

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。

立即学习“go语言免费学习笔记(深入)”;

-----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。

GitHub 人才猎手 (GitHub Talent Hunter)
GitHub 人才猎手 (GitHub Talent Hunter)

在GitHub上精准匹配技术人才,自动生成个性化触达话术,适用于招聘、技术合伙人寻找、猎头候选人交付等场景。

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

加载和解析公钥

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

签名验证逻辑

核心问题在于:签名验证不是解密操作。 原始代码中的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"

热门AI工具

更多
墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

2529

2023.09.06

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

4987

2023.09.25

go语言编程软件有哪些
go语言编程软件有哪些

go语言编程软件有Go编译器、Go开发环境、Go包管理器、Go测试框架、Go文档生成器、Go代码质量工具和Go性能分析工具等。本专题为大家提供go语言相关的文章、下载、课程内容,供大家免费下载体验。

642

2023.10.13

0基础如何学go语言
0基础如何学go语言

0基础学习Go语言需要分阶段进行,从基础知识到实践项目,逐步深入。php中文网给大家带来了go语言相关的教程以及文章,欢迎大家前来学习。

6745

2023.10.26

Go语言实现运算符重载有哪些方法
Go语言实现运算符重载有哪些方法

Go语言不支持运算符重载,但可以通过一些方法来模拟运算符重载的效果。使用函数重载来模拟运算符重载,可以为不同的类型定义不同的函数,以实现类似运算符重载的效果,通过函数重载,可以为不同的类型实现不同的操作。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2356

2024.02.23

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2584

2024.02.23

go语言开发工具大全
go语言开发工具大全

本专题整合了go语言开发工具大全,想了解更多相关详细内容,请阅读下面的文章。

5679

2025.06.11

go语言引用传递
go语言引用传递

本专题整合了go语言引用传递机制,想了解更多相关内容,请阅读专题下面的文章。

3637

2025.06.26

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

80

2026.09.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 20.8万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 4.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn