ab-image-ob 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export declare enum EncryptionAlgorithm {
|
|
2
|
+
/** AES-128-GCM - 快速,安全性高,推荐 */
|
|
3
|
+
AES_128_GCM = "aes-128-gcm",
|
|
4
|
+
/** AES-192-GCM - 中等强度 */
|
|
5
|
+
AES_192_GCM = "aes-192-gcm",
|
|
6
|
+
/** AES-256-GCM - 最高强度,推荐敏感数据 */
|
|
7
|
+
AES_256_GCM = "aes-256-gcm",
|
|
8
|
+
/** AES-128-CBC - 传统模式,无完整性验证 */
|
|
9
|
+
AES_128_CBC = "aes-128-cbc",
|
|
10
|
+
/** AES-256-CBC - 传统模式,无完整性验证 */
|
|
11
|
+
AES_256_CBC = "aes-256-cbc",
|
|
12
|
+
/** ChaCha20-Poly1305 - 现代算法,移动端友好 */
|
|
13
|
+
CHACHA20_POLY1305 = "chacha20-poly1305",
|
|
14
|
+
/** XOR 简单混淆 - 快速但不安全,仅用于预览混淆 */
|
|
15
|
+
XOR_SIMPLE = "xor-simple",
|
|
16
|
+
/** 像素位置打乱 - 不改变颜色值,仅重排像素 */
|
|
17
|
+
PIXEL_SHUFFLE = "pixel-shuffle",
|
|
18
|
+
/** 颜色通道置换 - 交换 RGB 通道顺序 */
|
|
19
|
+
CHANNEL_SWAP = "channel-swap"
|
|
20
|
+
}
|
|
21
|
+
export declare enum EncryptionMode {
|
|
22
|
+
/** 仅加密像素数据(输出可显示的图片) */
|
|
23
|
+
PIXEL_ONLY = "pixel-only",
|
|
24
|
+
/** 加密完整文件(输出不可显示的加密文件) */
|
|
25
|
+
FULL_FILE = "full-file",
|
|
26
|
+
/** 混合模式:像素加密 + 元数据保护 */
|
|
27
|
+
HYBRID = "hybrid"
|
|
28
|
+
}
|
|
29
|
+
interface EncryptionMetadata {
|
|
30
|
+
algorithm: EncryptionAlgorithm;
|
|
31
|
+
mode: EncryptionMode;
|
|
32
|
+
iv: string;
|
|
33
|
+
authTag?: string;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
channels: number;
|
|
37
|
+
density?: number;
|
|
38
|
+
channelMapping?: number[];
|
|
39
|
+
shuffleSeed?: number;
|
|
40
|
+
originalFormat?: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}
|
|
43
|
+
export declare class ImageEncryptor {
|
|
44
|
+
private algorithm;
|
|
45
|
+
private mode;
|
|
46
|
+
private key;
|
|
47
|
+
constructor(algorithm?: EncryptionAlgorithm, mode?: EncryptionMode);
|
|
48
|
+
/**
|
|
49
|
+
* 从密码生成密钥(使用 PBKDF2)
|
|
50
|
+
*/
|
|
51
|
+
generateKeyFromPassword(password: string, salt?: Buffer): Promise<Buffer>;
|
|
52
|
+
/**
|
|
53
|
+
* 生成随机密钥
|
|
54
|
+
*/
|
|
55
|
+
generateRandomKey(): Buffer;
|
|
56
|
+
/**
|
|
57
|
+
* 设置已有密钥
|
|
58
|
+
*/
|
|
59
|
+
setKey(key: Buffer): void;
|
|
60
|
+
/**
|
|
61
|
+
* 加密图片
|
|
62
|
+
*/
|
|
63
|
+
encrypt(inputPath: string, outputPath: string): Promise<EncryptionMetadata>;
|
|
64
|
+
/**
|
|
65
|
+
* 解密图片
|
|
66
|
+
*/
|
|
67
|
+
decrypt(encryptedPath: string, outputPath: string, fullMetadata?: EncryptionMetadata, metaPath?: string): Promise<void>;
|
|
68
|
+
private extractMetadata;
|
|
69
|
+
private standardEncrypt;
|
|
70
|
+
private standardDecrypt;
|
|
71
|
+
private xorEncrypt;
|
|
72
|
+
private xorDecrypt;
|
|
73
|
+
private pixelShuffleEncrypt;
|
|
74
|
+
private pixelShuffleDecrypt;
|
|
75
|
+
private generateShuffleIndices;
|
|
76
|
+
private channelSwapEncrypt;
|
|
77
|
+
private channelSwapDecrypt;
|
|
78
|
+
private generateChannelMapping;
|
|
79
|
+
private saveAsImage;
|
|
80
|
+
}
|
|
81
|
+
export declare function quickEncrypt(inputPath: string, outputPath: string, algorithm?: EncryptionAlgorithm, mode?: EncryptionMode, password?: string): Promise<Buffer>;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=ImageEncryptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageEncryptor.d.ts","sourceRoot":"","sources":["../../src/crypto/ImageEncryptor.ts"],"names":[],"mappings":"AAOA,oBAAY,mBAAmB;IAC3B,+BAA+B;IAC/B,WAAW,gBAAgB;IAC3B,yBAAyB;IACzB,WAAW,gBAAgB;IAC3B,gCAAgC;IAChC,WAAW,gBAAgB;IAC3B,gCAAgC;IAChC,WAAW,gBAAgB;IAC3B,gCAAgC;IAChC,WAAW,gBAAgB;IAC3B,qCAAqC;IACrC,iBAAiB,sBAAsB;IACvC,gCAAgC;IAChC,UAAU,eAAe;IACzB,4BAA4B;IAC5B,aAAa,kBAAkB;IAC/B,2BAA2B;IAC3B,YAAY,iBAAiB;CAChC;AAGD,oBAAY,cAAc;IACtB,wBAAwB;IACxB,UAAU,eAAe;IACzB,0BAA0B;IAC1B,SAAS,cAAc;IACvB,wBAAwB;IACxB,MAAM,WAAW;CACpB;AAqED,UAAU,kBAAkB;IACxB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACrB;AAGD,qBAAa,cAAc;IACvB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,GAAG,CAAS;gBAER,SAAS,GAAE,mBAAqD,EAAE,IAAI,GAAE,cAA0C;IAM9H;;OAEG;IACG,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO/E;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAM3B;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQzB;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyDjF;;OAEG;IACG,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,kBAAkB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YA0D/G,eAAe;YAYf,eAAe;YAmBf,eAAe;YAYf,UAAU;YAYV,UAAU;YAUV,mBAAmB;YAwBnB,mBAAmB;IAmBjC,OAAO,CAAC,sBAAsB;YAehB,kBAAkB;YAiClB,kBAAkB;IA2BhC,OAAO,CAAC,sBAAsB;YAchB,WAAW;CAsB5B;AAGD,wBAAsB,YAAY,CAC9B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,mBAAqD,EAChE,IAAI,GAAE,cAA0C,EAChD,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAWjB"}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ImageEncryptor = exports.EncryptionMode = exports.EncryptionAlgorithm = void 0;
|
|
7
|
+
exports.quickEncrypt = quickEncrypt;
|
|
8
|
+
// imageCrypto.ts
|
|
9
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
10
|
+
const crypto_1 = require("crypto");
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
// ============ 加密算法枚举 ============
|
|
14
|
+
var EncryptionAlgorithm;
|
|
15
|
+
(function (EncryptionAlgorithm) {
|
|
16
|
+
/** AES-128-GCM - 快速,安全性高,推荐 */
|
|
17
|
+
EncryptionAlgorithm["AES_128_GCM"] = "aes-128-gcm";
|
|
18
|
+
/** AES-192-GCM - 中等强度 */
|
|
19
|
+
EncryptionAlgorithm["AES_192_GCM"] = "aes-192-gcm";
|
|
20
|
+
/** AES-256-GCM - 最高强度,推荐敏感数据 */
|
|
21
|
+
EncryptionAlgorithm["AES_256_GCM"] = "aes-256-gcm";
|
|
22
|
+
/** AES-128-CBC - 传统模式,无完整性验证 */
|
|
23
|
+
EncryptionAlgorithm["AES_128_CBC"] = "aes-128-cbc";
|
|
24
|
+
/** AES-256-CBC - 传统模式,无完整性验证 */
|
|
25
|
+
EncryptionAlgorithm["AES_256_CBC"] = "aes-256-cbc";
|
|
26
|
+
/** ChaCha20-Poly1305 - 现代算法,移动端友好 */
|
|
27
|
+
EncryptionAlgorithm["CHACHA20_POLY1305"] = "chacha20-poly1305";
|
|
28
|
+
/** XOR 简单混淆 - 快速但不安全,仅用于预览混淆 */
|
|
29
|
+
EncryptionAlgorithm["XOR_SIMPLE"] = "xor-simple";
|
|
30
|
+
/** 像素位置打乱 - 不改变颜色值,仅重排像素 */
|
|
31
|
+
EncryptionAlgorithm["PIXEL_SHUFFLE"] = "pixel-shuffle";
|
|
32
|
+
/** 颜色通道置换 - 交换 RGB 通道顺序 */
|
|
33
|
+
EncryptionAlgorithm["CHANNEL_SWAP"] = "channel-swap";
|
|
34
|
+
})(EncryptionAlgorithm || (exports.EncryptionAlgorithm = EncryptionAlgorithm = {}));
|
|
35
|
+
// ============ 加密模式枚举 ============
|
|
36
|
+
var EncryptionMode;
|
|
37
|
+
(function (EncryptionMode) {
|
|
38
|
+
/** 仅加密像素数据(输出可显示的图片) */
|
|
39
|
+
EncryptionMode["PIXEL_ONLY"] = "pixel-only";
|
|
40
|
+
/** 加密完整文件(输出不可显示的加密文件) */
|
|
41
|
+
EncryptionMode["FULL_FILE"] = "full-file";
|
|
42
|
+
/** 混合模式:像素加密 + 元数据保护 */
|
|
43
|
+
EncryptionMode["HYBRID"] = "hybrid";
|
|
44
|
+
})(EncryptionMode || (exports.EncryptionMode = EncryptionMode = {}));
|
|
45
|
+
// ============ 算法配置映射 ============
|
|
46
|
+
const ALGORITHM_CONFIG = {
|
|
47
|
+
[EncryptionAlgorithm.AES_128_GCM]: {
|
|
48
|
+
keyLength: 16,
|
|
49
|
+
ivLength: 12,
|
|
50
|
+
authTagLength: 16,
|
|
51
|
+
isAuthenticated: true
|
|
52
|
+
},
|
|
53
|
+
[EncryptionAlgorithm.AES_192_GCM]: {
|
|
54
|
+
keyLength: 24,
|
|
55
|
+
ivLength: 12,
|
|
56
|
+
authTagLength: 16,
|
|
57
|
+
isAuthenticated: true
|
|
58
|
+
},
|
|
59
|
+
[EncryptionAlgorithm.AES_256_GCM]: {
|
|
60
|
+
keyLength: 32,
|
|
61
|
+
ivLength: 12,
|
|
62
|
+
authTagLength: 16,
|
|
63
|
+
isAuthenticated: true
|
|
64
|
+
},
|
|
65
|
+
[EncryptionAlgorithm.AES_128_CBC]: {
|
|
66
|
+
keyLength: 16,
|
|
67
|
+
ivLength: 16,
|
|
68
|
+
authTagLength: 0,
|
|
69
|
+
isAuthenticated: false
|
|
70
|
+
},
|
|
71
|
+
[EncryptionAlgorithm.AES_256_CBC]: {
|
|
72
|
+
keyLength: 32,
|
|
73
|
+
ivLength: 16,
|
|
74
|
+
authTagLength: 0,
|
|
75
|
+
isAuthenticated: false
|
|
76
|
+
},
|
|
77
|
+
[EncryptionAlgorithm.CHACHA20_POLY1305]: {
|
|
78
|
+
keyLength: 32,
|
|
79
|
+
ivLength: 12,
|
|
80
|
+
authTagLength: 16,
|
|
81
|
+
isAuthenticated: true
|
|
82
|
+
},
|
|
83
|
+
[EncryptionAlgorithm.XOR_SIMPLE]: {
|
|
84
|
+
keyLength: 1, // XOR 只需要 1 字节密钥
|
|
85
|
+
ivLength: 0,
|
|
86
|
+
authTagLength: 0,
|
|
87
|
+
isAuthenticated: false
|
|
88
|
+
},
|
|
89
|
+
[EncryptionAlgorithm.PIXEL_SHUFFLE]: {
|
|
90
|
+
keyLength: 32, // 用作随机种子
|
|
91
|
+
ivLength: 0,
|
|
92
|
+
authTagLength: 0,
|
|
93
|
+
isAuthenticated: false
|
|
94
|
+
},
|
|
95
|
+
[EncryptionAlgorithm.CHANNEL_SWAP]: {
|
|
96
|
+
keyLength: 0, // 不需要密钥,只是通道置换
|
|
97
|
+
ivLength: 0,
|
|
98
|
+
authTagLength: 0,
|
|
99
|
+
isAuthenticated: false
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// ============ 主加密类 ============
|
|
103
|
+
class ImageEncryptor {
|
|
104
|
+
constructor(algorithm = EncryptionAlgorithm.AES_256_GCM, mode = EncryptionMode.PIXEL_ONLY) {
|
|
105
|
+
this.algorithm = algorithm;
|
|
106
|
+
this.mode = mode;
|
|
107
|
+
this.key = Buffer.alloc(ALGORITHM_CONFIG[algorithm].keyLength);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 从密码生成密钥(使用 PBKDF2)
|
|
111
|
+
*/
|
|
112
|
+
async generateKeyFromPassword(password, salt) {
|
|
113
|
+
const config = ALGORITHM_CONFIG[this.algorithm];
|
|
114
|
+
const usedSalt = salt || (0, crypto_1.randomBytes)(16);
|
|
115
|
+
this.key = (0, crypto_1.scryptSync)(password, usedSalt, config.keyLength);
|
|
116
|
+
return this.key;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 生成随机密钥
|
|
120
|
+
*/
|
|
121
|
+
generateRandomKey() {
|
|
122
|
+
const config = ALGORITHM_CONFIG[this.algorithm];
|
|
123
|
+
this.key = (0, crypto_1.randomBytes)(config.keyLength);
|
|
124
|
+
return this.key;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 设置已有密钥
|
|
128
|
+
*/
|
|
129
|
+
setKey(key) {
|
|
130
|
+
const config = ALGORITHM_CONFIG[this.algorithm];
|
|
131
|
+
if (key.length !== config.keyLength) {
|
|
132
|
+
throw new Error(`密钥长度必须为 ${config.keyLength} 字节,当前为 ${key.length} 字节`);
|
|
133
|
+
}
|
|
134
|
+
this.key = key;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 加密图片
|
|
138
|
+
*/
|
|
139
|
+
async encrypt(inputPath, outputPath) {
|
|
140
|
+
const metadata = await this.extractMetadata(inputPath);
|
|
141
|
+
// 根据算法执行加密
|
|
142
|
+
let encryptedBuffer;
|
|
143
|
+
let extraMetadata = {};
|
|
144
|
+
switch (this.algorithm) {
|
|
145
|
+
case EncryptionAlgorithm.AES_128_GCM:
|
|
146
|
+
case EncryptionAlgorithm.AES_192_GCM:
|
|
147
|
+
case EncryptionAlgorithm.AES_256_GCM:
|
|
148
|
+
case EncryptionAlgorithm.AES_128_CBC:
|
|
149
|
+
case EncryptionAlgorithm.AES_256_CBC:
|
|
150
|
+
case EncryptionAlgorithm.CHACHA20_POLY1305:
|
|
151
|
+
encryptedBuffer = await this.standardEncrypt(inputPath, metadata, extraMetadata);
|
|
152
|
+
break;
|
|
153
|
+
case EncryptionAlgorithm.XOR_SIMPLE:
|
|
154
|
+
encryptedBuffer = await this.xorEncrypt(inputPath);
|
|
155
|
+
break;
|
|
156
|
+
case EncryptionAlgorithm.PIXEL_SHUFFLE:
|
|
157
|
+
encryptedBuffer = await this.pixelShuffleEncrypt(inputPath, extraMetadata);
|
|
158
|
+
break;
|
|
159
|
+
case EncryptionAlgorithm.CHANNEL_SWAP:
|
|
160
|
+
encryptedBuffer = await this.channelSwapEncrypt(inputPath, extraMetadata);
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
throw new Error(`不支持的算法: ${this.algorithm}`);
|
|
164
|
+
}
|
|
165
|
+
// 保存加密后的图片
|
|
166
|
+
if (this.mode === EncryptionMode.PIXEL_ONLY) {
|
|
167
|
+
await this.saveAsImage(encryptedBuffer, outputPath, metadata);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
await fs_1.promises.writeFile(outputPath, encryptedBuffer);
|
|
171
|
+
}
|
|
172
|
+
// 保存元数据
|
|
173
|
+
const metaPath = outputPath + '.meta.json';
|
|
174
|
+
const fullMetadata = {
|
|
175
|
+
algorithm: this.algorithm,
|
|
176
|
+
mode: this.mode,
|
|
177
|
+
iv: extraMetadata.iv || '',
|
|
178
|
+
authTag: extraMetadata.authTag,
|
|
179
|
+
width: metadata.width,
|
|
180
|
+
height: metadata.height,
|
|
181
|
+
channels: metadata.channels,
|
|
182
|
+
density: metadata.density,
|
|
183
|
+
channelMapping: extraMetadata.channelMapping,
|
|
184
|
+
shuffleSeed: extraMetadata.shuffleSeed,
|
|
185
|
+
originalFormat: metadata.format,
|
|
186
|
+
timestamp: Date.now()
|
|
187
|
+
};
|
|
188
|
+
await fs_1.promises.writeFile(metaPath, JSON.stringify(fullMetadata, null, 2));
|
|
189
|
+
return fullMetadata;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 解密图片
|
|
193
|
+
*/
|
|
194
|
+
async decrypt(encryptedPath, outputPath, fullMetadata, metaPath) {
|
|
195
|
+
// 读取元数据
|
|
196
|
+
let metadata;
|
|
197
|
+
if (fullMetadata) {
|
|
198
|
+
metadata = fullMetadata;
|
|
199
|
+
}
|
|
200
|
+
else if (!metaPath) {
|
|
201
|
+
throw new Error("需要 metaData 或 metaPath");
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const metaContent = await fs_1.promises.readFile(metaPath, 'utf-8');
|
|
205
|
+
metadata = JSON.parse(metaContent);
|
|
206
|
+
}
|
|
207
|
+
// 验证算法匹配
|
|
208
|
+
if (metadata.algorithm !== this.algorithm) {
|
|
209
|
+
throw new Error(`算法不匹配:文件使用 ${metadata.algorithm},当前使用 ${this.algorithm}`);
|
|
210
|
+
}
|
|
211
|
+
// 读取加密数据
|
|
212
|
+
let encryptedBuffer;
|
|
213
|
+
if (this.mode === EncryptionMode.PIXEL_ONLY) {
|
|
214
|
+
const image = (0, sharp_1.default)(encryptedPath);
|
|
215
|
+
encryptedBuffer = await image.raw().toBuffer();
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
encryptedBuffer = await fs_1.promises.readFile(encryptedPath);
|
|
219
|
+
}
|
|
220
|
+
// 根据算法执行解密
|
|
221
|
+
let decryptedBuffer;
|
|
222
|
+
switch (this.algorithm) {
|
|
223
|
+
case EncryptionAlgorithm.AES_128_GCM:
|
|
224
|
+
case EncryptionAlgorithm.AES_192_GCM:
|
|
225
|
+
case EncryptionAlgorithm.AES_256_GCM:
|
|
226
|
+
case EncryptionAlgorithm.AES_128_CBC:
|
|
227
|
+
case EncryptionAlgorithm.AES_256_CBC:
|
|
228
|
+
case EncryptionAlgorithm.CHACHA20_POLY1305:
|
|
229
|
+
decryptedBuffer = await this.standardDecrypt(encryptedBuffer, metadata);
|
|
230
|
+
break;
|
|
231
|
+
case EncryptionAlgorithm.XOR_SIMPLE:
|
|
232
|
+
decryptedBuffer = await this.xorDecrypt(encryptedBuffer);
|
|
233
|
+
break;
|
|
234
|
+
case EncryptionAlgorithm.PIXEL_SHUFFLE:
|
|
235
|
+
decryptedBuffer = await this.pixelShuffleDecrypt(encryptedBuffer, metadata);
|
|
236
|
+
break;
|
|
237
|
+
case EncryptionAlgorithm.CHANNEL_SWAP:
|
|
238
|
+
decryptedBuffer = await this.channelSwapDecrypt(encryptedBuffer, metadata);
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
throw new Error(`不支持的算法: ${this.algorithm}`);
|
|
242
|
+
}
|
|
243
|
+
// 保存解密后的图片
|
|
244
|
+
await this.saveAsImage(decryptedBuffer, outputPath, metadata);
|
|
245
|
+
console.log(`解密完成:${outputPath}`);
|
|
246
|
+
}
|
|
247
|
+
// ============ 私有方法 ============
|
|
248
|
+
async extractMetadata(inputPath) {
|
|
249
|
+
const image = (0, sharp_1.default)(inputPath);
|
|
250
|
+
const metadata = await image.metadata();
|
|
251
|
+
if (!metadata.width || !metadata.height || !metadata.channels) {
|
|
252
|
+
throw new Error('无法读取图片尺寸信息');
|
|
253
|
+
}
|
|
254
|
+
console.log('原始元数据:', metadata);
|
|
255
|
+
return metadata;
|
|
256
|
+
}
|
|
257
|
+
async standardEncrypt(inputPath, metadata, extraMetadata) {
|
|
258
|
+
const image = (0, sharp_1.default)(inputPath);
|
|
259
|
+
const pixelBuffer = await image.raw().toBuffer();
|
|
260
|
+
const config = ALGORITHM_CONFIG[this.algorithm];
|
|
261
|
+
const iv = (0, crypto_1.randomBytes)(config.ivLength);
|
|
262
|
+
const cipher = (0, crypto_1.createCipheriv)(this.algorithm, this.key, iv);
|
|
263
|
+
const encryptedBuffer = Buffer.concat([cipher.update(pixelBuffer), cipher.final()]);
|
|
264
|
+
extraMetadata.iv = iv.toString('hex');
|
|
265
|
+
if (config.isAuthenticated && 'getAuthTag' in cipher) {
|
|
266
|
+
extraMetadata.authTag = cipher.getAuthTag().toString('hex');
|
|
267
|
+
}
|
|
268
|
+
return encryptedBuffer;
|
|
269
|
+
}
|
|
270
|
+
async standardDecrypt(encryptedBuffer, metadata) {
|
|
271
|
+
const config = ALGORITHM_CONFIG[this.algorithm];
|
|
272
|
+
const iv = Buffer.from(metadata.iv, 'hex');
|
|
273
|
+
const decipher = (0, crypto_1.createDecipheriv)(this.algorithm, this.key, iv);
|
|
274
|
+
if (config.isAuthenticated && metadata.authTag) {
|
|
275
|
+
decipher.setAuthTag(Buffer.from(metadata.authTag, 'hex'));
|
|
276
|
+
}
|
|
277
|
+
return Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
|
|
278
|
+
}
|
|
279
|
+
async xorEncrypt(inputPath) {
|
|
280
|
+
const image = (0, sharp_1.default)(inputPath);
|
|
281
|
+
const pixelBuffer = await image.raw().toBuffer();
|
|
282
|
+
const xorKey = this.key[0]; // 使用第一个字节作为 XOR 密钥
|
|
283
|
+
const result = Buffer.alloc(pixelBuffer.length);
|
|
284
|
+
for (let i = 0; i < pixelBuffer.length; i++) {
|
|
285
|
+
result[i] = pixelBuffer[i] ^ xorKey;
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
async xorDecrypt(encryptedBuffer) {
|
|
290
|
+
// XOR 是对称的,相同操作即可解密
|
|
291
|
+
const xorKey = this.key[0];
|
|
292
|
+
const result = Buffer.alloc(encryptedBuffer.length);
|
|
293
|
+
for (let i = 0; i < encryptedBuffer.length; i++) {
|
|
294
|
+
result[i] = encryptedBuffer[i] ^ xorKey;
|
|
295
|
+
}
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
async pixelShuffleEncrypt(inputPath, extraMetadata) {
|
|
299
|
+
const image = (0, sharp_1.default)(inputPath);
|
|
300
|
+
const metadata = await image.metadata();
|
|
301
|
+
const pixelBuffer = await image.raw().toBuffer();
|
|
302
|
+
const pixelCount = metadata.width * metadata.height;
|
|
303
|
+
const pixelSize = metadata.channels;
|
|
304
|
+
const seed = this.key.readUInt32BE(0);
|
|
305
|
+
extraMetadata.shuffleSeed = seed;
|
|
306
|
+
// 生成随机索引映射
|
|
307
|
+
const indices = this.generateShuffleIndices(pixelCount, seed);
|
|
308
|
+
const shuffledBuffer = Buffer.alloc(pixelBuffer.length);
|
|
309
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
310
|
+
const srcOffset = indices[i] * pixelSize;
|
|
311
|
+
const dstOffset = i * pixelSize;
|
|
312
|
+
pixelBuffer.copy(shuffledBuffer, dstOffset, srcOffset, srcOffset + pixelSize);
|
|
313
|
+
}
|
|
314
|
+
return shuffledBuffer;
|
|
315
|
+
}
|
|
316
|
+
async pixelShuffleDecrypt(encryptedBuffer, metadata) {
|
|
317
|
+
const pixelCount = metadata.width * metadata.height;
|
|
318
|
+
const pixelSize = metadata.channels;
|
|
319
|
+
const seed = metadata.shuffleSeed || 0;
|
|
320
|
+
// 生成相同的随机索引映射
|
|
321
|
+
const indices = this.generateShuffleIndices(pixelCount, seed);
|
|
322
|
+
const restoredBuffer = Buffer.alloc(encryptedBuffer.length);
|
|
323
|
+
// 反向映射
|
|
324
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
325
|
+
const srcOffset = i * pixelSize;
|
|
326
|
+
const dstOffset = indices[i] * pixelSize;
|
|
327
|
+
encryptedBuffer.copy(restoredBuffer, dstOffset, srcOffset, srcOffset + pixelSize);
|
|
328
|
+
}
|
|
329
|
+
return restoredBuffer;
|
|
330
|
+
}
|
|
331
|
+
generateShuffleIndices(count, seed) {
|
|
332
|
+
const indices = Array.from({ length: count }, (_, i) => i);
|
|
333
|
+
let rng = (s) => {
|
|
334
|
+
s = (s * 9301 + 49297) % 233280;
|
|
335
|
+
return s / 233280;
|
|
336
|
+
};
|
|
337
|
+
for (let i = count - 1; i > 0; i--) {
|
|
338
|
+
const j = Math.floor(rng(seed) * (i + 1));
|
|
339
|
+
[indices[i], indices[j]] = [indices[j], indices[i]];
|
|
340
|
+
seed = (seed * 9301 + 49297) % 233280;
|
|
341
|
+
}
|
|
342
|
+
return indices;
|
|
343
|
+
}
|
|
344
|
+
async channelSwapEncrypt(inputPath, extraMetadata) {
|
|
345
|
+
const image = (0, sharp_1.default)(inputPath);
|
|
346
|
+
const metadata = await image.metadata();
|
|
347
|
+
const pixelBuffer = await image.raw().toBuffer();
|
|
348
|
+
const channels = metadata.channels;
|
|
349
|
+
if (channels < 3) {
|
|
350
|
+
throw new Error('通道置换需要至少 3 个通道(RGB)');
|
|
351
|
+
}
|
|
352
|
+
// 使用密钥决定置换映射
|
|
353
|
+
const mapping = this.generateChannelMapping(this.key);
|
|
354
|
+
extraMetadata.channelMapping = mapping;
|
|
355
|
+
const result = Buffer.alloc(pixelBuffer.length);
|
|
356
|
+
const pixelCount = metadata.width * metadata.height;
|
|
357
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
358
|
+
const offset = i * channels;
|
|
359
|
+
for (let c = 0; c < channels; c++) {
|
|
360
|
+
if (c < 3) {
|
|
361
|
+
// 交换 RGB 通道
|
|
362
|
+
result[offset + mapping[c]] = pixelBuffer[offset + c];
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// Alpha 通道保持不变
|
|
366
|
+
result[offset + c] = pixelBuffer[offset + c];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
async channelSwapDecrypt(encryptedBuffer, metadata) {
|
|
373
|
+
const channels = metadata.channels;
|
|
374
|
+
const mapping = metadata.channelMapping || [0, 1, 2];
|
|
375
|
+
// 创建反向映射
|
|
376
|
+
const inverseMapping = new Array(3);
|
|
377
|
+
for (let i = 0; i < 3; i++) {
|
|
378
|
+
inverseMapping[mapping[i]] = i;
|
|
379
|
+
}
|
|
380
|
+
const result = Buffer.alloc(encryptedBuffer.length);
|
|
381
|
+
const pixelCount = metadata.width * metadata.height;
|
|
382
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
383
|
+
const offset = i * channels;
|
|
384
|
+
for (let c = 0; c < 3; c++) {
|
|
385
|
+
result[offset + inverseMapping[c]] = encryptedBuffer[offset + c];
|
|
386
|
+
}
|
|
387
|
+
// 保留 Alpha 通道
|
|
388
|
+
if (channels === 4) {
|
|
389
|
+
result[offset + 3] = encryptedBuffer[offset + 3];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
generateChannelMapping(key) {
|
|
395
|
+
// 使用密钥的哈希值决定 RGB 通道的置换方式
|
|
396
|
+
const hash = key.reduce((a, b) => a + b, 0) % 6;
|
|
397
|
+
const mappings = [
|
|
398
|
+
[0, 1, 2], // 原始 RGB
|
|
399
|
+
[0, 2, 1], // R B G
|
|
400
|
+
[1, 0, 2], // G R B
|
|
401
|
+
[1, 2, 0], // G B R
|
|
402
|
+
[2, 0, 1], // B R G
|
|
403
|
+
[2, 1, 0] // B G R
|
|
404
|
+
];
|
|
405
|
+
return mappings[hash];
|
|
406
|
+
}
|
|
407
|
+
async saveAsImage(buffer, outputPath, metadata) {
|
|
408
|
+
let sharpInstance = (0, sharp_1.default)(buffer, {
|
|
409
|
+
raw: {
|
|
410
|
+
width: metadata.width,
|
|
411
|
+
height: metadata.height,
|
|
412
|
+
channels: metadata.channels
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
// 恢复 density 信息
|
|
416
|
+
if (metadata.density) {
|
|
417
|
+
sharpInstance = sharpInstance.withMetadata({ density: metadata.density });
|
|
418
|
+
}
|
|
419
|
+
// 根据原格式保存
|
|
420
|
+
const ext = path_1.default.extname(outputPath).toLowerCase();
|
|
421
|
+
if (ext === '.jpg' || ext === '.jpeg') {
|
|
422
|
+
await sharpInstance.jpeg({ quality: 100 }).toFile(outputPath);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
await sharpInstance.png({ compressionLevel: 9 }).toFile(outputPath);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
exports.ImageEncryptor = ImageEncryptor;
|
|
430
|
+
// ============ 便捷函数 ============
|
|
431
|
+
async function quickEncrypt(inputPath, outputPath, algorithm = EncryptionAlgorithm.AES_256_GCM, mode = EncryptionMode.PIXEL_ONLY, password) {
|
|
432
|
+
const encryptor = new ImageEncryptor(algorithm, mode);
|
|
433
|
+
if (password) {
|
|
434
|
+
await encryptor.generateKeyFromPassword(password);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
encryptor.generateRandomKey();
|
|
438
|
+
console.log(`随机密钥(请保存): ${encryptor['key'].toString('hex')}`);
|
|
439
|
+
}
|
|
440
|
+
return await encryptor.encrypt(inputPath, outputPath);
|
|
441
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ImageEncryptor, quickEncrypt } from "./crypto/ImageEncryptor";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
}
|
|
3
|
+
exports.quickEncrypt = exports.ImageEncryptor = void 0;
|
|
4
|
+
var ImageEncryptor_1 = require("./crypto/ImageEncryptor");
|
|
5
|
+
Object.defineProperty(exports, "ImageEncryptor", { enumerable: true, get: function () { return ImageEncryptor_1.ImageEncryptor; } });
|
|
6
|
+
Object.defineProperty(exports, "quickEncrypt", { enumerable: true, get: function () { return ImageEncryptor_1.quickEncrypt; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ab-image-ob",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,5 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|
|
20
|
-
]
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"sharp": "^0.34.5"
|
|
23
|
+
}
|
|
21
24
|
}
|