在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
相关产品推荐
相关产品推荐

