🚧 improve image encryption and decryption
This commit is contained in:
82
common/hybrid_encrypt/README.md
Normal file
82
common/hybrid_encrypt/README.md
Normal 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密钥部分
|
180
common/hybrid_encrypt/hybrid_encrypt.go
Normal file
180
common/hybrid_encrypt/hybrid_encrypt.go
Normal 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)
|
||||
}
|
148
common/hybrid_encrypt/hybrid_encrypt_js.js
Normal file
148
common/hybrid_encrypt/hybrid_encrypt_js.js
Normal 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
|
||||
};
|
46
common/hybrid_encrypt/image_encrypt.go
Normal file
46
common/hybrid_encrypt/image_encrypt.go
Normal 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
|
||||
}
|
28
common/hybrid_encrypt/image_encrypt_test.go
Normal file
28
common/hybrid_encrypt/image_encrypt_test.go
Normal 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))
|
||||
}
|
Reference in New Issue
Block a user