mirror of
https://gitee.com/johng/gf
synced 2026-07-06 05:42:20 +08:00
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:
1
Makefile
1
Makefile
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
264
crypto/grsa/README.md
Normal 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
571
crypto/grsa/grsa.go
Normal 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)
|
||||
}
|
||||
1058
crypto/grsa/grsa_z_unit_test.go
Normal file
1058
crypto/grsa/grsa_z_unit_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user