-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathcrypto.ts
More file actions
154 lines (139 loc) · 4.82 KB
/
Copy pathcrypto.ts
File metadata and controls
154 lines (139 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import * as CryptoJS from 'crypto-js';
import { getCookie } from '@app/utils/common';
import { Buffer } from 'buffer';
import { JSEncrypt } from 'jsencrypt';
import { sm2, sm4 } from 'sm-crypto';
export function fillKey(key: string): Buffer | string {
const KeyLength = 16;
if (key.length > KeyLength) {
key = key.slice(0, KeyLength);
}
const filledKey = Buffer.alloc(KeyLength);
const keys = Buffer.from(key);
for (let i = 0; i < keys.length; i++) {
filledKey[i] = keys[i];
}
return filledKey;
}
function aesEncrypt(text, originKey) {
const key = CryptoJS.enc.Utf8.parse(fillKey(originKey));
return CryptoJS.AES.encrypt(text, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
}).toString();
}
function rsaEncrypt(text, pubKey) {
if (!text) {
return text;
}
const jsEncrypt = new JSEncrypt();
jsEncrypt.setPublicKey(pubKey);
return jsEncrypt.encrypt(text);
}
function rsaDecrypt(cipher, pkey) {
const jsEncrypt = new JSEncrypt();
jsEncrypt.setPrivateKey(pkey);
return jsEncrypt.decrypt(cipher);
}
function hexToBytes(hex) {
if (!hex) return new Uint8Array([]);
hex = hex.toString().trim().toLowerCase();
if (hex.startsWith('0x')) {
hex = hex.slice(2);
}
// 确保是偶数长度
const len = Math.floor(hex.length / 2);
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return bytes;
}
function bytesToBase64(bytes) {
// Uint8Array -> base64(标准 base64)
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function rsaEncryptPassword(password, rsaPublicKey) {
const aesKey = (Math.random() + 1).toString(36).substring(2);
// public key 是 base64 存储的
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey);
const passwordCipher = aesEncrypt(password, aesKey);
return `${keyCipher}:${passwordCipher}`;
}
function ensureSm2PublicKey(sm2PublicKey) {
// sm2.min.js 的 doEncrypt 需要能被 decodePointHex 解析的公钥:
// 通常为非压缩点 hex,格式 `04||x||y`(总长度 130)。
// 但后端生成/下发的公钥有时是 `x||y`(长度 128),这里做归一化补齐 `04` 前缀。
if (typeof sm2PublicKey === 'string') {
sm2PublicKey = sm2PublicKey.replace(/"/g, '').trim();
if (sm2PublicKey.startsWith('0x')) {
sm2PublicKey = sm2PublicKey.slice(2);
}
// 后端下发的 SM2 公钥常见是 x||y(128 hex),sm-crypto 需要 04||x||y(130 hex)
if (sm2PublicKey.length === 128 && !sm2PublicKey.startsWith('04')) {
sm2PublicKey = '04' + sm2PublicKey;
}
}
return sm2PublicKey;
}
function gmEncryptPassword(password, sm2PublicKey) {
sm2PublicKey = ensureSm2PublicKey(sm2PublicKey);
// 只适配前端,不改后端:
// 直接生成 16 字符 key(后端 padding_key 会保持原样,不再补齐)
const sm4KeyRaw = randomString(16);
const sm4KeyHex = Buffer.from(sm4KeyRaw).toString('hex');
let keyCipher = '';
try {
// 与后端 gmssl.sm2.CryptSM2 默认 decrypt 的 mode 对齐:
// gmssl 解析的格式是 C1C2C3(mode=0),前端这里输出也用 mode=0。
keyCipher = sm2.doEncrypt(sm4KeyRaw, sm2PublicKey, 0);
} catch (e) {
console.error('gmEncryptPassword sm2.doEncrypt failed:', e);
// 避免前端崩溃:失败时返回明文,由后端按原值流程处理(至少可继续登录/看报错)
return password;
}
const passwordCipher = sm4.encrypt(password, sm4KeyHex);
// sm2/sm4 默认输出是 hex,但后端 gm.py/session.py 需要 base64:
// - sm2_decrypt: base64.b64decode
// - sm4 decrypt: base64.urlsafe_b64decode
const keyCipherB64 = bytesToBase64(hexToBytes(keyCipher));
const passwordCipherB64 = bytesToBase64(hexToBytes(passwordCipher));
return `${keyCipherB64}:${passwordCipherB64}`;
}
export function encryptPassword(password) {
if (!password) {
console.log('password is empty');
return '';
}
let publicKeyText = getCookie('jms_public_key');
if (!publicKeyText) {
console.log('publicKeyText is empty');
return password;
}
publicKeyText = publicKeyText.replace(/"/g, '');
publicKeyText = atob(publicKeyText);
let cipher = '';
let jmsGMSSL = getCookie('jms_gm_ssl');
if (publicKeyText.includes('PUBLIC KEY')) {
jmsGMSSL = '0';
}
if (jmsGMSSL === '1') {
cipher = gmEncryptPassword(password, publicKeyText);
} else {
cipher = rsaEncryptPassword(password, publicKeyText);
}
return cipher;
}
export function randomString(length) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}