封面/截图加密对接教程
开启图片加密后,基本上EFV生成的封面、截图全部会被加密乱码,完全看不到任何实质内容,需前端解密调用。
对接之前请先了解下封面截图加密的原理及设置 ⇒ 传送门,这里只提供前端使用示例,然后自行对接。
加密模式说明
EFV提供两种图片加密模式,可在后台自由切换:
| 模式 | 适用场景 | 安全强度 | 是否需要配置密钥 |
|---|---|---|---|
| 二进制加密(XOR) | 网页端 | 基础 | 否,无需任何配置 |
| AES加密 | App端 | 高 | 是,需配置 Key/IV |
AES模式下支持四种加密类型,可在后台自由选择,推荐使用AES-256-CTR,性能最高。
XOR加密
适用于网页端,无需任何密钥配置,开启后直接使用以下代码解密显示。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>EFV XOR加密图片解密展示</title>
</head>
<body>
<img id="decodedImage" src="" alt="解密后的图片将显示在这里">
<script>
function fetchEncryptedImage() {
return fetch('http://127.0.0.1:3000/videos/202403/08/65eb06d5561d9e3d0b990740/2.jpg')
.then(response => response.arrayBuffer());
}
// 解密函数,对图片数据的前9个字节进行XOR操作
function decryptImage(data) {
let view = new Uint8Array(data);
for (let i = 0; i < Math.min(9, view.length); i++) {
view[i] = view[i] ^ 0x12;
}
return data;
}
// 将解密后的图片数据设置为img元素的src属性
function displayDecryptedImage(data) {
let blob = new Blob([data]);
let url = URL.createObjectURL(blob);
document.getElementById('decodedImage').src = url;
}
// 加载并解密图片
fetchEncryptedImage().then(encryptedData => {
let decryptedData = decryptImage(encryptedData);
displayDecryptedImage(decryptedData);
}).catch(error => console.error('加载或解密图片时出错:', error));
</script>
</body>
</html>
AES加密
推荐App端使用,需在后台配置Key和IV后使用,Key/IV请妥善保管,切勿泄露。
HTML示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>EFV AES加密图片解密展示</title>
</head>
<body>
<img id="decodedImage" src="" alt="解密后的图片将显示在这里">
<script>
// 与后台配置的Key/IV/加密类型保持一致
const AES_KEY_HEX = '你的hex字符串'; // 后台AES Key
const AES_IV_HEX = '你的32位hex字符串'; // 后台AES IV
const AES_CIPHER = 'aes-256-ctr'; // 后台选择的加密类型
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
}
return bytes;
}
async function fetchEncryptedImage() {
const response = await fetch('http://127.0.0.1:3000/videos/202403/08/65eb06d5561d9e3d0b990740/2.jpg');
return response.arrayBuffer();
}
// 使用Web Crypto API进行AES解密,自动适配CTR/CBC模式
async function decryptImage(encryptedData) {
const keyBytes = hexToBytes(AES_KEY_HEX);
const ivBytes = hexToBytes(AES_IV_HEX);
let algorithmName, decryptParams;
if (AES_CIPHER.includes('cbc')) {
algorithmName = 'AES-CBC';
decryptParams = { name: 'AES-CBC', iv: ivBytes };
} else {
algorithmName = 'AES-CTR';
decryptParams = { name: 'AES-CTR', counter: ivBytes, length: 64 };
}
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyBytes,
{ name: algorithmName },
false,
['decrypt']
);
return crypto.subtle.decrypt(decryptParams, cryptoKey, encryptedData);
}
function displayDecryptedImage(data) {
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
document.getElementById('decodedImage').src = url;
}
// 加载并解密图片
fetchEncryptedImage()
.then(encryptedData => decryptImage(encryptedData))
.then(decryptedData => displayDecryptedImage(decryptedData))
.catch(error => console.error('加载或解密图片时出错:', error));
</script>
</body>
</html>
Android(Kotlin)示例
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object ImageDecryptor {
private val KEY_HEX = "你的hex字符串"
private val IV_HEX = "你的32位hex字符串"
private val AES_CIPHER = "AES/CTR/NoPadding" // CTR: AES/CTR/NoPadding CBC: AES/CBC/PKCS5Padding
fun decrypt(encryptedBytes: ByteArray): ByteArray {
val key = SecretKeySpec(KEY_HEX.hexToBytes(), "AES")
val ivSpec = IvParameterSpec(IV_HEX.hexToBytes())
val cipher = Cipher.getInstance(AES_CIPHER)
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
return cipher.doFinal(encryptedBytes)
}
private fun String.hexToBytes() =
chunked(2).map { it.toInt(16).toByte() }.toByteArray()
}
iOS(Swift)示例
import CryptoKit
struct ImageDecryptor {
static let keyHex = "你的hex字符串"
static let ivHex = "你的32位hex字符串"
static let aesCipher = "ctr" // 后台选择的模式:ctr或cbc
static func decrypt(_ data: Data) throws -> Data {
let keyData = Data(hexString: keyHex)!
let key = SymmetricKey(data: keyData)
if aesCipher == "cbc" {
let iv = Data(hexString: ivHex)!
let sealedBox = try AES.CBC.SealedBox(combined: iv + data)
return try AES.CBC.open(sealedBox, using: key)
} else {
let nonce = try AES.CTR.Nonce(data: Data(hexString: ivHex)!)
return try AES.CTR.decrypt(data, using: key, nonce: nonce)
}
}
}
Flutter(Dart)示例
import 'package:pointycastle/export.dart';
Uint8List decryptImage(Uint8List encrypted, String keyHex, String ivHex, String cipher) {
final key = Uint8List.fromList(HEX.decode(keyHex));
final iv = Uint8List.fromList(HEX.decode(ivHex));
if (cipher.contains('cbc')) {
final cbcCipher = CBCBlockCipher(AESEngine())
..init(false, ParametersWithIV(KeyParameter(key), iv));
final output = Uint8List(encrypted.length);
for (var i = 0; i < encrypted.length; i += 16) {
cbcCipher.processBlock(encrypted, i, output, i);
}
return output;
} else {
final ctrCipher = CTRStreamCipher(AESEngine())
..init(false, ParametersWithIV(KeyParameter(key), iv));
return ctrCipher.process(encrypted);
}
}
注意事项
XOR模式和AES模式不可同时开启,后台切换后立即生效AES模式下Key/IV不可泄露,请勿将其硬编码在网页端JS中- 后台更换
Key/IV或切换加密类型后,所有客户端需同步更新,否则解密失败 AES-256系列Key为64位hex字符串,AES-128系列Key为32位hex字符串,IV均为32位hex字符串- 后端统一按后台当前配置的加密模式返回,响应头
x-encode-mode标识本次加密类型,客户端据此选择对应解密方式 CTR模式密文长度与原文一致;CBC模式密文长度略大于原文,传输时Content-Length会有差异