【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 更高。

相关推荐
乘风gg34 分钟前
还在养虾吗?虾王已诞生:微信龙虾 ClawBot
前端·ai编程·claude
小小小小宇1 小时前
LLM 长期记忆构建
前端
lichenyang4531 小时前
从 Express 老项目到 NestJS + Docker:一次车辆管理系统的渐进式重构
前端
Momo__2 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富2 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇2 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇2 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆3 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马3 小时前
Verilog开发常见问题汇总解析
前端
子兮曰3 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端