首页 > 后端开发 > Golang > 正文

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

聖光之護
发布: 2025-11-14 19:45:00
原创
839人浏览过

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。

怪兽AI数字人
怪兽AI数字人

数字人短视频创作,数字人直播,实时驱动数字人

怪兽AI数字人 44
查看详情 怪兽AI数字人
-----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"
登录后复制

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

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号