You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Golang中实现RSA/ECB/NoPadding字节数组解密的问题

解决Go中实现RSA无填充(对应Java RSA/ECB/NoPadding)解密的问题

Go的crypto/rsa标准库没有直接提供无填充的RSA解密接口,而Java的RSA/ECB/NoPadding本质是原始RSA解密——不对密文做任何填充处理,直接执行模幂运算c^d mod n(其中c是密文,d是私钥指数,n是模)。我们可以基于Go的math/big包手动实现这个逻辑。

实现步骤

1. 解析Java私钥

首先需要将Java使用的私钥(通常是PKCS1或PKCS8格式)解析为Go的rsa.PrivateKey结构:

解析PKCS8格式私钥(常见于Java导出的标准私钥)

import (
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "os"
)

func LoadPKCS8PrivateKey(path string) (*rsa.PrivateKey, error) {
    keyData, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read private key file failed: %w", err)
    }

    pemBlock, _ := pem.Decode(keyData)
    if pemBlock == nil || pemBlock.Type != "PRIVATE KEY" {
        return nil, fmt.Errorf("invalid PKCS8 private key PEM")
    }

    rawKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
    if err != nil {
        return nil, fmt.Errorf("parse PKCS8 private key failed: %w", err)
    }

    rsaKey, ok := rawKey.(*rsa.PrivateKey)
    if !ok {
        return nil, fmt.Errorf("key is not an RSA private key")
    }
    return rsaKey, nil
}

解析PKCS1格式私钥(Java的RSAPrivateKey格式)

如果私钥是PKCS1格式(PEM头为RSA PRIVATE KEY),替换解析逻辑:

func LoadPKCS1PrivateKey(path string) (*rsa.PrivateKey, error) {
    keyData, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read private key file failed: %w", err)
    }

    pemBlock, _ := pem.Decode(keyData)
    if pemBlock == nil || pemBlock.Type != "RSA PRIVATE KEY" {
        return nil, fmt.Errorf("invalid PKCS1 private key PEM")
    }

    rsaKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
    if err != nil {
        return nil, fmt.Errorf("parse PKCS1 private key failed: %w", err)
    }
    return rsaKey, nil
}

2. 实现无填充RSA解密函数

直接基于大数运算实现原始RSA解密,对齐Java的RSA/ECB/NoPadding行为:

import (
    "crypto/rsa"
    "fmt"
    "math/big"
)

// RSADecryptNoPadding 实现无填充RSA解密,完全对应Java的RSA/ECB/NoPadding
func RSADecryptNoPadding(privateKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
    // 校验密文长度:必须等于密钥模的字节数(如2048位密钥对应256字节)
    keySize := privateKey.Size()
    if len(ciphertext) != keySize {
        return nil, fmt.Errorf("ciphertext length mismatch: expected %d bytes, got %d", keySize, len(ciphertext))
    }

    // 将密文转换为大数
    cipherBig := new(big.Int).SetBytes(ciphertext)
    // 执行模幂运算:c^d mod n
    plainBig := new(big.Int).Exp(cipherBig, privateKey.D, privateKey.N)

    // 将大数转换为字节数组,并补前导零至密钥长度(和Java解密输出对齐)
    plainBytes := plainBig.Bytes()
    paddedPlain := make([]byte, keySize)
    copy(paddedPlain[keySize-len(plainBytes):], plainBytes)

    return paddedPlain, nil
}

3. 调用示例

func main() {
    // 加载私钥(根据实际格式选择PKCS1或PKCS8的加载函数)
    privateKey, err := LoadPKCS8PrivateKey("java_private_key.pem")
    if err != nil {
        panic(err)
    }

    // 待解密的字节数组(和Java端传入的密文完全一致)
    ciphertext := []byte{/* 替换为你的待解密数据 */}

    // 执行解密
    plaintext, err := RSADecryptNoPadding(privateKey, ciphertext)
    if err != nil {
        panic(err)
    }

    // 提取用户名和密码(根据你的业务逻辑处理,比如按固定长度截取)
    // 示例:假设前16字节是用户名,后16字节是密码
    username := string(plaintext[:16])
    password := string(plaintext[16:32])
    fmt.Printf("用户名: %s\n密码: %s\n", username, password)
}

关键注意事项

  • 密文长度必须严格等于密钥模的字节数,否则Java和Go都会解密失败。
  • 解密后的字节数组会保留前导零(和Java行为一致),需要根据你的业务规则提取有效数据。
  • 如果私钥是从Java代码中导出的,注意确认私钥格式(PKCS1或PKCS8),避免解析错误。

内容的提问来源于stack exchange,提问作者Ming

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:47:35