f
This commit is contained in:
212
src/utils/smsSignature.js
Normal file
212
src/utils/smsSignature.js
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* 短信发送接口签名和编码工具
|
||||
*
|
||||
* 用于生成HMAC-SHA256签名和自定义编码请求数据
|
||||
*/
|
||||
|
||||
/**
|
||||
* 自定义编码字符集(与后端保持一致)
|
||||
*/
|
||||
const CUSTOM_ENCODE_CHARSET = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?"
|
||||
|
||||
/**
|
||||
* 获取签名密钥(通过多种方式混淆,增加破解难度)
|
||||
* 注意:这只是示例,实际使用时应该进一步混淆
|
||||
*/
|
||||
function getSecretKey() {
|
||||
// 方式1: 字符串拆分和拼接
|
||||
const part1 = 'TyApi2024'
|
||||
const part2 = 'SMSSecret'
|
||||
const part3 = 'Key!@#$%^'
|
||||
const part4 = '&*()_+QWERTY'
|
||||
const part5 = 'UIOP'
|
||||
|
||||
// 方式2: 使用数组和join(增加混淆)
|
||||
const arr = [part1, part2, part3, part4, part5]
|
||||
return arr.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串(用于nonce)
|
||||
*/
|
||||
export function generateNonce(length = 16) {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let result = ''
|
||||
const array = new Uint8Array(length)
|
||||
crypto.getRandomValues(array)
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[array[i] % chars.length]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Web Crypto API生成HMAC-SHA256签名
|
||||
*
|
||||
* @param {Object} params - 请求参数对象
|
||||
* @param {string} secretKey - 签名密钥
|
||||
* @param {number} timestamp - 时间戳(秒)
|
||||
* @param {string} nonce - 随机字符串
|
||||
* @returns {Promise<string>} 签名字符串(hex编码)
|
||||
*/
|
||||
async function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式
|
||||
const keys = Object.keys(params)
|
||||
.filter(k => k !== 'signature') // 排除签名字段
|
||||
.sort()
|
||||
|
||||
const parts = keys.map(k => `${k}=${params[k]}`)
|
||||
|
||||
// 2. 添加时间戳和随机数
|
||||
parts.push(`timestamp=${timestamp}`)
|
||||
parts.push(`nonce=${nonce}`)
|
||||
|
||||
// 3. 拼接成待签名字符串
|
||||
const signString = parts.join('&')
|
||||
|
||||
// 4. 使用Web Crypto API计算HMAC-SHA256签名
|
||||
const encoder = new TextEncoder()
|
||||
const keyData = encoder.encode(secretKey)
|
||||
const messageData = encoder.encode(signString)
|
||||
|
||||
// 导入密钥
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
// 计算签名
|
||||
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData)
|
||||
|
||||
// 转换为hex字符串
|
||||
const hashArray = Array.from(new Uint8Array(signature))
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
|
||||
return hashHex
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义Base64编码(使用自定义字符集)
|
||||
*/
|
||||
function customBase64Encode(data) {
|
||||
if (data.length === 0) return ''
|
||||
|
||||
// 将字符串转换为UTF-8字节数组
|
||||
const encoder = new TextEncoder()
|
||||
const bytes = encoder.encode(data)
|
||||
const charset = CUSTOM_ENCODE_CHARSET
|
||||
let result = ''
|
||||
|
||||
// 将3个字节(24位)编码为4个字符
|
||||
for (let i = 0; i < bytes.length; i += 3) {
|
||||
const b1 = bytes[i]
|
||||
const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0
|
||||
const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0
|
||||
|
||||
// 组合成24位
|
||||
const combined = (b1 << 16) | (b2 << 8) | b3
|
||||
|
||||
// 分成4个6位段
|
||||
result += charset[(combined >> 18) & 0x3F]
|
||||
result += charset[(combined >> 12) & 0x3F]
|
||||
|
||||
if (i + 1 < bytes.length) {
|
||||
result += charset[(combined >> 6) & 0x3F]
|
||||
} else {
|
||||
result += '=' // 填充字符
|
||||
}
|
||||
|
||||
if (i + 2 < bytes.length) {
|
||||
result += charset[combined & 0x3F]
|
||||
} else {
|
||||
result += '=' // 填充字符
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用字符偏移混淆
|
||||
*/
|
||||
function applyCharShift(data, shift) {
|
||||
const charset = CUSTOM_ENCODE_CHARSET
|
||||
const charsetLen = charset.length
|
||||
let result = ''
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const c = data[i]
|
||||
if (c === '=') {
|
||||
result += c // 填充字符不变
|
||||
continue
|
||||
}
|
||||
|
||||
const idx = charset.indexOf(c)
|
||||
if (idx === -1) {
|
||||
result += c // 不在字符集中,保持不变
|
||||
} else {
|
||||
// 应用偏移
|
||||
const newIdx = (idx + shift) % charsetLen
|
||||
result += charset[newIdx]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义编码请求数据
|
||||
*/
|
||||
export function encodeRequest(data) {
|
||||
// 1. 使用自定义Base64编码
|
||||
const encoded = customBase64Encode(data)
|
||||
|
||||
// 2. 应用字符偏移混淆(偏移7个位置)
|
||||
const confused = applyCharShift(encoded, 7)
|
||||
|
||||
return confused
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并编码短信发送请求数据
|
||||
*
|
||||
* @param {string} phone - 手机号
|
||||
* @param {string} scene - 场景(register/login/change_password/reset_password等)
|
||||
* @returns {Promise<{data: string}>} 编码后的请求数据
|
||||
*/
|
||||
export async function generateSMSRequest(phone, scene) {
|
||||
// 1. 准备参数
|
||||
const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒)
|
||||
const nonce = generateNonce(16) // 生成随机字符串
|
||||
|
||||
const params = {
|
||||
phone: phone,
|
||||
scene: scene
|
||||
}
|
||||
|
||||
// 2. 生成签名
|
||||
const secretKey = getSecretKey()
|
||||
const signature = await generateSignature(params, secretKey, timestamp, nonce)
|
||||
|
||||
// 3. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: phone,
|
||||
scene: scene,
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature
|
||||
}
|
||||
|
||||
// 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码
|
||||
const jsonString = JSON.stringify(allParams)
|
||||
const encodedData = encodeRequest(jsonString)
|
||||
|
||||
// 5. 返回编码后的数据
|
||||
return {
|
||||
data: encodedData
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user