befly 3.9.12 → 3.9.14

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/docs/cipher.md ADDED
@@ -0,0 +1,580 @@
1
+ # Cipher 加密工具
2
+
3
+ > 哈希、签名、密码加密与 JWT 令牌
4
+
5
+ ## 目录
6
+
7
+ - [概述](#概述)
8
+ - [Cipher 加密类](#cipher-加密类)
9
+ - [哈希算法](#哈希算法)
10
+ - [HMAC 签名](#hmac-签名)
11
+ - [密码加密](#密码加密)
12
+ - [工具方法](#工具方法)
13
+ - [JWT 令牌](#jwt-令牌)
14
+ - [配置选项](#配置选项)
15
+ - [签发令牌](#签发令牌)
16
+ - [验证令牌](#验证令牌)
17
+ - [解码令牌](#解码令牌)
18
+ - [插件集成](#插件集成)
19
+ - [使用示例](#使用示例)
20
+ - [FAQ](#faq)
21
+
22
+ ---
23
+
24
+ ## 概述
25
+
26
+ Befly 提供两个安全相关的工具:
27
+
28
+ | 工具 | 说明 | 典型场景 |
29
+ | -------- | ---------- | ------------------------ |
30
+ | `Cipher` | 加密工具类 | 数据哈希、签名、密码加密 |
31
+ | `Jwt` | JWT 令牌类 | 用户认证、API 授权 |
32
+
33
+ ---
34
+
35
+ ## Cipher 加密类
36
+
37
+ ### 基本导入
38
+
39
+ ```typescript
40
+ import { Cipher } from '../lib/cipher.js';
41
+ ```
42
+
43
+ ### 哈希算法
44
+
45
+ #### MD5
46
+
47
+ ```typescript
48
+ // 基本用法
49
+ const hash = Cipher.md5('hello');
50
+ // '5d41402abc4b2a76b9719d911017c592'
51
+
52
+ // 指定编码
53
+ const hashBase64 = Cipher.md5('hello', 'base64');
54
+ ```
55
+
56
+ #### SHA-1
57
+
58
+ ```typescript
59
+ const hash = Cipher.sha1('hello');
60
+ // 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'
61
+ ```
62
+
63
+ #### SHA-256
64
+
65
+ ```typescript
66
+ const hash = Cipher.sha256('hello');
67
+ // '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
68
+ ```
69
+
70
+ #### SHA-512
71
+
72
+ ```typescript
73
+ const hash = Cipher.sha512('hello');
74
+ // '9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca...'
75
+ ```
76
+
77
+ #### 通用哈希
78
+
79
+ ```typescript
80
+ // 支持多种算法
81
+ const hash = Cipher.hash('sha256', 'hello');
82
+ const hash2 = Cipher.hash('sha384', 'hello');
83
+ const hash3 = Cipher.hash('blake2b256', 'hello');
84
+ ```
85
+
86
+ #### 支持的算法
87
+
88
+ | 算法 | 输出长度 | 说明 |
89
+ | ------------ | -------- | ------------------ |
90
+ | `md5` | 32 | 不推荐用于安全场景 |
91
+ | `sha1` | 40 | 不推荐用于安全场景 |
92
+ | `sha256` | 64 | 推荐 |
93
+ | `sha384` | 96 | 高安全性 |
94
+ | `sha512` | 128 | 高安全性 |
95
+ | `blake2b256` | 64 | 高性能 |
96
+ | `blake2b512` | 128 | 高性能 |
97
+
98
+ #### 输出编码
99
+
100
+ | 编码 | 说明 |
101
+ | ----------- | ----------------- |
102
+ | `hex` | 十六进制(默认) |
103
+ | `base64` | Base64 编码 |
104
+ | `base64url` | URL 安全的 Base64 |
105
+
106
+ ### HMAC 签名
107
+
108
+ HMAC 使用密钥进行签名,用于数据完整性验证:
109
+
110
+ ```typescript
111
+ const key = 'my-secret-key';
112
+
113
+ // HMAC-MD5
114
+ const sig1 = Cipher.hmacMd5(key, 'data');
115
+
116
+ // HMAC-SHA1
117
+ const sig2 = Cipher.hmacSha1(key, 'data');
118
+
119
+ // HMAC-SHA256(推荐)
120
+ const sig3 = Cipher.hmacSha256(key, 'data');
121
+
122
+ // HMAC-SHA512
123
+ const sig4 = Cipher.hmacSha512(key, 'data');
124
+
125
+ // 通用 HMAC
126
+ const sig5 = Cipher.hmac('sha256', key, 'data');
127
+ ```
128
+
129
+ #### RSA-SHA256 签名
130
+
131
+ 用于非对称加密签名:
132
+
133
+ ```typescript
134
+ const privateKey = `-----BEGIN PRIVATE KEY-----
135
+ MIIEvQIBADANBg...
136
+ -----END PRIVATE KEY-----`;
137
+
138
+ const signature = Cipher.rsaSha256('data to sign', privateKey);
139
+ // 或指定编码
140
+ const signatureBase64 = Cipher.rsaSha256('data to sign', privateKey, 'base64');
141
+ ```
142
+
143
+ ### 密码加密
144
+
145
+ 使用 bcrypt 算法加密密码(推荐用于用户密码存储):
146
+
147
+ #### 加密密码
148
+
149
+ ```typescript
150
+ // 默认配置(推荐)
151
+ const hash = await Cipher.hashPassword('123456');
152
+ // '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92...'
153
+
154
+ // 自定义强度(cost 越高越安全但越慢)
155
+ const hash2 = await Cipher.hashPassword('123456', { cost: 12 });
156
+ ```
157
+
158
+ #### 验证密码
159
+
160
+ ```typescript
161
+ const isValid = await Cipher.verifyPassword('123456', storedHash);
162
+ if (isValid) {
163
+ // 密码正确
164
+ }
165
+ ```
166
+
167
+ #### bcrypt 参数说明
168
+
169
+ | 参数 | 默认值 | 说明 |
170
+ | ------ | ------ | ------------------- |
171
+ | `cost` | 10 | 计算强度,范围 4-31 |
172
+
173
+ **cost 选择建议**:
174
+
175
+ - `10`:一般应用(默认)
176
+ - `12`:高安全性应用
177
+ - `14+`:极高安全性(性能影响较大)
178
+
179
+ ### 工具方法
180
+
181
+ #### Base64 编解码
182
+
183
+ ```typescript
184
+ // 编码
185
+ const encoded = Cipher.base64Encode('hello');
186
+ // 'aGVsbG8='
187
+
188
+ // 解码
189
+ const decoded = Cipher.base64Decode('aGVsbG8=');
190
+ // 'hello'
191
+ ```
192
+
193
+ #### 随机字符串
194
+
195
+ ```typescript
196
+ // 生成指定长度的随机十六进制字符串
197
+ const random = Cipher.randomString(32);
198
+ // 'a1b2c3d4e5f6...'(32位)
199
+ ```
200
+
201
+ #### 文件哈希
202
+
203
+ ```typescript
204
+ // 计算文件哈希(异步)
205
+ const fileHash = await Cipher.hashFile('/path/to/file.txt');
206
+ // 默认 SHA-256
207
+
208
+ // 指定算法
209
+ const md5Hash = await Cipher.hashFile('/path/to/file.txt', 'md5');
210
+ ```
211
+
212
+ #### 快速哈希(非密码学)
213
+
214
+ ```typescript
215
+ // 高性能哈希,用于数据指纹、去重等
216
+ const hash = Cipher.fastHash('data');
217
+ const hashWithSeed = Cipher.fastHash('data', 12345);
218
+ ```
219
+
220
+ ---
221
+
222
+ ## JWT 令牌
223
+
224
+ JWT (JSON Web Token) 用于用户认证和 API 授权。
225
+
226
+ ### 基本导入
227
+
228
+ ```typescript
229
+ import { Jwt } from '../lib/jwt.js';
230
+
231
+ const jwt = new Jwt({
232
+ secret: 'your-secret-key',
233
+ expiresIn: '7d',
234
+ algorithm: 'HS256'
235
+ });
236
+ ```
237
+
238
+ ### 配置选项
239
+
240
+ #### AuthConfig 接口
241
+
242
+ ```typescript
243
+ interface AuthConfig {
244
+ secret?: string; // 密钥
245
+ expiresIn?: string; // 过期时间
246
+ algorithm?: string; // 签名算法
247
+ }
248
+ ```
249
+
250
+ #### 配置说明
251
+
252
+ | 属性 | 默认值 | 说明 |
253
+ | ----------- | ---------------- | ---------------------------- |
254
+ | `secret` | `'befly-secret'` | JWT 密钥(生产环境必须修改) |
255
+ | `expiresIn` | `'7d'` | 过期时间 |
256
+ | `algorithm` | `'HS256'` | 签名算法 |
257
+
258
+ #### 过期时间格式
259
+
260
+ | 格式 | 示例 | 说明 |
261
+ | ---- | ------- | ------- |
262
+ | 秒 | `3600` | 3600 秒 |
263
+ | 分钟 | `'30m'` | 30 分钟 |
264
+ | 小时 | `'2h'` | 2 小时 |
265
+ | 天 | `'7d'` | 7 天 |
266
+ | 周 | `'2w'` | 2 周 |
267
+
268
+ #### 支持的算法
269
+
270
+ | 算法 | 类型 | 说明 |
271
+ | ------- | ------ | -------------------- |
272
+ | `HS256` | 对称 | HMAC SHA-256(默认) |
273
+ | `HS384` | 对称 | HMAC SHA-384 |
274
+ | `HS512` | 对称 | HMAC SHA-512 |
275
+ | `RS256` | 非对称 | RSA SHA-256 |
276
+ | `RS384` | 非对称 | RSA SHA-384 |
277
+ | `RS512` | 非对称 | RSA SHA-512 |
278
+ | `ES256` | 非对称 | ECDSA SHA-256 |
279
+ | `ES384` | 非对称 | ECDSA SHA-384 |
280
+ | `ES512` | 非对称 | ECDSA SHA-512 |
281
+
282
+ ### 签发令牌
283
+
284
+ ```typescript
285
+ // 基本用法
286
+ const token = jwt.sign({ userId: 123, role: 'admin' });
287
+
288
+ // 自定义选项
289
+ const token2 = jwt.sign(
290
+ { userId: 123 },
291
+ {
292
+ expiresIn: '1h',
293
+ issuer: 'my-app',
294
+ audience: 'users',
295
+ subject: 'auth',
296
+ jwtId: 'unique-id'
297
+ }
298
+ );
299
+ ```
300
+
301
+ #### JwtSignOptions
302
+
303
+ | 属性 | 类型 | 说明 |
304
+ | ----------- | ------ | ---------------- |
305
+ | `expiresIn` | string | 覆盖默认过期时间 |
306
+ | `secret` | string | 覆盖默认密钥 |
307
+ | `algorithm` | string | 覆盖默认算法 |
308
+ | `issuer` | string | 签发者 (iss) |
309
+ | `audience` | string | 受众 (aud) |
310
+ | `subject` | string | 主题 (sub) |
311
+ | `jwtId` | string | 唯一标识 (jti) |
312
+ | `notBefore` | number | 生效时间 (nbf) |
313
+
314
+ ### 验证令牌
315
+
316
+ ```typescript
317
+ try {
318
+ const payload = jwt.verify(token);
319
+ console.log(payload.userId); // 123
320
+ } catch (error) {
321
+ // 令牌无效或已过期
322
+ console.error(error.message);
323
+ }
324
+ ```
325
+
326
+ #### JwtVerifyOptions
327
+
328
+ ```typescript
329
+ const payload = jwt.verify(token, {
330
+ ignoreExpiration: false, // 是否忽略过期
331
+ ignoreNotBefore: false, // 是否忽略 nbf
332
+ issuer: 'my-app', // 验证签发者
333
+ audience: 'users', // 验证受众
334
+ subject: 'auth', // 验证主题
335
+ algorithms: ['HS256'] // 允许的算法
336
+ });
337
+ ```
338
+
339
+ ### 解码令牌
340
+
341
+ 解码不验证签名,仅解析内容:
342
+
343
+ ```typescript
344
+ // 只获取 payload
345
+ const payload = jwt.decode(token);
346
+ console.log(payload.userId);
347
+
348
+ // 获取完整信息
349
+ const decoded = jwt.decode(token, true);
350
+ console.log(decoded.header); // { alg: 'HS256', typ: 'JWT' }
351
+ console.log(decoded.payload); // { userId: 123, iat: ..., exp: ... }
352
+ console.log(decoded.signature); // 签名字符串
353
+ ```
354
+
355
+ ---
356
+
357
+ ## 插件集成
358
+
359
+ ### Cipher 插件
360
+
361
+ ```typescript
362
+ // 通过 befly 访问
363
+ export default {
364
+ name: '示例接口',
365
+ handler: async (befly, ctx) => {
366
+ const hash = befly.cipher.sha256('data');
367
+ const isValid = await befly.cipher.verifyPassword(password, storedHash);
368
+ return Yes('成功');
369
+ }
370
+ } as ApiRoute;
371
+ ```
372
+
373
+ ### JWT 插件
374
+
375
+ JWT 插件自动读取配置文件中的 `auth` 配置:
376
+
377
+ ```json
378
+ // befly.dev.json
379
+ {
380
+ "auth": {
381
+ "secret": "your-dev-secret",
382
+ "expiresIn": "7d",
383
+ "algorithm": "HS256"
384
+ }
385
+ }
386
+ ```
387
+
388
+ ```typescript
389
+ // 通过 befly 访问
390
+ export default {
391
+ name: '登录',
392
+ auth: false,
393
+ handler: async (befly, ctx) => {
394
+ // 签发令牌
395
+ const token = befly.jwt.sign({ userId: user.id, role: user.role });
396
+ return Yes('登录成功', { token: token });
397
+ }
398
+ } as ApiRoute;
399
+ ```
400
+
401
+ ---
402
+
403
+ ## 使用示例
404
+
405
+ ### 示例 1:用户注册
406
+
407
+ ```typescript
408
+ export default {
409
+ name: '用户注册',
410
+ auth: false,
411
+ fields: {
412
+ email: { name: '邮箱', type: 'string', regexp: '@email' },
413
+ password: { name: '密码', type: 'string', min: 6, max: 100 }
414
+ },
415
+ required: ['email', 'password'],
416
+ handler: async (befly, ctx) => {
417
+ // 加密密码
418
+ const hashedPassword = await befly.cipher.hashPassword(ctx.body.password);
419
+
420
+ // 存储用户
421
+ await befly.db.insData({
422
+ table: 'user',
423
+ data: {
424
+ email: ctx.body.email,
425
+ password: hashedPassword
426
+ }
427
+ });
428
+
429
+ return Yes('注册成功');
430
+ }
431
+ } as ApiRoute;
432
+ ```
433
+
434
+ ### 示例 2:用户登录
435
+
436
+ ```typescript
437
+ export default {
438
+ name: '用户登录',
439
+ auth: false,
440
+ fields: {
441
+ email: { name: '邮箱', type: 'string', regexp: '@email' },
442
+ password: { name: '密码', type: 'string', min: 6, max: 100 }
443
+ },
444
+ required: ['email', 'password'],
445
+ handler: async (befly, ctx) => {
446
+ // 查询用户
447
+ const user = await befly.db.getDetail({
448
+ table: 'user',
449
+ columns: ['id', 'email', 'password', 'role'],
450
+ where: { email: ctx.body.email }
451
+ });
452
+
453
+ if (!user?.id) {
454
+ return No('用户不存在');
455
+ }
456
+
457
+ // 验证密码
458
+ const isValid = await befly.cipher.verifyPassword(ctx.body.password, user.password);
459
+ if (!isValid) {
460
+ return No('密码错误');
461
+ }
462
+
463
+ // 签发令牌
464
+ const token = befly.jwt.sign({
465
+ userId: user.id,
466
+ role: user.role
467
+ });
468
+
469
+ return Yes('登录成功', { token: token });
470
+ }
471
+ } as ApiRoute;
472
+ ```
473
+
474
+ ### 示例 3:API 签名验证
475
+
476
+ ```typescript
477
+ export default {
478
+ name: '第三方回调',
479
+ auth: false,
480
+ handler: async (befly, ctx) => {
481
+ const { data, signature, timestamp } = ctx.body;
482
+
483
+ // 验证时间戳(5分钟内有效)
484
+ if (Date.now() - timestamp > 5 * 60 * 1000) {
485
+ return No('请求已过期');
486
+ }
487
+
488
+ // 验证签名
489
+ const expectedSig = befly.cipher.hmacSha256('api-secret-key', `${data}${timestamp}`);
490
+
491
+ if (signature !== expectedSig) {
492
+ return No('签名验证失败');
493
+ }
494
+
495
+ // 处理业务逻辑
496
+ return Yes('验证成功');
497
+ }
498
+ } as ApiRoute;
499
+ ```
500
+
501
+ ### 示例 4:文件完整性校验
502
+
503
+ ```typescript
504
+ export default {
505
+ name: '上传文件',
506
+ handler: async (befly, ctx) => {
507
+ const { filePath, expectedHash } = ctx.body;
508
+
509
+ // 计算文件哈希
510
+ const actualHash = await befly.cipher.hashFile(filePath);
511
+
512
+ if (actualHash !== expectedHash) {
513
+ return No('文件校验失败');
514
+ }
515
+
516
+ return Yes('文件校验通过');
517
+ }
518
+ } as ApiRoute;
519
+ ```
520
+
521
+ ---
522
+
523
+ ## FAQ
524
+
525
+ ### Q: 密码应该用什么算法加密?
526
+
527
+ A: 使用 `hashPassword` 方法(bcrypt 算法)。不要使用 MD5、SHA 等哈希算法存储密码。
528
+
529
+ ### Q: JWT secret 应该如何设置?
530
+
531
+ A:
532
+
533
+ 1. 生产环境必须使用强密钥(32 字符以上随机字符串)
534
+ 2. 密钥应通过环境变量或配置文件注入
535
+ 3. 不要在代码中硬编码密钥
536
+
537
+ ```bash
538
+ # 生成随机密钥
539
+ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
540
+ ```
541
+
542
+ ### Q: JWT 过期后如何处理?
543
+
544
+ A:
545
+
546
+ 1. 客户端检测到 401 错误后引导用户重新登录
547
+ 2. 或实现 refresh token 机制
548
+
549
+ ### Q: MD5 和 SHA1 还能用吗?
550
+
551
+ A:
552
+
553
+ - **不要用于安全场景**(密码、签名等)
554
+ - 可以用于非安全场景(文件指纹、缓存键等)
555
+ - 推荐使用 SHA-256 或更高
556
+
557
+ ### Q: HMAC 和普通哈希有什么区别?
558
+
559
+ A:
560
+
561
+ - **哈希**:单向转换,任何人都能计算
562
+ - **HMAC**:带密钥的签名,只有知道密钥才能计算
563
+ - HMAC 用于验证数据完整性和来源
564
+
565
+ ### Q: 如何选择 JWT 算法?
566
+
567
+ A:
568
+
569
+ - **HS256**:对称算法,简单高效,适合单服务
570
+ - **RS256**:非对称算法,适合微服务(私钥签发,公钥验证)
571
+ - **ES256**:椭圆曲线,签名更短,性能更好
572
+
573
+ ### Q: bcrypt cost 参数如何选择?
574
+
575
+ A:
576
+
577
+ - 参考标准:单次验证耗时约 100-300ms
578
+ - 一般应用:`cost=10`(默认)
579
+ - 高安全性:`cost=12`
580
+ - 每增加 1,耗时翻倍