
本文深入探讨了在go语言中使用rsa-sha算法进行数字签名和验证的正确方法。我们将详细介绍如何加载pem格式的rsa私钥和公钥,并基于`crypto/rsa`和`crypto/sha256`包实现数据的签名与验证过程。文章重点纠正了常见的验证逻辑错误,提供了结构清晰、可直接运行的示例代码,并强调了关键的安全实践,旨在帮助开发者构建健壮可靠的数字签名系统。
数字签名是保障数据完整性、认证性和不可否认性的重要密码学技术。它利用非对称加密算法(如RSA)和哈希函数(如SHA256)来创建。签名过程通常包括:对原始数据计算哈希值,然后使用发送方的私钥对哈希值进行加密(签名)。验证过程则相反:接收方使用发送方的公钥解密(验证)签名,得到原始哈希值,同时独立计算接收到数据的哈希值,比对两者是否一致。如果一致,则证明数据未被篡改且确实来自私钥的持有者。
Go标准库提供了强大的密码学支持,主要涉及以下几个包:
在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。
-----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程序。它包括了密钥加载、数据签名、签名值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中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号