/** * 短信发送接口签名和编码工具 * * 用于生成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} 签名字符串(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 } }