【javascript】什么是HMAC-SHA256 签名

最近做了一个需求,就是利用HMAC-SHA256 对接口进行签名,签名的流程是

将生成随机字符串、时间戳、待签名的请求数据,排序后,利用HMAC-SHA256结合 AppSercet 生成签名,放在请求的 header 里面

1、随机生成字符串

ts 复制代码
/**

* 生成随机字符串
* @param length 字符串长度,默认32
* @returns 随机字符串

*/
export function generateNonce(length: number = 32): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let nonce = '';

for (let i = 0; i < length; i++) {
nonce += chars.charAt(Math.floor(Math.random() * chars.length));
}
return nonce;
}

2、 生成时间戳

javascript 复制代码
/**
* 生成时间戳(毫秒)
* @returns 时间戳字符串
*/

export function generateTimestamp(): string {
return Date.now().toString();
}

3、对请求体进行排序

ts 复制代码
/**
 * 对data进行排序
 * @param obj  body
 * @returns  排序后的对象
 */
export function canonicalString(obj: any): string {

    if (obj === null || typeof obj !== "object") return JSON.stringify(obj);

    if (Array.isArray(obj)) {
        return "[" + obj.map(canonicalString).join(",") + "]";
    }
    // 对象:先过滤掉 undefined 的字段,再排序
    const keys = Object.keys(obj)
        .filter((key) => obj[key] !== undefined) // 过滤掉 undefined
        .sort();

    const kv = keys.map((k) => `"${k}":${canonicalString(obj[k])}`);

    return "{" + kv.join(",") + "}";
}

4、 将时间戳、随机字符串、请求体拼接

ts 复制代码
/**
* 生成API请求签名
* @param body 请求体(对象或null)
* @param timestamp 时间戳(可选,不提供则自动生成)
* @param nonce 随机串(可选,不提供则自动生成)
* @returns Promise<{signature: string, timestamp: string, nonce: string}>
*/
export async function generateSignature(
body: any = null,
timestamp?: string,
nonce?: string
): Promise<{
signature: string;
timestamp: string;
nonce: string;
}> {

  // 生成时间戳和随机串
    const ts = timestamp || generateTimestamp();
    const n = nonce || generateNonce();

    // 构建待签名字符串
    let payload = canonicalString(data)

      // 这里与后端约定好拼接的规则,不一定是这个
    const signString = [payload, ts, n, API_CONFIG.APP02].join('&');

    // 利用 APP_SECERT 计算签名
    const signature = await calculateHMAC(signString, API_CONFIG.APP_SECERT);

return {
signature,
timestamp: ts,
nonce: n,
};
}

5、计算签名

在这里是碰到一个问题,最开始的写法是使用 Web Crypto API, 在线上(http)会有问题,本地 localhost 问题, 原因是 crypto.title 为 false, 所以后续的逻辑走不下去,原因是这个仅支持 localhost 和 https,所以换了一种方案,使用crypto-js 来计算签名,这是使用纯 js 实现的,与浏览器 API 没有关系,支持 http/https

ts 复制代码
/**

* 使用 HMAC-SHA256 计算签名
* @param message 待签名字符串
* @param secret 密钥
* @returns Promise<string> 签名字符串(hex格式)
*/

export async function calculateHMAC(
    message: string,
    secret: string
): Promise<string> {


    // 使用 crypto-js 计算 HMAC-SHA256
    const hash = CryptoJS.HmacSHA256(message, secret);
    const hashHex = hash.toString(CryptoJS.enc.Hex);

    return hashHex

    // // 使用 Web Crypto API
    // const encoder = new TextEncoder();
    // const keyData = encoder.encode(secret);
    // const messageData = encoder.encode(message);

    // alert(`keyData-${keyData}`)
    // alert(`messageData-${messageData}`)
    // alert(`Before importKey, crypto.subtle exists: ${!!crypto.subtle}`);

    // // 导入密钥
    // let cryptoKey;
    // try {
    //     cryptoKey = await crypto.subtle.importKey(
    //         "raw",
    //         keyData,
    //         {
    //             name: "HMAC",
    //             hash: "SHA-256",
    //         },
    //         false,
    //         ["sign"]
    //     );
    //     alert(`After importKey`);
    // } catch (error: any) {
    //     alert(`importKey error: ${error.message}`);
    //     throw error;
    // }
    // // 计算签名
    // const signature = await crypto.subtle.sign(
    //     'HMAC',
    //     cryptoKey,
    //     messageData
    // );

    // alert(`signature-${signature}`)

    // // 转换为十六进制字符串
    // const hashArray = Array.from(new Uint8Array(signature));
    // alert(`hashArray-${hashArray}`)
    // const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    // alert(`hashHex-${hashHex}`)

    // return hashHex;
}

6、 为请求添加 header 参数

ts 复制代码
/**

* 为请求添加签名Header
* @param headers 现有的请求头对象
* @param body 请求体
* @returns Promise<Record<string, string>> 包含签名信息的请求头
*/

export async function addSignatureHeaders(

headers: Record<string, string> = {},

body: any = null
): Promise<Record<string, string>> {
const { signature, timestamp, nonce } = await generateSignature(body);


return {

...headers,

'X-App-Id': API_CONFIG.APP_ID,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature,
};
}

现在我要弄清楚,为什么要签名,其实就是为了告诉请求服务器是自己人发的,防止篡改数据,防止伪装数据。密钥前后端约定好, 签名用在无 access_token 的场景中,通常是发送验证码等场景

SHA256是一种哈希算法,转换成一串固定的 64 位十六进制字符串(叫 "哈希值")。它的特点是 "不可逆"------ 只能从内容算出哈希值,没法从哈希值反推回内容;而且内容只要改一个字符,哈希值就会完全不同。

HMAC 是SHA256的基础上还要加上一个 "只有你和对方知道的密钥"。这样一来,就算别人拿到原始内容,没有密钥也算不出正确的哈希值,安全性比单纯的 SHA256 更高。

相关推荐
有点笨的蛋3 小时前
深入前端工程的细枝末节:那些被忽略却决定页面体验的 CSS 关键细节
前端·css
Holin_浩霖3 小时前
mini-react 动态渲染复杂的DOM结构
前端
一点七加一3 小时前
Harmony鸿蒙开发0基础入门到精通Day11--TypeScript篇
前端·javascript·typescript
超绝大帅哥3 小时前
你不知道的javascript学习心得
前端
小皮虾3 小时前
告别胶水代码!一行命令,让你的小程序云函数实现API路由自动化
前端·rpc·小程序·云开发
Revol_C3 小时前
【Element Plus】升级版本后,el-drawer自定义的关闭按钮离奇消失之谜
前端·css·vue.js
烟袅3 小时前
小程序开发入门:从结构到事件,快速掌握核心要点
前端·微信小程序
caicai_lf_niuniu3 小时前
🌳 ComboTreeV2:高性能虚拟树
前端·vue.js
BLOOM3 小时前
新一代前端数据mock工具Data Faker
前端·javascript