纯RSA?或许混合加密才是更好的解决方案
前言
在之前的文章【公钥密码算法之RSA】中介绍了RSA
加密的原理,但是讲得比较偏向于底层数学,没有向大家演示过如何使用高级语言进行加密解密,也没有介绍在实际过程使用该方式加密数据存在的一些问题,本文为此做出补充。
加密解密数据
以使用Golang
作为示例:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
type CryptoService struct {
}
const RSA_LEN int = 2048
// 生成RSA密钥对,并将公钥和私钥分别转为Base64字符串
func (service *CryptoService) GenerateRSAKeyPairBase64() (string, string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_LEN)
if err != nil {
return "", "", fmt.Errorf("error generating RSA key: %v", err)
}
//私钥转为 pem 字节数组
privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyDER,
})
privateKeyBase64 := base64.StdEncoding.EncodeToString(privateKeyPEM)
// 拆分出私钥
publicKey := &privateKey.PublicKey
// 转为 pem 数组
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", "", fmt.Errorf("error marshaling public key: %v", err)
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyDER,
})
publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKeyPEM)
return publicKeyBase64, privateKeyBase64, nil
}
// 使用公钥Base64字符串加密数据
func (service *CryptoService) EncryptWithPublicKeyBase64(publicKeyBase64, originData string) (string, error) {
// 先解码为字节数组
publicKeyPEM, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", fmt.Errorf("error decoding public key: %v", err)
}
// 从字节数组还原
block, _ := pem.Decode(publicKeyPEM)
if block == nil || block.Type != "PUBLIC KEY" {
return "", fmt.Errorf("invalid public key PEM")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("error parsing public key: %v", err)
}
// 断言是 rsa 公钥类型
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return "", fmt.Errorf("not an RSA public key")
}
// 加密成字节数组
encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPublicKey, []byte(originData), nil)
if err != nil {
return "", fmt.Errorf("error encrypting data: %v", err)
}
encryptedBase64 := base64.StdEncoding.EncodeToString(encryptedData)
return encryptedBase64, nil
}
// 使用私钥Base64字符串解密数据
func (service *CryptoService) DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64 string) (string, error) {
privateKeyPEM, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", fmt.Errorf("error decoding private key: %v", err)
}
block, _ := pem.Decode(privateKeyPEM)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return "", fmt.Errorf("invalid private key PEM")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("error parsing private key: %v", err)
}
// 将原先加密的base64数据转为加密的字节数组
encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", fmt.Errorf("error decoding encrypted data: %v", err)
}
// 解密成字节数组
decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptedData, nil)
if err != nil {
return "", fmt.Errorf("error decrypting data: %v", err)
}
// 字节转为字符串
return string(decryptedData), nil
}
func main() {
service := CryptoService{}
myData := "Hello RSA"
// 生成密钥对
pubBase64, priBase64, err := service.GenerateRSAKeyPairBase64()
if err != nil {
fmt.Printf("GenerateRSAKeyPairBase64 fail:%v", err)
return
}
// 使用公钥加密
encryptedBase64, err := service.EncryptWithPublicKeyBase64(pubBase64, myData)
if err != nil {
fmt.Printf("EncryptWithPublicKeyBase64 fail:%v", err)
return
}
fmt.Printf("encryptedBase64:%v\n", encryptedBase64)
// 使用私钥解密
decryptedData, err := service.DecryptWithPrivateKeyBase64(priBase64, encryptedBase64)
if err != nil {
fmt.Printf("DecryptWithPrivateKeyBase64 fail:%v", err)
return
}
fmt.Printf("decryptedData:%v\n", decryptedData)
}
运行上述代码,我们会得到类似的输出:
8sISH0kAwLdwCS9dBvUn5cCJBU50hMsGQNw2diLkQa35zPlJOV3op/We9HQguw/9RP+j8/HA4blAgxbEALZV6zfNsfLuTlkYvpfh42TjfCuIJaRJh/dHEcv52kgejbbLBLZ/hJm8bq1JBmnrFrq78nBTB71dfrF3ZA8vdUwkIT6ISHAiD4jMdKJwSo9Vg==
decryptedData:Hello RSA
长数据表现
好,我们继续来一个比较实际的例子,待加密的字符串是比较常规的:
myData := `
Never give up, Never lose hope. Always have faith, It allows you to cope.
Trying times will pass, As they always do.
Just have patience, Your dreams will come true.
So put on a smile, You'll live through your pain.
Know it will pass, And strength you will gain.
`
很遗憾,我们会得到下面的错误:
EncryptWithPublicKeyBase64 fail:error encrypting data: crypto/rsa: message too long for RSA key size
这是因为对于RSA加密,由于其数学原理,能够直接加密的数据长度是由密钥的长度决定的,具体来说,可加密数据的最大长度大致等于密钥长度减去一些固定的开销(通常为11或更多字节,取决于填充方案)。例如,一个1024位的RSA密钥实际上只能加密大约1024位减去开销后的数据位。
通常我们可以通过下面的几种方法来解决:
- 增大RSA密钥长度:简单粗暴,既然用户要加密的数据比较大,那我直接把密钥长度加大就行了,但是这种方法治标不治本,用户要加密的数据可能是无限长的,我们总不可能定义一个超长的密钥长度吧?而且密钥越长,加密解密的时间也会对应增加。
- 分块加密:如果数据过长,可以将数据分割成多个小块,每个块的大小都保证在RSA密钥允许的加密范围内,然后分别对这些块进行加密。
- 混合加密:RSA加密一个对称密钥(比如AES密钥),然后用这个对称密钥去加密实际的数据内容。这样做的好处是,RSA仅用于加密一个小的密钥,而对称加密算法(如AES)则用于快速加密大量数据。接收方首先用RSA私钥解密出对称密钥,再用这个密钥解密数据内容,这种方式结合了RSA的安全性和对称加密的高效性。
下面演示后面两种方法,并对其性能做出对比。
改进方案-分块加密
我们代码中使用了OAEP
作为填充方案,而且采用SHA-256
,那么这个开销将会是大约62字节,我们将开销放宽到80字节,分块的长度是密钥长度减去该开销长度,其他代码不变,只要修改加密和解密部分的代码
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
type CryptoService struct {
}
const RSA_LEN int = 2048
// 生成RSA密钥对,并将公钥和私钥分别转为Base64字符串
func (service *CryptoService) GenerateRSAKeyPairBase64() (string, string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_LEN)
if err != nil {
return "", "", fmt.Errorf("error generating RSA key: %v", err)
}
//私钥转为 pem 字节数组
privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyDER,
})
privateKeyBase64 := base64.StdEncoding.EncodeToString(privateKeyPEM)
// 拆分出私钥
publicKey := &privateKey.PublicKey
// 转为 pem 数组
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", "", fmt.Errorf("error marshaling public key: %v", err)
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyDER,
})
publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKeyPEM)
return publicKeyBase64, privateKeyBase64, nil
}
// 使用公钥Base64字符串加密数据
func (service *CryptoService) EncryptWithPublicKeyBase64(publicKeyBase64, originData string) (string, error) {
// 先解码为字节数组
publicKeyPEM, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", fmt.Errorf("error decoding public key: %v", err)
}
// 从字节数组还原
block, _ := pem.Decode(publicKeyPEM)
if block == nil || block.Type != "PUBLIC KEY" {
return "", fmt.Errorf("invalid public key PEM")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("error parsing public key: %v", err)
}
// 断言是 rsa 公钥类型
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return "", fmt.Errorf("not an RSA public key")
}
const OVERHEAD = 80
chunkLen := rsaPublicKey.N.BitLen()/8 - OVERHEAD
// 按照长度切分(个数向上取整)
chunks := split([]byte(originData), chunkLen)
// 分块加密并拼接
buffer := bytes.NewBufferString("")
for _, chunk := range chunks {
bytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPublicKey, chunk, nil)
if err != nil {
return "", err
}
buffer.Write(bytes)
}
return base64.StdEncoding.EncodeToString(buffer.Bytes()), nil
}
func split(buf []byte, lim int) [][]byte {
var chunk []byte
chunks := make([][]byte, 0, len(buf)/lim+1)
for len(buf) >= lim {
chunk, buf = buf[:lim], buf[lim:]
chunks = append(chunks, chunk)
}
if len(buf) > 0 {
chunks = append(chunks, buf[:len(buf)])
}
return chunks
}
// 使用私钥Base64字符串解密数据
func (service *CryptoService) DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64 string) (string, error) {
privateKeyPEM, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", fmt.Errorf("error decoding private key: %v", err)
}
block, _ := pem.Decode(privateKeyPEM)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return "", fmt.Errorf("invalid private key PEM")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("error parsing private key: %v", err)
}
// 将原先加密的base64数据转为加密的字节数组
encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", fmt.Errorf("error decoding encrypted data: %v", err)
}
// 分块解密
chunkLen := privateKey.N.BitLen() / 8
chunks := split(encryptedData, chunkLen)
buffer := bytes.NewBufferString("")
for _, chunk := range chunks {
decrypted, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, chunk, nil)
if err != nil {
return "", err
}
buffer.Write(decrypted)
}
return buffer.String(), err
}
func main() {
service := CryptoService{}
myData := `
Never give up, Never lose hope. Always have faith, It allows you to cope.
Trying times will pass, As they always do.
Just have patience, Your dreams will come true.
So put on a smile, You'll live through your pain.
Know it will pass, And strength you will gain.
`
// 生成密钥对
pubBase64, priBase64, err := service.GenerateRSAKeyPairBase64()
if err != nil {
fmt.Printf("GenerateRSAKeyPairBase64 fail:%v", err)
return
}
// 使用公钥加密
encryptedBase64, err := service.EncryptWithPublicKeyBase64(pubBase64, myData)
if err != nil {
fmt.Printf("EncryptWithPublicKeyBase64 fail:%v", err)
return
}
fmt.Printf("encryptedBase64:%v\n", encryptedBase64)
// 使用私钥解密
decryptedData, err := service.DecryptWithPrivateKeyBase64(priBase64, encryptedBase64)
if err != nil {
fmt.Printf("DecryptWithPrivateKeyBase64 fail:%v", err)
return
}
fmt.Printf("decryptedData:%v\n", decryptedData)
}
改进方案-混合加密
这里采用AES
对称,加密过程,先随机产生一个AES
密钥,然后使用该密钥对数据加密,得到encryptedData1
,然后再使用RSA
公钥加密产生的AES
密钥,得到encryptedData2
,然后我们借助某些分界符把这两个加密数据拼接到一起。
解密过程则要先从加密的数据中分离出加密后的AES
密钥,使用RSA
解密出AES
密钥,再使用该密钥对剩余的加密内容解密,即可得到原始数据。
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
)
const RSA_LEN int = 2048
const AES_LEN int = 32
var KEY_DATA_SEPARATOR [3]byte = [3]byte{0x00, 0x00, 0x00}
type MixedCryptoService struct{}
// 生成RSA密钥对,并将公钥和私钥分别转为Base64字符串
func (service *MixedCryptoService) GenerateRSAKeyPairBase64() (string, string, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, RSA_LEN)
if err != nil {
return "", "", fmt.Errorf("error generating RSA key: %v", err)
}
//私钥转为 pem 字节数组
privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyDER,
})
privateKeyBase64 := base64.StdEncoding.EncodeToString(privateKeyPEM)
// 拆分出私钥
publicKey := &privateKey.PublicKey
// 转为 pem 数组
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", "", fmt.Errorf("error marshaling public key: %v", err)
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyDER,
})
publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKeyPEM)
return publicKeyBase64, privateKeyBase64, nil
}
// 使用公钥Base64字符串加密数据(采用混合加密的方式,即结合AES对称加密,否则直接采用 RSA 会收到加密数据长度的限制,虽然也可以使用分组加密的方式)
func (service *MixedCryptoService) EncryptWithPublicKeyBase64(publicKeyBase64, originData string) (string, error) {
// 先解码为字节数组
publicKeyPEM, err := base64.StdEncoding.DecodeString(publicKeyBase64)
if err != nil {
return "", fmt.Errorf("error decoding public key: %v", err)
}
// 从字节数组还原
block, _ := pem.Decode(publicKeyPEM)
if block == nil || block.Type != "PUBLIC KEY" {
return "", fmt.Errorf("invalid public key PEM")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("error parsing public key: %v", err)
}
// 断言是 rsa 公钥类型
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return "", fmt.Errorf("not an RSA public key")
}
// 生成一个随机的对称密钥
symmetricKey := make([]byte, AES_LEN)
if _, err = rand.Read(symmetricKey); err != nil {
return "", err
}
// 使用对称密钥和AES-GCM模式加密数据
aesBlock, err := aes.NewCipher(symmetricKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(aesBlock)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return "", err
}
encryptedData := gcm.Seal(nonce, nonce, []byte(originData), nil)
// 使用RSA公钥加密对称密钥
encryptedSymmetricKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPublicKey, symmetricKey, nil)
if err != nil {
return "", err
}
// 将加密后的对称密钥和加密后的数据组合(分隔符之前是加密密钥,分隔符后是加密数据)
separator := KEY_DATA_SEPARATOR[:]
dataBytes := append(append(encryptedSymmetricKey, separator...), encryptedData...)
// 字节转 base64
encryptedBase64 := base64.StdEncoding.EncodeToString(dataBytes)
return encryptedBase64, nil
}
// 使用私钥Base64字符串解密数据
func (service *MixedCryptoService) DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64 string) (string, error) {
// 密钥先转为字节切片
privateKeyPEM, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return "", fmt.Errorf("error decoding private key: %v", err)
}
block, _ := pem.Decode(privateKeyPEM)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return "", fmt.Errorf("invalid private key PEM")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("error parsing private key: %v", err)
}
//加密数据接着转为字节切片
decodeString, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", fmt.Errorf("error DecodeString privateKeyBase64: %v", err)
}
// 分离出 AES 和 加密数据
separator := KEY_DATA_SEPARATOR[:]
separatorIndex := bytes.Index(decodeString, separator)
if separatorIndex == -1 {
return "", fmt.Errorf("separator not found")
}
// 分离加密后的对称密钥和加密后的数据
encryptedSymmetricKey := decodeString[:separatorIndex]
encryptedData := decodeString[separatorIndex+len(separator):]
// 将AES key解密成字节数组
symmetricKeyBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptedSymmetricKey, nil)
if err != nil {
return "", err
}
// 使用解密后的对称密钥和AES-GCM模式解密数据
aesBlock, aesErr := aes.NewCipher(symmetricKeyBytes)
if aesErr != nil {
return "", aesErr
}
gcm, err := cipher.NewGCM(aesBlock)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
nonce, ciphertext := encryptedData[:nonceSize], encryptedData[nonceSize:]
open, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
// 字节转为字符串
return string(open), nil
}
func main() {
service := MixedCryptoService{}
myData := `
Never give up, Never lose hope. Always have faith, It allows you to cope.
Trying times will pass, As they always do.
Just have patience, Your dreams will come true.
So put on a smile, You'll live through your pain.
Know it will pass, And strength you will gain.
`
// 生成密钥对
pubBase64, priBase64, err := service.GenerateRSAKeyPairBase64()
if err != nil {
fmt.Printf("GenerateRSAKeyPairBase64 fail:%v", err)
return
}
// 使用公钥加密
encryptedBase64, err := service.EncryptWithPublicKeyBase64(pubBase64, myData)
if err != nil {
fmt.Printf("EncryptWithPublicKeyBase64 fail:%v", err)
return
}
fmt.Printf("encryptedBase64:%v\n", encryptedBase64)
// 使用私钥解密
decryptedData, err := service.DecryptWithPrivateKeyBase64(priBase64, encryptedBase64)
if err != nil {
fmt.Printf("DecryptWithPrivateKeyBase64 fail:%v", err)
return
}
fmt.Printf("decryptedData:%v\n", decryptedData)
}
性能对比
在对比环节,为了公平性,我们使用同一组密钥,对同一个字符串加密和解密100次,分别统计总用时:
package main
import (
"codeSandbox/inner_test/mixed_encryption"
"codeSandbox/inner_test/onlyRSA"
"fmt"
"math/rand"
"time"
)
// 可见ASCII字符集(不包括空格)
const visibleChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
// 随机字符串生成函数
func generateRandomVisibleString(length int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, length)
for i := range b {
b[i] = visibleChars[rand.Intn(len(visibleChars))]
}
return string(b)
}
func main() {
mixedCryptoService := mixed_encryption.MixedCryptoService{}
onlyRSACryptoService := onlyRSA.CryptoService{}
// 生成密钥(二者共用)
publicKeyBase64, privateKeyBase64, err := mixedCryptoService.GenerateRSAKeyPairBase64()
if err != nil {
fmt.Printf("GenerateRSAKeyPairBase64 fail:%v\n", err)
}
// 产生随机原始数据
myData := generateRandomVisibleString(1024)
fmt.Println("Data prepare finish")
iterations := 1000
// 统计纯 RSA 加密花费时间
startT := time.Now()
for i := 0; i < iterations; i++ {
encryptedBase64, _ := onlyRSACryptoService.EncryptWithPublicKeyBase64(publicKeyBase64, myData)
onlyRSACryptoService.DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64)
}
// 结束时间打点
tc := time.Since(startT)
encryptedBase64, _ := onlyRSACryptoService.EncryptWithPublicKeyBase64(publicKeyBase64, myData)
decryptData, _ := onlyRSACryptoService.DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64)
// 判断 纯 RSA 解密正确性
if decryptData != myData {
fmt.Println("onlyRSACryptoService DecryptWithPrivateKeyBase64 fail")
}
fmt.Printf("onlyRSACryptoService time cost:%v ms\n", tc.Milliseconds())
// 统计混合加密花费时间
startT = time.Now()
for i := 0; i < iterations; i++ {
encryptedBase64, _ := mixedCryptoService.EncryptWithPublicKeyBase64(publicKeyBase64, myData)
mixedCryptoService.DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64)
}
// 结束时间打点
tc = time.Since(startT)
encryptedBase64, _ = mixedCryptoService.EncryptWithPublicKeyBase64(publicKeyBase64, myData)
decryptData, _ = mixedCryptoService.DecryptWithPrivateKeyBase64(privateKeyBase64, encryptedBase64)
// 判断混合加密解密正确性
if decryptData != myData {
fmt.Println("mixedCryptoService DecryptWithPrivateKeyBase64 fail")
}
fmt.Printf("mixedCryptoService time cost:%v ms\n", tc.Milliseconds())
}
输出结果为:
Data prepare finish
onlyRSACryptoService time cost:6561 ms
mixedCryptoService time cost:1271 ms
可以看到,混合加密方案的速度是纯RSA
的五倍多。
写在最后
虽然从理论上来看,像RSA
这类非对称加密方案比AES
对称加密方案能够通过更高的安全性,但是这是通过支付时间代价而争取到的,在实际使用过程中,我们可以灵活安排,结合二者的优点,兼顾效率和安全性。
本文由「黄阿信」创作,创作不易,请多支持。
如果您觉得本文写得不错,那就点一下「赞赏」请我喝杯咖啡~
商业转载请联系作者获得授权,非商业转载请附上原文出处及本链接。
关注公众号,获取最新动态!