mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-05-15 11:41:23 +00:00
170 lines
5.2 KiB
Python
170 lines
5.2 KiB
Python
function fillKey(key) {
|
||
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)
|
||
}
|
||
|
||
|
||
window.rsaEncrypt = rsaEncrypt
|
||
window.rsaDecrypt = rsaDecrypt
|
||
|
||
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.replaceAll('"', '').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}`
|
||
}
|
||
|
||
|
||
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.replaceAll('"', '')
|
||
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
|
||
}
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
function testEncrypt() {
|
||
const radio = []
|
||
const len2 = []
|
||
for (let i = 1; i < 4096; i++) {
|
||
const password = randomString(i)
|
||
const cipher = encryptPassword(password)
|
||
len2.push([password.length, cipher.length])
|
||
radio.push(cipher.length / password.length)
|
||
}
|
||
return radio
|
||
}
|
||
|
||
window.encryptPassword = encryptPassword
|