🚧 improve image encryption and decryption

This commit is contained in:
2025-03-22 13:05:50 +08:00
parent 3a03224f8c
commit 781a71a27c
39 changed files with 1274 additions and 977 deletions

View File

@@ -0,0 +1,82 @@
# 混合加密图片方案
本模块提供了一套完整的混合加密解决方案用于图片数据的安全传输。它使用RSA和AES-GCM混合加密方式确保数据传输的安全性和效率。
## 工作原理
1. **混合加密**使用RSA加密AES密钥使用AES-GCM加密实际数据
- 生成随机AES-256密钥
- 使用RSA公钥加密AES密钥
- 使用AES-GCM加密图片数据
- 组合加密后的AES密钥、nonce和加密数据
- 将结果进行Base64编码便于网络传输
2. **混合解密**使用RSA私钥解密AES密钥使用AES-GCM解密数据
- Base64解码密文
- 分离加密的AES密钥、nonce和加密数据
- 使用RSA私钥解密AES密钥
- 使用解密后的AES密钥和nonce解密数据
## 后端使用方法 (Go)
```go
// 生成RSA密钥对
privateKey, _ := hybrid_encrypt.GenerateRSAKey(2048)
// 导出公钥和私钥为PEM格式
pubPEM, _ := hybrid_encrypt.ExportPublicKeyPEM(&privateKey.PublicKey)
privPEM := hybrid_encrypt.ExportPrivateKeyPEM(privateKey)
// 将PEM格式的密钥转换为Base64编码便于在网络上传输
base64PubKey := base64.StdEncoding.EncodeToString(pubPEM)
base64PrivKey := base64.StdEncoding.EncodeToString(privPEM)
// 读取图片文件
imageData, _ := ioutil.ReadFile("path/to/image.jpg")
// 使用公钥加密图片
encryptedBase64, _ := hybrid_encrypt.EncryptImageWithBase64Key(base64PubKey, imageData)
// 将加密后的Base64字符串发送到前端
// ...
// 如果需要在后端解密
decryptedData, _ := hybrid_encrypt.DecryptImageWithBase64Key(base64PrivKey, encryptedBase64)
```
## 前端使用方法 (JavaScript)
```javascript
import { decryptImage } from './hybrid_encrypt_js.js';
// 从后端接收加密的图片数据和私钥
const encryptedImageBase64 = '...'; // 从后端接收的Base64编码的加密图片数据
const privateKeyPEM = '...'; // 从安全渠道获取的PEM格式RSA私钥
// 解密图片
decryptImage(encryptedImageBase64, privateKeyPEM)
.then(decryptedImageBase64 => {
// 使用解密后的图片数据
const imgElement = document.createElement('img');
imgElement.src = decryptedImageBase64; // 直接设置为data:image/jpeg;base64,...格式
document.body.appendChild(imgElement);
})
.catch(error => {
console.error('图片解密失败:', error);
});
```
## 安全注意事项
1. 私钥必须妥善保管,不应在不安全的渠道传输
2. 对于大型图片,考虑分块加密和解密
3. 在生产环境中应使用至少2048位的RSA密钥
4. 前端解密应在安全的环境中进行,避免私钥泄露
## 性能考虑
混合加密方案在处理大型数据(如高分辨率图片)时具有明显的性能优势:
- RSA仅用于加密小型AES密钥32字节
- 大型图片数据使用高效的AES-GCM加密
- 加密后的数据大小增加有限主要是RSA加密的AES密钥部分

View File

@@ -0,0 +1,180 @@
package hybrid_encrypt
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
)
// HybridEncrypt 使用RSA公钥加密AES密钥并用AES-GCM加密数据
func HybridEncrypt(publicKey *rsa.PublicKey, plaintext []byte) ([]byte, error) {
// 生成随机AES-256密钥
aesKey := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, aesKey); err != nil {
return nil, err
}
// 创建AES-GCM实例
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// 生成随机Nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// 加密数据
ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
// 加密AES密钥
encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, aesKey, nil)
if err != nil {
return nil, err
}
// 组合最终密文加密的AES密钥 + nonce + AES加密的数据
result := make([]byte, len(encryptedKey)+len(nonce)+len(ciphertext))
copy(result[:len(encryptedKey)], encryptedKey)
copy(result[len(encryptedKey):len(encryptedKey)+len(nonce)], nonce)
copy(result[len(encryptedKey)+len(nonce):], ciphertext)
return result, nil
}
// HybridDecrypt 使用RSA私钥解密AES密钥并用AES-GCM解密数据
func HybridDecrypt(privateKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
keySize := privateKey.PublicKey.Size()
if len(ciphertext) < keySize+12 {
return nil, errors.New("ciphertext too short")
}
// 分解密文各部分
encryptedKey := ciphertext[:keySize]
nonce := ciphertext[keySize : keySize+12]
data := ciphertext[keySize+12:]
// 解密AES密钥
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptedKey, nil)
if err != nil {
return nil, err
}
// 创建AES-GCM实例
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// 解密数据
plaintext, err := gcm.Open(nil, nonce, data, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
// GenerateRSAKey 生成RSA密钥对
func GenerateRSAKey(bits int) (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, bits)
}
// ExportPublicKeyPEM 导出公钥为PEM格式
func ExportPublicKeyPEM(pub *rsa.PublicKey) ([]byte, error) {
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
pubPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubASN1,
})
return pubPEM, nil
}
// ExportPrivateKeyPEM 导出私钥为PEM格式
func ExportPrivateKeyPEM(priv *rsa.PrivateKey) []byte {
privPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(priv),
})
return privPEM
}
// ImportPublicKeyPEM 从PEM导入公钥
func ImportPublicKeyPEM(pubPEM []byte) (*rsa.PublicKey, error) {
block, _ := pem.Decode(pubPEM)
if block == nil {
return nil, errors.New("failed to parse PEM block")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, errors.New("not RSA public key")
}
return rsaPub, nil
}
// ImportPrivateKeyPEM 从PEM导入私钥
func ImportPrivateKeyPEM(privPEM []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(privPEM)
if block == nil {
return nil, errors.New("failed to parse PEM block")
}
return x509.ParsePKCS1PrivateKey(block.Bytes)
}
func main() {
// 生成RSA密钥对
privateKey, err := GenerateRSAKey(2048)
if err != nil {
panic(err)
}
// 导出导入测试
pubPEM, _ := ExportPublicKeyPEM(&privateKey.PublicKey)
publicKey, _ := ImportPublicKeyPEM(pubPEM)
privPEM := ExportPrivateKeyPEM(privateKey)
privateKey, _ = ImportPrivateKeyPEM(privPEM)
// 加密测试
message := []byte("Secret image data")
fmt.Printf("Original: %s\n", message)
ciphertext, err := HybridEncrypt(publicKey, message)
if err != nil {
panic(err)
}
fmt.Printf("Ciphertext length: %d bytes\n", len(ciphertext))
// 解密测试
plaintext, err := HybridDecrypt(privateKey, ciphertext)
if err != nil {
panic(err)
}
fmt.Printf("Decrypted: %s\n", plaintext)
}

View File

@@ -0,0 +1,148 @@
/**
* hybrid_encrypt_js.js
* 前端混合解密实现与Go后端的hybrid_encrypt.go配合使用
*/
/**
* 使用RSA私钥和AES-GCM解密数据
* @param {ArrayBuffer} ciphertext - 加密的数据
* @param {CryptoKey} privateKey - RSA私钥
* @returns {Promise<ArrayBuffer>} - 解密后的数据
*/
async function hybridDecrypt(ciphertext, privateKey) {
// 将ArrayBuffer转换为Uint8Array以便处理
const ciphertextArray = new Uint8Array(ciphertext);
// 获取RSA密钥大小字节
const keySize = getPrivateKeySize(privateKey);
// 检查密文长度是否足够
if (ciphertextArray.length < keySize + 12) {
throw new Error('密文太短');
}
// 分解密文各部分
const encryptedKey = ciphertextArray.slice(0, keySize);
const nonce = ciphertextArray.slice(keySize, keySize + 12);
const data = ciphertextArray.slice(keySize + 12);
// 使用RSA私钥解密AES密钥
const aesKeyBuffer = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
hash: { name: 'SHA-256' },
},
privateKey,
encryptedKey
);
// 使用AES密钥和GCM模式解密数据
const aesKey = await window.crypto.subtle.importKey(
'raw',
aesKeyBuffer,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
);
const plaintext = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: nonce,
},
aesKey,
data
);
return plaintext;
}
/**
* 从PEM格式导入RSA私钥
* @param {string} pemString - PEM格式的私钥字符串
* @returns {Promise<CryptoKey>} - 导入的RSA私钥
*/
async function importPrivateKeyFromPEM(pemString) {
// 移除PEM头尾和换行符
const pemContents = pemString
.replace('-----BEGIN RSA PRIVATE KEY-----', '')
.replace('-----END RSA PRIVATE KEY-----', '')
.replace(/\s+/g, '');
// Base64解码
const binaryDer = window.atob(pemContents);
const derArray = new Uint8Array(binaryDer.length);
for (let i = 0; i < binaryDer.length; i++) {
derArray[i] = binaryDer.charCodeAt(i);
}
// 导入私钥
const privateKey = await window.crypto.subtle.importKey(
'pkcs8',
derArray.buffer,
{
name: 'RSA-OAEP',
hash: { name: 'SHA-256' },
},
false,
['decrypt']
);
return privateKey;
}
/**
* 获取RSA私钥的大小字节
* @param {CryptoKey} privateKey - RSA私钥
* @returns {number} - 密钥大小(字节)
*/
function getPrivateKeySize(privateKey) {
// 在实际应用中可能需要从privateKey对象中提取密钥大小
// 这里简化处理假设使用2048位RSA密钥256字节
return 256; // 2048位 / 8 = 256字节
}
/**
* 解密Base64编码的混合加密图片数据
* @param {string} base64Data - Base64编码的加密图片数据
* @param {string} privateKeyPEM - PEM格式的RSA私钥
* @returns {Promise<string>} - 解密后的图片Base64字符串
*/
async function decryptImage(base64Data, privateKeyPEM) {
try {
// 解码Base64
const binaryString = window.atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// 导入私钥
const privateKey = await importPrivateKeyFromPEM(privateKeyPEM);
// 解密数据
const decryptedData = await hybridDecrypt(bytes.buffer, privateKey);
// 将解密后的图片数据转换为Base64
const decryptedArray = new Uint8Array(decryptedData);
let binary = '';
for (let i = 0; i < decryptedArray.length; i++) {
binary += String.fromCharCode(decryptedArray[i]);
}
// 返回带有MIME类型的Base64图片数据
// 注意这里假设是JPEG图片实际应用中可能需要根据图片类型动态设置
return 'data:image/jpeg;base64,' + window.btoa(binary);
} catch (error) {
console.error('图片解密失败:', error);
throw error;
}
}
// 导出函数
export {
hybridDecrypt,
importPrivateKeyFromPEM,
decryptImage
};

View File

@@ -0,0 +1,46 @@
package hybrid_encrypt
import (
"crypto/rsa"
"errors"
)
// EncryptImage 使用混合加密方式加密图片数据并返回Base64编码的密文
// 参数:
// - publicKey: RSA公钥
// - imageData: 原始图片数据
//
// 返回:
// - string: Base64编码的加密图片数据
// - error: 错误信息
func EncryptImage(publicKey *rsa.PublicKey, imageData []byte) ([]byte, error) {
if len(imageData) == 0 {
return nil, errors.New("empty image data")
}
// 使用混合加密方式加密图片数据
ciphertext, err := HybridEncrypt(publicKey, imageData)
if err != nil {
return nil, err
}
return ciphertext, nil
}
// DecryptImage 解密Base64编码的加密图片数据
// 参数:
// - privateKey: RSA私钥
// - base64Ciphertext: Base64编码的加密图片数据
//
// 返回:
// - []byte: 解密后的原始图片数据
// - error: 错误信息
func DecryptImage(privateKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
// 使用混合解密方式解密图片数据
imageData, err := HybridDecrypt(privateKey, ciphertext)
if err != nil {
return nil, err
}
return imageData, nil
}

View File

@@ -0,0 +1,28 @@
package hybrid_encrypt
import (
"fmt"
"testing"
)
// 测试生成RSA密钥对、导出公钥和私钥为PEM格式、加密解密图片数据
// 注意:测试代码仅用于演示,实际应用中请使用更安全的加密算法和密钥长度
// 请不要在生产环境中使用此测试代码
func TestHybridEncrypt(t *testing.T) {
// 生成RSA密钥对
privateKey, err := GenerateRSAKey(2048)
if err != nil {
t.Fatalf("Failed to generate RSA key: %v", err)
}
// 导出公钥和私钥为PEM格式
pubPEM, err := ExportPublicKeyPEM(&privateKey.PublicKey)
if err != nil {
t.Fatalf("Failed to export public key: %v", err)
}
privPEM := ExportPrivateKeyPEM(privateKey)
// 打印公钥和私钥的PEM格式
fmt.Println(string(pubPEM))
fmt.Println(string(privPEM))
}