@alibarbar/common 1.1.2 → 1.1.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.
package/README.md CHANGED
@@ -15,6 +15,16 @@ yarn add @alibarbar/common
15
15
  pnpm add @alibarbar/common
16
16
  ```
17
17
 
18
+ ### 可选依赖
19
+
20
+ 如果使用 `SecureStorage` 的纯 JS RSA 加密功能(非 HTTPS 环境),需要额外安装 `node-forge`:
21
+
22
+ ```bash
23
+ pnpm add node-forge
24
+ ```
25
+
26
+ **注意**:如果在 Vite 项目中使用,请参考 [Vite 配置说明](./VITE_NODE_FORGE_FIX.md) 进行配置。
27
+
18
28
  ## 配置 npm 源
19
29
 
20
30
  项目已配置使用淘宝镜像源(速度更快)。如需修改,请编辑 `.npmrc` 文件。
package/dist/crypto.cjs CHANGED
@@ -1,12 +1,16 @@
1
1
  'use strict';
2
2
 
3
- // src/helper/crypto/index.ts
4
- async function sha256(data) {
5
- const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
6
- const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
7
- const hashArray = Array.from(new Uint8Array(hashBuffer));
8
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
9
- }
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var JSEncrypt = require('jsencrypt');
6
+ var CryptoJS = require('crypto-js');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var JSEncrypt__default = /*#__PURE__*/_interopDefault(JSEncrypt);
11
+ var CryptoJS__default = /*#__PURE__*/_interopDefault(CryptoJS);
12
+
13
+ // src/helper/encryption/index.ts
10
14
  function base64Encode(data) {
11
15
  if (typeof data === "string") {
12
16
  return btoa(unescape(encodeURIComponent(data)));
@@ -18,282 +22,210 @@ function base64Encode(data) {
18
22
  }
19
23
  return btoa(binary);
20
24
  }
21
- function base64Decode(data) {
22
- try {
23
- return decodeURIComponent(escape(atob(data)));
24
- } catch {
25
- throw new Error("Invalid Base64 string");
26
- }
27
- }
28
- function generateUUID() {
29
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
30
- return crypto.randomUUID();
31
- }
32
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
33
- const r = Math.random() * 16 | 0;
34
- const v = c === "x" ? r : r & 3 | 8;
35
- return v.toString(16);
36
- });
25
+ function generateRandomAESKeyString() {
26
+ return CryptoJS__default.default.lib.WordArray.random(256 / 8).toString();
37
27
  }
38
- function generateRandomString(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
39
- let result = "";
40
- for (let i = 0; i < length; i++) {
41
- result += charset.charAt(Math.floor(Math.random() * charset.length));
28
+ function encryptJsonWithAES(data, aesKey) {
29
+ try {
30
+ const jsonString = JSON.stringify(data);
31
+ return CryptoJS__default.default.AES.encrypt(jsonString, aesKey).toString();
32
+ } catch (error) {
33
+ throw new Error(
34
+ "[encryption] AES encryptJsonWithAES failed: " + (error instanceof Error ? error.message : String(error))
35
+ );
42
36
  }
43
- return result;
44
37
  }
45
- function hash(data) {
46
- let hashValue = 0;
47
- for (let i = 0; i < data.length; i++) {
48
- const char = data.charCodeAt(i);
49
- hashValue = (hashValue << 5) - hashValue + char;
50
- hashValue = hashValue & hashValue;
38
+ function decryptJsonWithAES(encryptedData, aesKey) {
39
+ try {
40
+ const decrypted = CryptoJS__default.default.AES.decrypt(encryptedData, aesKey);
41
+ const jsonString = decrypted.toString(CryptoJS__default.default.enc.Utf8);
42
+ if (!jsonString) {
43
+ throw new Error("decrypted JSON string is empty");
44
+ }
45
+ return JSON.parse(jsonString);
46
+ } catch (error) {
47
+ throw new Error(
48
+ "[encryption] AES decryptJsonWithAES failed: " + (error instanceof Error ? error.message : String(error))
49
+ );
51
50
  }
52
- return Math.abs(hashValue);
53
51
  }
54
- async function generateRSAKeyPair(modulusLength = 2048) {
55
- if (typeof crypto === "undefined" || !crypto.subtle) {
56
- throw new Error("Web Crypto API is not available");
52
+ function getEnvPublicKey() {
53
+ const key = undefined.VITE_RSA_PUBLIC_KEY?.trim();
54
+ if (!key) {
55
+ throw new Error(
56
+ "[encryption] VITE_RSA_PUBLIC_KEY is not set. Please configure RSA public key in environment variables."
57
+ );
57
58
  }
58
- const keyPair = await crypto.subtle.generateKey(
59
- {
60
- name: "RSA-OAEP",
61
- modulusLength,
62
- publicExponent: new Uint8Array([1, 0, 1]),
63
- hash: "SHA-256"
64
- },
65
- true,
66
- ["encrypt", "decrypt"]
67
- );
68
- return {
69
- publicKey: keyPair.publicKey,
70
- privateKey: keyPair.privateKey
71
- };
59
+ return key;
72
60
  }
73
- async function rsaEncrypt(data, publicKey) {
74
- if (typeof crypto === "undefined" || !crypto.subtle) {
75
- throw new Error("Web Crypto API is not available");
76
- }
77
- const encoder = new TextEncoder();
78
- const dataBuffer = encoder.encode(data);
79
- const algo = publicKey.algorithm;
80
- const modulusLength = typeof algo.modulusLength === "number" ? algo.modulusLength : 2048;
81
- const hashName = typeof algo.hash === "object" && "name" in algo.hash ? algo.hash.name : "SHA-256";
82
- const hashLength = hashName === "SHA-1" ? 20 : 32;
83
- const maxChunkSize = Math.max(Math.floor(modulusLength / 8 - 2 * hashLength - 2), 1);
84
- const chunks = [];
85
- for (let i = 0; i < dataBuffer.length; i += maxChunkSize) {
86
- const chunk = dataBuffer.slice(i, i + maxChunkSize);
87
- const encrypted = await crypto.subtle.encrypt(
88
- {
89
- name: "RSA-OAEP"
90
- },
91
- publicKey,
92
- chunk
61
+ function getEnvPrivateKey() {
62
+ const key = undefined.VITE_RSA_PRIVATE_KEY?.trim();
63
+ if (!key) {
64
+ throw new Error(
65
+ "[encryption] VITE_RSA_PRIVATE_KEY is not set. Please configure RSA private key in environment variables."
93
66
  );
94
- chunks.push(encrypted);
95
- }
96
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
97
- const merged = new Uint8Array(totalLength);
98
- let offset = 0;
99
- for (const chunk of chunks) {
100
- merged.set(new Uint8Array(chunk), offset);
101
- offset += chunk.byteLength;
102
67
  }
103
- return base64Encode(merged.buffer);
68
+ return key;
104
69
  }
105
- async function rsaDecrypt(encryptedData, privateKey) {
106
- if (typeof crypto === "undefined" || !crypto.subtle) {
107
- throw new Error("Web Crypto API is not available");
108
- }
109
- const binaryString = atob(encryptedData);
110
- const encryptedArray = new Uint8Array(binaryString.length);
111
- for (let i = 0; i < binaryString.length; i++) {
112
- encryptedArray[i] = binaryString.charCodeAt(i);
113
- }
114
- const chunkSize = 256;
70
+ function base64ToPem(base64Key, type) {
115
71
  const chunks = [];
116
- for (let i = 0; i < encryptedArray.length; i += chunkSize) {
117
- const chunk = encryptedArray.slice(i, i + chunkSize);
118
- const decrypted = await crypto.subtle.decrypt(
119
- {
120
- name: "RSA-OAEP"
121
- },
122
- privateKey,
123
- chunk
124
- );
125
- const decoder = new TextDecoder();
126
- chunks.push(decoder.decode(decrypted));
72
+ for (let i = 0; i < base64Key.length; i += 64) {
73
+ chunks.push(base64Key.slice(i, i + 64));
127
74
  }
128
- return chunks.join("");
75
+ const body = chunks.join("\n");
76
+ const header = type === "PUBLIC" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
77
+ const footer = type === "PUBLIC" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
78
+ return `${header}
79
+ ${body}
80
+ ${footer}`;
129
81
  }
130
- async function exportPublicKey(publicKey) {
131
- if (typeof crypto === "undefined" || !crypto.subtle) {
132
- throw new Error("Web Crypto API is not available");
82
+ async function rsaEncrypt(plain, publicKeyPem) {
83
+ try {
84
+ const encrypt = new JSEncrypt__default.default();
85
+ let publicKeyString;
86
+ if (typeof publicKeyPem === "string") {
87
+ const trimmedKey = publicKeyPem.trim();
88
+ if (!trimmedKey) {
89
+ throw new Error("Public key string is empty");
90
+ }
91
+ if (trimmedKey.includes("-----BEGIN")) {
92
+ publicKeyString = trimmedKey;
93
+ } else {
94
+ publicKeyString = base64ToPem(trimmedKey, "PUBLIC");
95
+ }
96
+ } else if (publicKeyPem instanceof CryptoJS__default.default.lib.WordArray) {
97
+ const base64Key = publicKeyPem.toString();
98
+ publicKeyString = base64ToPem(base64Key, "PUBLIC");
99
+ } else {
100
+ try {
101
+ const exported = await crypto.subtle.exportKey("spki", publicKeyPem);
102
+ const base64Key = base64Encode(exported);
103
+ publicKeyString = base64ToPem(base64Key, "PUBLIC");
104
+ } catch (error) {
105
+ throw new Error(
106
+ "Failed to export CryptoKey. In non-HTTPS environment, use string publicKey (Base64 or PEM) directly. " + (error instanceof Error ? error.message : String(error))
107
+ );
108
+ }
109
+ }
110
+ encrypt.setPublicKey(publicKeyString);
111
+ const MAX_ENCRYPT_LENGTH = 200;
112
+ if (plain.length > MAX_ENCRYPT_LENGTH) {
113
+ const chunks = [];
114
+ for (let i = 0; i < plain.length; i += MAX_ENCRYPT_LENGTH) {
115
+ const chunk = plain.slice(i, i + MAX_ENCRYPT_LENGTH);
116
+ const encrypted2 = encrypt.encrypt(chunk);
117
+ if (!encrypted2) {
118
+ throw new Error(
119
+ `RSA encryption failed for chunk ${i / MAX_ENCRYPT_LENGTH + 1}. Public key may be invalid or JSEncrypt may not be loaded correctly.`
120
+ );
121
+ }
122
+ chunks.push(encrypted2);
123
+ }
124
+ return chunks.join("|");
125
+ }
126
+ const encrypted = encrypt.encrypt(plain);
127
+ if (!encrypted) {
128
+ throw new Error(
129
+ `RSA encryption failed. Public key may be invalid or JSEncrypt may not be loaded correctly. Plain text length: ${plain.length} bytes.`
130
+ );
131
+ }
132
+ return encrypted;
133
+ } catch (error) {
134
+ if (error instanceof Error) {
135
+ if (error.message.includes("RSA encryption failed") || error.message.includes("Public key")) {
136
+ throw error;
137
+ }
138
+ throw new Error(`[encryption] rsaEncrypt failed: ${error.message}`);
139
+ }
140
+ throw new Error(`[encryption] rsaEncrypt failed: ${String(error)}`);
141
+ }
142
+ }
143
+ async function rsaDecrypt(cipher, privateKeyPem) {
144
+ const decrypt = new JSEncrypt__default.default();
145
+ let privateKeyString;
146
+ if (typeof privateKeyPem === "string") {
147
+ if (privateKeyPem.includes("-----BEGIN")) {
148
+ privateKeyString = privateKeyPem.trim();
149
+ } else {
150
+ const trimmed = privateKeyPem.trim();
151
+ privateKeyString = base64ToPem(trimmed, "PRIVATE");
152
+ }
153
+ } else if (privateKeyPem instanceof CryptoJS__default.default.lib.WordArray) {
154
+ const base64Key = privateKeyPem.toString();
155
+ privateKeyString = base64ToPem(base64Key, "PRIVATE");
156
+ } else {
157
+ try {
158
+ const exported = await crypto.subtle.exportKey("pkcs8", privateKeyPem);
159
+ const base64Key = base64Encode(exported);
160
+ privateKeyString = base64ToPem(base64Key, "PRIVATE");
161
+ } catch (error) {
162
+ throw new Error(
163
+ "[encryption] rsaDecrypt: failed to export CryptoKey. In non-HTTPS environment, use PEM string directly."
164
+ );
165
+ }
166
+ }
167
+ decrypt.setPrivateKey(privateKeyString);
168
+ const chunks = cipher.split("|");
169
+ const decryptedChunks = [];
170
+ for (const chunk of chunks) {
171
+ const decrypted = decrypt.decrypt(chunk);
172
+ if (!decrypted) {
173
+ throw new Error("[encryption] rsaDecrypt: RSA decryption failed");
174
+ }
175
+ decryptedChunks.push(decrypted);
133
176
  }
134
- const exported = await crypto.subtle.exportKey("spki", publicKey);
135
- return base64Encode(exported);
177
+ return decryptedChunks.join("");
136
178
  }
137
- async function exportPrivateKey(privateKey) {
138
- if (typeof crypto === "undefined" || !crypto.subtle) {
139
- throw new Error("Web Crypto API is not available");
140
- }
141
- const exported = await crypto.subtle.exportKey("pkcs8", privateKey);
142
- return base64Encode(exported);
179
+ async function encryptWithEnvPublicKey(plain) {
180
+ const publicKey = getEnvPublicKey();
181
+ return rsaEncrypt(plain, publicKey);
143
182
  }
144
- async function importPublicKey(keyData) {
145
- if (typeof crypto === "undefined" || !crypto.subtle) {
146
- throw new Error("Web Crypto API is not available");
147
- }
148
- const keyBuffer = base64Decode(keyData);
149
- const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
150
- return crypto.subtle.importKey(
151
- "spki",
152
- keyArray.buffer,
153
- {
154
- name: "RSA-OAEP",
155
- hash: "SHA-256"
156
- },
157
- true,
158
- ["encrypt"]
159
- );
183
+ async function decryptWithEnvPrivateKey(cipher) {
184
+ const privateKey = getEnvPrivateKey();
185
+ return rsaDecrypt(cipher, privateKey);
160
186
  }
161
- async function importPrivateKey(keyData) {
187
+ async function generateRSAKeyPair(modulusLength = 2048) {
162
188
  if (typeof crypto === "undefined" || !crypto.subtle) {
163
- throw new Error("Web Crypto API is not available");
189
+ throw new Error(
190
+ "[encryption] Web Crypto API is not available. Cannot generate RSA key pair. Please use fixed key pair from environment variables."
191
+ );
164
192
  }
165
- const keyBuffer = base64Decode(keyData);
166
- const keyArray = new Uint8Array(keyBuffer.split("").map((char) => char.charCodeAt(0)));
167
- return crypto.subtle.importKey(
168
- "pkcs8",
169
- keyArray.buffer,
193
+ const keyPair = await crypto.subtle.generateKey(
170
194
  {
171
195
  name: "RSA-OAEP",
196
+ modulusLength,
197
+ publicExponent: new Uint8Array([1, 0, 1]),
172
198
  hash: "SHA-256"
173
199
  },
174
200
  true,
175
- ["decrypt"]
176
- );
177
- }
178
- async function generateHMACKey() {
179
- if (typeof crypto === "undefined" || !crypto.subtle) {
180
- throw new Error("Web Crypto API is not available");
181
- }
182
- return crypto.subtle.generateKey(
183
- {
184
- name: "HMAC",
185
- hash: "SHA-256"
186
- },
187
- true,
188
- ["sign", "verify"]
189
- );
190
- }
191
- async function computeHMAC(data, key) {
192
- if (typeof crypto === "undefined" || !crypto.subtle) {
193
- throw new Error("Web Crypto API is not available");
194
- }
195
- const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
196
- const signature = await crypto.subtle.sign("HMAC", key, buffer);
197
- return base64Encode(signature);
198
- }
199
- async function verifyHMAC(data, signature, key) {
200
- if (typeof crypto === "undefined" || !crypto.subtle) {
201
- throw new Error("Web Crypto API is not available");
202
- }
203
- try {
204
- const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
205
- const signatureBuffer = new Uint8Array(
206
- atob(signature).split("").map((char) => char.charCodeAt(0))
207
- );
208
- return await crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
209
- } catch {
210
- return false;
211
- }
212
- }
213
- async function deriveKeyFromPassword(password, salt, iterations = 1e5, keyLength = 256) {
214
- if (typeof crypto === "undefined" || !crypto.subtle) {
215
- throw new Error("Web Crypto API is not available");
216
- }
217
- const saltBuffer = typeof salt === "string" ? new TextEncoder().encode(salt) : salt;
218
- const passwordKey = await crypto.subtle.importKey(
219
- "raw",
220
- new TextEncoder().encode(password),
221
- "PBKDF2",
222
- false,
223
- ["deriveBits", "deriveKey"]
224
- );
225
- return crypto.subtle.deriveKey(
226
- {
227
- name: "PBKDF2",
228
- salt: saltBuffer,
229
- iterations,
230
- hash: "SHA-256"
231
- },
232
- passwordKey,
233
- {
234
- name: "AES-GCM",
235
- length: keyLength
236
- },
237
- false,
238
201
  ["encrypt", "decrypt"]
239
202
  );
240
- }
241
- async function aesGCMEncrypt(data, key) {
242
- if (typeof crypto === "undefined" || !crypto.subtle) {
243
- throw new Error("Web Crypto API is not available");
244
- }
245
- const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
246
- const iv = crypto.getRandomValues(new Uint8Array(12));
247
- const encrypted = await crypto.subtle.encrypt(
248
- {
249
- name: "AES-GCM",
250
- iv
251
- },
252
- key,
253
- dataBuffer
254
- );
203
+ const publicKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
204
+ const privateKeyDer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
205
+ const publicKeyBase64 = base64Encode(publicKeyDer);
206
+ const privateKeyBase64 = base64Encode(privateKeyDer);
255
207
  return {
256
- encrypted: base64Encode(encrypted),
257
- iv: base64Encode(iv.buffer)
208
+ publicKey: base64ToPem(publicKeyBase64, "PUBLIC"),
209
+ privateKey: base64ToPem(privateKeyBase64, "PRIVATE")
258
210
  };
259
211
  }
260
- async function aesGCMDecrypt(encryptedData, iv, key) {
261
- if (typeof crypto === "undefined" || !crypto.subtle) {
262
- throw new Error("Web Crypto API is not available");
263
- }
264
- const encryptedBuffer = new Uint8Array(
265
- atob(encryptedData).split("").map((char) => char.charCodeAt(0))
266
- );
267
- const ivBuffer = new Uint8Array(
268
- atob(iv).split("").map((char) => char.charCodeAt(0))
269
- );
270
- const decrypted = await crypto.subtle.decrypt(
271
- {
272
- name: "AES-GCM",
273
- iv: ivBuffer
274
- },
275
- key,
276
- encryptedBuffer
277
- );
278
- return new TextDecoder().decode(decrypted);
279
- }
212
+ var rsaEnv = {
213
+ encrypt: encryptWithEnvPublicKey,
214
+ decrypt: decryptWithEnvPrivateKey,
215
+ getPublicKey: getEnvPublicKey,
216
+ getPrivateKey: getEnvPrivateKey
217
+ };
218
+ var encryption_default = rsaEnv;
280
219
 
281
- exports.aesGCMDecrypt = aesGCMDecrypt;
282
- exports.aesGCMEncrypt = aesGCMEncrypt;
283
- exports.base64Decode = base64Decode;
284
220
  exports.base64Encode = base64Encode;
285
- exports.computeHMAC = computeHMAC;
286
- exports.deriveKeyFromPassword = deriveKeyFromPassword;
287
- exports.exportPrivateKey = exportPrivateKey;
288
- exports.exportPublicKey = exportPublicKey;
289
- exports.generateHMACKey = generateHMACKey;
221
+ exports.decryptJsonWithAES = decryptJsonWithAES;
222
+ exports.decryptWithEnvPrivateKey = decryptWithEnvPrivateKey;
223
+ exports.default = encryption_default;
224
+ exports.encryptJsonWithAES = encryptJsonWithAES;
225
+ exports.encryptWithEnvPublicKey = encryptWithEnvPublicKey;
290
226
  exports.generateRSAKeyPair = generateRSAKeyPair;
291
- exports.generateRandomString = generateRandomString;
292
- exports.generateUUID = generateUUID;
293
- exports.hash = hash;
294
- exports.importPrivateKey = importPrivateKey;
295
- exports.importPublicKey = importPublicKey;
227
+ exports.generateRandomAESKeyString = generateRandomAESKeyString;
228
+ exports.getEnvPrivateKey = getEnvPrivateKey;
229
+ exports.getEnvPublicKey = getEnvPublicKey;
296
230
  exports.rsaDecrypt = rsaDecrypt;
297
231
  exports.rsaEncrypt = rsaEncrypt;
298
- exports.sha256 = sha256;
299
- exports.verifyHMAC = verifyHMAC;