feat(‎crypto/grsa): Add RSA encryption and decryption function (#4571)

补充RSA加密解密功能
This pull request improves documentation and developer onboarding for
the project, with a particular focus on the RSA cryptography package and
general installation instructions. The main changes include the addition
of a comprehensive README for the `grsa` RSA package, updated
installation steps in both English and Chinese documentation, and minor
clarifications to documentation links.

**Documentation improvements:**

* Added a detailed `README.md` for the `crypto/grsa` package, including
features, security considerations, usage examples, API descriptions, key
format explanations, and error handling guidance.
* Updated the English (`README.MD`) and Chinese (`README.zh_CN.MD`)
documentation to include a clear installation section with `go get`
instructions for easier onboarding.
[[1]](diffhunk://#diff-01e6d9ffed056a02cae8d8a0ec5d476a64d017bf85c0d5a94bb23ca21f33f5aaR27-R32)
[[2]](diffhunk://#diff-c93759cb9a9500f20e551c741eb167fc72825fd638d36121357feb8253ce6ac1R27-R41)
* Clarified and improved documentation links in both English and Chinese
README files, including the addition of a link to the documentation
source and improved naming for the GoDoc/Go package documentation.
[[1]](diffhunk://#diff-01e6d9ffed056a02cae8d8a0ec5d476a64d017bf85c0d5a94bb23ca21f33f5aaR41)
[[2]](diffhunk://#diff-c93759cb9a9500f20e551c741eb167fc72825fd638d36121357feb8253ce6ac1R27-R41)

**Developer tooling:**

* Added a commented-out `go install` command for `golangci-lint` in the
`Makefile` to assist developers in setting up linting tools.

---------

Co-authored-by: hailaz <739476267@qq.com>
This commit is contained in:
Lance Add
2025-12-26 18:18:30 +08:00
committed by GitHub
parent 7daf916032
commit cb4681ce3e
6 changed files with 1909 additions and 1 deletions

View File

@ -6,6 +6,7 @@ tidy:
./.make_tidy.sh
# execute "golangci-lint" to check code style
# go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
.PHONY: lint
lint:
golangci-lint run -c .golangci.yml

View File

@ -24,6 +24,12 @@ English | [简体中文](README.zh_CN.MD)
A powerful framework for faster, easier, and more efficient project development.
## Installation
```bash
go get -u github.com/gogf/gf/v2
```
## Documentation
- Official Site: [https://goframe.org](https://goframe.org)
@ -32,6 +38,7 @@ A powerful framework for faster, easier, and more efficient project development.
- Mirror Site: [Github Pages](https://pages.goframe.org)
- Mirror Site: [Offline Docs](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
- Doc Source: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site)
## Contributors

View File

@ -24,6 +24,12 @@
一个强大的框架,为了更快、更轻松、更高效的项目开发。
## 安装
```bash
go get -u github.com/gogf/gf/v2
```
## 文档
- 官方网站: [https://goframe.org](https://goframe.org)
@ -31,7 +37,8 @@
- 国内镜像: [https://goframe.org.cn](https://goframe.org.cn)
- 镜像网站: [Github Pages](https://pages.goframe.org)
- 镜像网站: [离线文档](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
- Go包文档: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
- 文档源码: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site)
## 贡献者

264
crypto/grsa/README.md Normal file
View File

@ -0,0 +1,264 @@
# GoFrame RSA Package
Package `grsa` provides useful API for RSA encryption/decryption algorithms within the GoFrame framework.
## Features
- Generating RSA key pairs in PKCS#1 and PKCS#8 formats
- Encrypting and decrypting data with various key formats
- Handling Base64 encoded keys
- Detecting private key types
- Plaintext size validation
- **OAEP padding support (recommended for new applications)**
## Security Considerations
This package provides two padding schemes for RSA encryption:
### 1. PKCS#1 v1.5 (Legacy)
Used by `Encrypt*`, `DecryptPKCS1*`, `DecryptPKCS8*` functions.
⚠️ **Security Warning**: PKCS#1 v1.5 padding is considered less secure and vulnerable to padding oracle attacks. It is provided for backward compatibility with existing systems.
### 2. OAEP (Recommended)
Used by `EncryptOAEP*`, `DecryptOAEP*` functions.
**Recommended**: OAEP (Optimal Asymmetric Encryption Padding) provides better security guarantees and should be used for all new applications.
## Quick Start
### Basic Encryption/Decryption (OAEP - Recommended)
```go
package main
import (
"fmt"
"github.com/gogf/gf/v2/crypto/grsa"
)
func main() {
// Generate a default RSA key pair (2048 bits)
privateKey, publicKey, err := grsa.GenerateDefaultKeyPair()
if err != nil {
panic(err)
}
// Data to encrypt
plainText := []byte("Hello, World!")
// Encrypt with public key using OAEP (recommended)
cipherText, err := grsa.EncryptOAEP(plainText, publicKey)
if err != nil {
panic(err)
}
// Decrypt with private key using OAEP
decryptedText, err := grsa.DecryptOAEP(cipherText, privateKey)
if err != nil {
panic(err)
}
fmt.Println(string(decryptedText)) // Output: Hello, World!
}
```
### Legacy Encryption/Decryption (PKCS#1 v1.5)
```go
package main
import (
"fmt"
"github.com/gogf/gf/v2/crypto/grsa"
)
func main() {
// Generate a default RSA key pair (2048 bits)
privateKey, publicKey, err := grsa.GenerateDefaultKeyPair()
if err != nil {
panic(err)
}
// Data to encrypt
plainText := []byte("Hello, World!")
// Encrypt with public key (PKCS#1 v1.5 - legacy)
cipherText, err := grsa.Encrypt(plainText, publicKey)
if err != nil {
panic(err)
}
// Decrypt with private key
decryptedText, err := grsa.Decrypt(cipherText, privateKey)
if err != nil {
panic(err)
}
fmt.Println(string(decryptedText)) // Output: Hello, World!
}
```
### Working with Base64 Encoded Keys
```go
package main
import (
"encoding/base64"
"fmt"
"github.com/gogf/gf/v2/crypto/grsa"
)
func main() {
// Generate a key pair
privateKey, publicKey, err := grsa.GenerateDefaultKeyPair()
if err != nil {
panic(err)
}
// Encode keys to Base64
privateKeyBase64 := base64.StdEncoding.EncodeToString(privateKey)
publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKey)
// Data to encrypt
plainText := []byte("Hello, Base64 World!")
// Encrypt with Base64 encoded public key using OAEP (recommended)
cipherTextBase64, err := grsa.EncryptOAEPBase64(plainText, publicKeyBase64)
if err != nil {
panic(err)
}
// Decrypt with Base64 encoded private key using OAEP
decryptedText, err := grsa.DecryptOAEPBase64(cipherTextBase64, privateKeyBase64)
if err != nil {
panic(err)
}
fmt.Println(string(decryptedText)) // Output: Hello, Base64 World!
}
```
## Functions
### Key Generation
- `GenerateKeyPair(bits int)`: Generates a new RSA key pair with the given bits in PKCS#1 format
- `GenerateKeyPairPKCS8(bits int)`: Generates a new RSA key pair with the given bits in PKCS#8 format
- `GenerateDefaultKeyPair()`: Generates a new RSA key pair with default bits (2048) in PKCS#1 format
### OAEP Encryption/Decryption (Recommended)
- `EncryptOAEP(plainText, publicKey []byte)`: Encrypts data with public key using OAEP padding (SHA-256)
- `DecryptOAEP(cipherText, privateKey []byte)`: Decrypts data with private key using OAEP padding (SHA-256)
- `EncryptOAEPBase64(plainText []byte, publicKeyBase64 string)`: Encrypts data with OAEP and returns base64-encoded result
- `DecryptOAEPBase64(cipherTextBase64, privateKeyBase64 string)`: Decrypts base64-encoded OAEP data
- `EncryptOAEPWithHash(plainText, publicKey, label []byte, hash hash.Hash)`: Encrypts with custom hash function
- `DecryptOAEPWithHash(cipherText, privateKey, label []byte, hash hash.Hash)`: Decrypts with custom hash function
### General Encryption/Decryption (Legacy - PKCS#1 v1.5)
- `Encrypt(plainText, publicKey []byte)`: Encrypts data with public key (auto-detect format)
- `Decrypt(cipherText, privateKey []byte)`: Decrypts data with private key (auto-detect format)
- `EncryptBase64(plainText []byte, publicKeyBase64 string)`: Encrypts data with base64-encoded public key and returns base64-encoded result
- `DecryptBase64(cipherTextBase64, privateKeyBase64 string)`: Decrypts base64-encoded data with base64-encoded private key
### PKCS#1 Specific Functions (Legacy)
- `EncryptPKCS1(plainText, publicKey []byte)`: Encrypts data with PKCS#1 format public key
- `DecryptPKCS1(cipherText, privateKey []byte)`: Decrypts data with PKCS#1 format private key
- `EncryptPKCS1Base64(plainText []byte, publicKeyBase64 string)`: Encrypts data with PKCS#1 public key and returns base64-encoded result
- `DecryptPKCS1Base64(cipherTextBase64, privateKeyBase64 string)`: Decrypts base64-encoded data with PKCS#1 private key
### PKIX Specific Functions (Legacy)
PKIX (X.509) is the standard format for public keys, used with PKCS#8 private keys.
- `EncryptPKIX(plainText, publicKey []byte)`: Encrypts data with PKIX format public key
- `EncryptPKIXBase64(plainText []byte, publicKeyBase64 string)`: Encrypts data with PKIX public key and returns base64-encoded result
- `DecryptPKCS8(cipherText, privateKey []byte)`: Decrypts data with PKCS#8 format private key
- `DecryptPKCS8Base64(cipherTextBase64, privateKeyBase64 string)`: Decrypts base64-encoded data with PKCS#8 private key
### Deprecated Functions
The following functions are deprecated and will be removed in future versions:
- `EncryptPKCS8(plainText, publicKey []byte)`: Use `EncryptPKIX` instead
- `EncryptPKCS8Base64(plainText []byte, publicKeyBase64 string)`: Use `EncryptPKIXBase64` instead
### Utility Functions
- `GetPrivateKeyType(privateKey []byte)`: Detects the type of private key (PKCS#1 or PKCS#8)
- `GetPrivateKeyTypeBase64(privateKeyBase64 string)`: Detects the type of base64 encoded private key
- `ExtractPKCS1PublicKey(privateKey []byte)`: Extracts PKCS#1 public key from PKCS#1 private key
## Key Formats
The package supports two popular RSA key formats:
1. **PKCS#1**: Traditional RSA key format
- Private key PEM header: `-----BEGIN RSA PRIVATE KEY-----`
- Public key PEM header: `-----BEGIN RSA PUBLIC KEY-----`
2. **PKCS#8/PKIX**: More modern and flexible key format
- Private key PEM header: `-----BEGIN PRIVATE KEY-----`
- Public key PEM header: `-----BEGIN PUBLIC KEY-----`
Both formats are supported for encryption and decryption operations, with auto-detection capabilities for general functions.
### Technical Background: PKCS#8 vs PKIX
**PKCS#8** is a standard for **private keys** only, not public keys. Public keys use the **PKIX (X.509 SubjectPublicKeyInfo)** format.
| Format | Private Key PEM Header | Public Key PEM Header |
|--------|------------------------|----------------------|
| PKCS#1 | `RSA PRIVATE KEY` | `RSA PUBLIC KEY` |
| PKCS#8/PKIX | `PRIVATE KEY` | `PUBLIC KEY` |
When we refer to a "PKCS#8 key pair", it actually means:
- **Private key**: PKCS#8 format (RFC 5208)
- **Public key**: PKIX/SubjectPublicKeyInfo format (RFC 5280, X.509)
This is why the Go standard library provides `x509.MarshalPKCS8PrivateKey` for private keys but `x509.MarshalPKIXPublicKey` for public keys — there is no `MarshalPKCS8PublicKey` function.
The deprecated `EncryptPKCS8` function was a misnomer because encryption uses public keys, and public keys are in PKIX format, not PKCS#8. The correct function name is `EncryptPKIX`.
## Plaintext Size Limit
RSA encryption has a size limit based on key size and padding scheme.
### PKCS#1 v1.5 Padding (Legacy)
- **Max plaintext size = key_size_in_bytes - 11**
- For a 2048-bit key: max 245 bytes
- For a 4096-bit key: max 501 bytes
### OAEP Padding with SHA-256 (Recommended)
- **Max plaintext size = key_size_in_bytes - 2 × hash_size - 2**
- For a 2048-bit key with SHA-256: max 190 bytes
- For a 4096-bit key with SHA-256: max 446 bytes
If you need to encrypt larger data, consider using hybrid encryption (RSA + AES).
## Error Handling
All functions return descriptive errors that can be handled using the GoFrame error package (`gerror`). Errors typically include:
- Invalid key format
- Failed key parsing
- Plaintext too long
- Encryption/decryption failures
Always check for errors in production code to ensure robust handling of edge cases.
## Testing
Run the package tests with:
```bash
go test -v
```

571
crypto/grsa/grsa.go Normal file
View File

@ -0,0 +1,571 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package grsa provides useful API for RSA encryption/decryption algorithms.
//
// This package includes functionality for:
// - Generating RSA key pairs in PKCS#1 and PKCS#8 formats
// - Encrypting and decrypting data with various key formats
// - Handling Base64 encoded keys
// - Detecting private key types
//
// # Security Considerations
//
// This package provides two padding schemes for RSA encryption:
//
// 1. PKCS#1 v1.5 (legacy): Used by Encrypt*, DecryptPKCS1*, DecryptPKCS8* functions.
// This padding scheme is considered less secure and vulnerable to padding oracle attacks.
// It is provided for backward compatibility with existing systems.
//
// 2. OAEP (recommended): Used by EncryptOAEP*, DecryptOAEP* functions.
// OAEP (Optimal Asymmetric Encryption Padding) is the recommended padding scheme
// for new applications as it provides better security guarantees.
//
// For new implementations, prefer using OAEP functions (EncryptOAEP, DecryptOAEP, etc.).
package grsa
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"hash"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
const (
// DefaultRSAKeyBits is the default bit size for RSA key generation
DefaultRSAKeyBits = 2048
// KeyTypePKCS1 represents PKCS#1 format private key
KeyTypePKCS1 = "PKCS#1"
// KeyTypePKCS8 represents PKCS#8 format private key
KeyTypePKCS8 = "PKCS#8"
// PEM block types
pemTypeRSAPrivateKey = "RSA PRIVATE KEY" // PKCS#1 private key
pemTypePrivateKey = "PRIVATE KEY" // PKCS#8 private key
pemTypeRSAPublicKey = "RSA PUBLIC KEY" // PKCS#1 public key
pemTypePublicKey = "PUBLIC KEY" // PKIX public key
)
// Encrypt encrypts data with public key using PKCS#1 v1.5 padding (auto-detect format).
// The publicKey can be either PKCS#1 or PKCS#8 (PKIX) format.
//
// Note: RSA encryption has a size limit based on key size.
// For PKCS#1 v1.5 padding, max plaintext size = key_size_in_bytes - 11.
// For example, a 2048-bit key can encrypt at most 245 bytes.
//
// Security Warning: PKCS#1 v1.5 padding is vulnerable to padding oracle attacks.
// For new applications, consider using EncryptOAEP instead.
func Encrypt(plainText, publicKey []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid public key")
}
// Try PKCS#8 (PKIX) first
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
// Try PKCS#1
pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse public key")
}
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA public key")
}
// Validate plaintext size for PKCS#1 v1.5 padding
maxSize := rsaPub.Size() - 11
if len(plainText) > maxSize {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter,
"plaintext too long: max %d bytes for this key, got %d bytes", maxSize, len(plainText))
}
return rsa.EncryptPKCS1v15(rand.Reader, rsaPub, plainText)
}
// Decrypt decrypts data with private key using PKCS#1 v1.5 padding (auto-detect format).
// The privateKey can be either PKCS#1 or PKCS#8 format.
//
// Security Warning: PKCS#1 v1.5 padding is vulnerable to padding oracle attacks.
// For new applications, consider using DecryptOAEP instead.
func Decrypt(cipherText, privateKey []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid private key")
}
// Try PKCS#8 first
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
// Try PKCS#1
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse private key")
}
}
rsaPriv, ok := priv.(*rsa.PrivateKey)
if !ok {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA private key")
}
return rsa.DecryptPKCS1v15(rand.Reader, rsaPriv, cipherText)
}
// EncryptBase64 encrypts data with base64-encoded public key (auto-detect format)
// and returns base64-encoded result.
func EncryptBase64(plainText []byte, publicKeyBase64 string) (string, error) {
publicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode public key")
}
encrypted, err := Encrypt(plainText, publicKey)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptBase64 decrypts base64-encoded data with base64-encoded private key (auto-detect format).
func DecryptBase64(cipherTextBase64, privateKeyBase64 string) ([]byte, error) {
privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode private key")
}
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode cipher text")
}
return Decrypt(cipherText, privateKey)
}
// EncryptPKIX encrypts data with public key in PKIX (X.509) format.
// PKIX is the standard format for public keys, often referred to as "PKCS#8 public key".
//
// Note: RSA encryption has a size limit based on key size.
// For PKCS#1 v1.5 padding, max plaintext size = key_size_in_bytes - 11.
func EncryptPKIX(plainText, publicKey []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid public key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse PKIX public key")
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA public key")
}
// Validate plaintext size for PKCS#1 v1.5 padding
maxSize := rsaPub.Size() - 11
if len(plainText) > maxSize {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter,
"plaintext too long: max %d bytes for this key, got %d bytes", maxSize, len(plainText))
}
return rsa.EncryptPKCS1v15(rand.Reader, rsaPub, plainText)
}
// EncryptPKCS8 is an alias for EncryptPKIX for backward compatibility.
//
// Deprecated: Use EncryptPKIX instead. Public keys use PKIX format, not PKCS#8.
func EncryptPKCS8(plainText, publicKey []byte) ([]byte, error) {
return EncryptPKIX(plainText, publicKey)
}
// EncryptPKCS1 encrypts data with public key in PKCS#1 format.
//
// Note: RSA encryption has a size limit based on key size.
// For PKCS#1 v1.5 padding, max plaintext size = key_size_in_bytes - 11.
func EncryptPKCS1(plainText, publicKey []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid public key")
}
pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse PKCS#1 public key")
}
// Validate plaintext size for PKCS#1 v1.5 padding
maxSize := pub.Size() - 11
if len(plainText) > maxSize {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter,
"plaintext too long: max %d bytes for this key, got %d bytes", maxSize, len(plainText))
}
return rsa.EncryptPKCS1v15(rand.Reader, pub, plainText)
}
// DecryptPKCS8 decrypts data with private key by PKCS#8 format.
func DecryptPKCS8(cipherText, privateKey []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid private key")
}
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse PKCS#8 private key")
}
rsaPriv, ok := priv.(*rsa.PrivateKey)
if !ok {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA private key")
}
return rsa.DecryptPKCS1v15(rand.Reader, rsaPriv, cipherText)
}
// DecryptPKCS1 decrypts data with private key by PKCS#1 format.
func DecryptPKCS1(cipherText, privateKey []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid private key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse private key")
}
return rsa.DecryptPKCS1v15(rand.Reader, priv, cipherText)
}
// EncryptPKIXBase64 encrypts data with PKIX public key and returns base64-encoded result.
func EncryptPKIXBase64(plainText []byte, publicKeyBase64 string) (string, error) {
publicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode public key")
}
encrypted, err := EncryptPKIX(plainText, publicKey)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// EncryptPKCS8Base64 is an alias for EncryptPKIXBase64 for backward compatibility.
//
// Deprecated: Use EncryptPKIXBase64 instead.
func EncryptPKCS8Base64(plainText []byte, publicKeyBase64 string) (string, error) {
return EncryptPKIXBase64(plainText, publicKeyBase64)
}
// EncryptPKCS1Base64 encrypts data with PKCS#1 public key and returns base64-encoded result.
func EncryptPKCS1Base64(plainText []byte, publicKeyBase64 string) (string, error) {
publicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode public key")
}
encrypted, err := EncryptPKCS1(plainText, publicKey)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptPKCS8Base64 decrypts data with private key by PKCS#8 format and decode base64 input.
func DecryptPKCS8Base64(cipherTextBase64, privateKeyBase64 string) ([]byte, error) {
privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode private key")
}
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode cipher text")
}
return DecryptPKCS8(cipherText, privateKey)
}
// DecryptPKCS1Base64 decrypts base64-encoded data with PKCS#1 private key.
func DecryptPKCS1Base64(cipherTextBase64, privateKeyBase64 string) ([]byte, error) {
privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode private key")
}
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode cipher text")
}
return DecryptPKCS1(cipherText, privateKey)
}
// GetPrivateKeyType detects the type of private key (PKCS#1 or PKCS#8).
// It attempts to parse the key in both formats to determine the actual type.
func GetPrivateKeyType(privateKey []byte) (string, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return "", gerror.NewCode(gcode.CodeInvalidParameter, "invalid private key")
}
// Try PKCS#1 first
_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err == nil {
return KeyTypePKCS1, nil
}
// Try PKCS#8
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err == nil {
if _, ok := priv.(*rsa.PrivateKey); ok {
return KeyTypePKCS8, nil
}
return "", gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA private key")
}
return "", gerror.NewCode(gcode.CodeInvalidParameter, "unknown private key format")
}
// GetPrivateKeyTypeBase64 detects the type of base64 encoded private key (PKCS#1 or PKCS#8).
func GetPrivateKeyTypeBase64(privateKeyBase64 string) (string, error) {
privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode private key")
}
return GetPrivateKeyType(privateKey)
}
// GenerateKeyPair generates a new RSA key pair with the given bits.
func GenerateKeyPair(bits int) (privateKey, publicKey []byte, err error) {
// Generate private key
privKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, gerror.WrapCode(gcode.CodeInternalError, err, "failed to generate rsa key")
}
// Validate private key
err = privKey.Validate()
if err != nil {
return nil, nil, gerror.WrapCode(gcode.CodeInternalError, err, "failed to validate rsa key")
}
// Marshal private key to PKCS#1 format
privKeyBytes := x509.MarshalPKCS1PrivateKey(privKey)
privateKey = pem.EncodeToMemory(&pem.Block{
Type: pemTypeRSAPrivateKey,
Bytes: privKeyBytes,
})
// Generate PKCS#1 public key
pubKeyBytes := x509.MarshalPKCS1PublicKey(&privKey.PublicKey)
publicKey = pem.EncodeToMemory(&pem.Block{
Type: pemTypeRSAPublicKey,
Bytes: pubKeyBytes,
})
return privateKey, publicKey, nil
}
// GenerateKeyPairPKCS8 generates a new RSA key pair with the given bits in PKCS#8 format.
func GenerateKeyPairPKCS8(bits int) (privateKey, publicKey []byte, err error) {
// Generate private key
privKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, gerror.WrapCode(gcode.CodeInternalError, err, "failed to generate rsa key")
}
// Validate private key
err = privKey.Validate()
if err != nil {
return nil, nil, gerror.WrapCode(gcode.CodeInternalError, err, "failed to validate rsa key")
}
// Marshal private key to PKCS#8 format
privKeyBytes, err := x509.MarshalPKCS8PrivateKey(privKey)
if err != nil {
return nil, nil, gerror.WrapCode(gcode.CodeInternalError, err, "failed to marshal private key to PKCS#8")
}
privateKey = pem.EncodeToMemory(&pem.Block{
Type: pemTypePrivateKey,
Bytes: privKeyBytes,
})
// Generate public key
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return nil, nil, gerror.WrapCode(gcode.CodeInternalError, err, "failed to marshal public key")
}
publicKey = pem.EncodeToMemory(&pem.Block{
Type: pemTypePublicKey,
Bytes: pubKeyBytes,
})
return privateKey, publicKey, nil
}
// GenerateDefaultKeyPair generates a new RSA key pair with default bits (2048).
func GenerateDefaultKeyPair() (privateKey, publicKey []byte, err error) {
return GenerateKeyPair(DefaultRSAKeyBits)
}
// ExtractPKCS1PublicKey extracts PKCS#1 public key from private key.
func ExtractPKCS1PublicKey(privateKey []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid private key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse private key")
}
pubKeyBytes := x509.MarshalPKCS1PublicKey(&priv.PublicKey)
return pem.EncodeToMemory(&pem.Block{
Type: pemTypeRSAPublicKey,
Bytes: pubKeyBytes,
}), nil
}
// ============================================================================
// OAEP Encryption/Decryption Functions (Recommended for new applications)
// ============================================================================
// EncryptOAEP encrypts data with public key using OAEP padding (auto-detect format).
// The publicKey can be either PKCS#1 or PKCS#8 (PKIX) format.
// Uses SHA-256 as the hash function by default.
//
// OAEP (Optimal Asymmetric Encryption Padding) is more secure than PKCS#1 v1.5
// and is recommended for new applications.
//
// Note: For OAEP with SHA-256, max plaintext size = key_size_in_bytes - 2*32 - 2.
// For a 2048-bit key, this is 190 bytes.
func EncryptOAEP(plainText, publicKey []byte) ([]byte, error) {
return EncryptOAEPWithHash(plainText, publicKey, nil, sha256.New())
}
// EncryptOAEPWithHash encrypts data with public key using OAEP padding with custom hash.
// The publicKey can be either PKCS#1 or PKCS#8 (PKIX) format.
// The label parameter can be nil for most use cases.
// The hash parameter specifies the hash function to use (e.g., sha256.New()).
func EncryptOAEPWithHash(plainText, publicKey, label []byte, hash hash.Hash) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid public key")
}
// Try PKCS#8 (PKIX) first
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
// Try PKCS#1
pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse public key")
}
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA public key")
}
// Validate plaintext size for OAEP padding
// maxSize = keySize - 2*hashSize - 2
maxSize := rsaPub.Size() - 2*hash.Size() - 2
if len(plainText) > maxSize {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter,
"plaintext too long: max %d bytes for this key with OAEP, got %d bytes", maxSize, len(plainText))
}
return rsa.EncryptOAEP(hash, rand.Reader, rsaPub, plainText, label)
}
// DecryptOAEP decrypts data with private key using OAEP padding (auto-detect format).
// The privateKey can be either PKCS#1 or PKCS#8 format.
// Uses SHA-256 as the hash function by default.
func DecryptOAEP(cipherText, privateKey []byte) ([]byte, error) {
return DecryptOAEPWithHash(cipherText, privateKey, nil, sha256.New())
}
// DecryptOAEPWithHash decrypts data with private key using OAEP padding with custom hash.
// The privateKey can be either PKCS#1 or PKCS#8 format.
// The label parameter must match the label used during encryption (nil if not used).
// The hash parameter must match the hash function used during encryption.
func DecryptOAEPWithHash(cipherText, privateKey, label []byte, hash hash.Hash) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid private key")
}
// Try PKCS#8 first
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
// Try PKCS#1
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to parse private key")
}
}
rsaPriv, ok := priv.(*rsa.PrivateKey)
if !ok {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "not an RSA private key")
}
return rsa.DecryptOAEP(hash, rand.Reader, rsaPriv, cipherText, label)
}
// EncryptOAEPBase64 encrypts data with public key using OAEP padding
// and returns base64-encoded result.
func EncryptOAEPBase64(plainText []byte, publicKeyBase64 string) (string, error) {
publicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode public key")
}
encrypted, err := EncryptOAEP(plainText, publicKey)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptOAEPBase64 decrypts base64-encoded data with private key using OAEP padding.
func DecryptOAEPBase64(cipherTextBase64, privateKeyBase64 string) ([]byte, error) {
privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode private key")
}
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err, "failed to decode cipher text")
}
return DecryptOAEP(cipherText, privateKey)
}

File diff suppressed because it is too large Load Diff