为什么签名的验证不同?

huangapple go评论78阅读模式
英文:

Why are the validations of signatures different?

问题

export function verifyCloseSignature(
  request: Request,
  key: string,
  payload: any,
) {
  const headers = request.headers;

  const timestamp = headers.get('close-sig-timestamp');
  const providedSignature = headers.get('close-sig-hash');

  if (!timestamp) {
    throw new Error('[verifyCloseSignature] Required timestamp header missing');
  }

  if (!providedSignature) {
    throw new Error('[verifyCloseSignature] Required signature header missing');
  }

  const payloadString = JSON.stringify(payload);
  const hmac = crypto.createHmac('sha256', Buffer.from(key, 'hex'));
  hmac.update(timestamp + payloadString);
  const calculatedSignature = hmac.digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(providedSignature, 'hex'),
    Buffer.from(calculatedSignature, 'hex'),
  );
}

The code you provided appears to be correctly translated into TypeScript. If the data validated by the Python code fails validation in JavaScript, it may be due to differences in the input data or how the verifyCloseSignature function is called.

Make sure that the key, timestamp, payload, and signature values in your JavaScript code match the values used in your Python code. Any discrepancies in these values could lead to validation failures. Also, ensure that the verifyCloseSignature function is called with the correct arguments.

If you continue to experience issues, please provide more specific details about the input data and how the function is called so that I can help you further.

英文:

I need to validate a signature. The code examples for signature validation are only in Python. Here is the Python function:

import hmac
import hashlib
import json


def verify_signature(key, timestamp, provided_signature, payload):
  key_bytes = bytes.fromhex(key)
  payload_str = json.dumps(payload)
  data = timestamp + payload_str
  signature = hmac.new(key_bytes, data.encode('utf-8'),
                       hashlib.sha256).hexdigest()
  valid = hmac.compare_digest(provided_signature, signature)
  return valid

I need to translate some Python code into TypeScript.

Here is my best attempt:

export function verifyCloseSignature(
  request: Request,
  key: string,
  payload: any,
) {
  const headers = request.headers;

  const timestamp = headers.get('close-sig-timestamp');
  const providedSignature = headers.get('close-sig-hash');

  if (!timestamp) {
    throw new Error('[verifyCloseSignature] Required timestamp header missing');
  }

  if (!providedSignature) {
    throw new Error('[verifyCloseSignature] Required signature header missing');
  }

  const payloadString = JSON.stringify(payload);
  const hmac = crypto.createHmac('sha256', Buffer.from(key, 'hex'));
  hmac.update(timestamp + payloadString);
  const calculatedSignature = hmac.digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(providedSignature, 'hex'),
    Buffer.from(calculatedSignature, 'hex'),
  );
}

Why is this code not equivalent? Data that is validated by the Python code, fails the validation in JavaScript, when used like this:

const headers = new Headers();
headers.set('close-sig-hash', signature);
headers.set('close-sig-timestamp', timestamp.toString());
headers.set('Content-Type', 'application/json');
const request = new Request(faker.internet.url(), {
  method: 'POST',
  headers: headers,
  body: JSON.stringify(payload),
});

const actual = verifyCloseSignature(request, key, payload);
const expected = true;

expect(actual).toEqual(expected);

I expect the function to return true.

答案1

得分: 0

Python转换到TypeScript似乎有一点问题,否则它是正常的。

在你的Python片段中,你使用了json.dumps()将载荷转换为字符串值。然而,这不仅将载荷转换为字符串,它还会对载荷中的键进行排序。

这很重要,因为当你使用hmac.update()时,键的顺序非常重要。这意味着如果在TypeScript中验证签名时,键的顺序与原始签名不完全相同时,验证将失败。

然而,你可以通过使用JSON.stringify()来快速修复这个问题,因为它不会在载荷中对键进行排序。

以下是你如何修复你的TypeScript代码的方法:

export function verifyCloseSignature(
  request: Request,
  key: string,
  payload: any
) {
  const headers = request.headers;

  const timestamp = headers.get("close-sig-timestamp");
  const providedSignature = headers.get("close-sig-hash");

  if (!timestamp) {
    throw new Error("[verifyCloseSignature] Required timestamp header missing");
  }

  if (!providedSignature) {
    throw new Error("[verifyCloseSignature] Required signature header missing");
  }

  const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
  const hmac = crypto.createHmac("sha256", Buffer.from(key, "hex"));
  hmac.update(timestamp + sortedPayload);
  const calculatedSignature = hmac.digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(providedSignature, "hex"),
    Buffer.from(calculatedSignature, "hex")
  );
}
英文:

The conversion from Python to TypeScript seems to have a little issue, otherwise it is fine.

In your Python snippet you use json.dumps() to convert the payload to a string value. This does not only convert the payload to a string, however it also sorts the keys in the payload.

This is important since when you use hmac.update() the order of the keys is very important. This means if the keys are not ordered exactly the same way when you verify the signature in TypeScript it will not match the original signature and the validation fails.

However you can fix that pretty fast by using JSON.stringify() since this doesn't sort the keys in TypeScript in the payload.

Here is a way how you could fix your TypeScript Code:

Code:

export function verifyCloseSignature(
  request: Request,
  key: string,
  payload: any
) {
  const headers = request.headers;

  const timestamp = headers.get("close-sig-timestamp");
  const providedSignature = headers.get("close-sig-hash");

  if (!timestamp) {
    throw new Error("[verifyCloseSignature] Required timestamp header missing");
  }

  if (!providedSignature) {
    throw new Error("[verifyCloseSignature] Required signature header missing");
  }

  const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
  const hmac = crypto.createHmac("sha256", Buffer.from(key, "hex"));
  hmac.update(timestamp + sortedPayload);
  const calculatedSignature = hmac.digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(providedSignature, "hex"),
    Buffer.from(calculatedSignature, "hex")
  );
}

答案2

得分: 0

@SUTerliakov的SO链接帮助我找到了一个很好的解决方案。的确是由于额外的空格导致的。

以下是我最终使用的代码:

import { pipe, replace } from Ramda;

export const toJSONWithSpaces = pipe(
  (object: unknown) => JSON.stringify(object, null, 1), // 带有换行和缩进的字符串化
  replace(/\n +/gm, ' '), // 将换行和后续空格替换为一个空格
  replace(/:\s/g, ': '), // 确保冒号后有一个空格
  replace(/{\s/g, '{'), // 删除打开大括号后的空格
  replace(/\s}/g, '}'), // 删除关闭大括号前的空格
  replace(/\[\s/g, '['), // 删除打开方括号后的空格
  replace(/\s]/g, ']'), // 删除关闭方括号前的空格
  replace(/,\s/g, ', '), // 确保逗号后有一个空格
);

export function verifyCloseSignature(
  request: Request,
  key: string,
  payload: any,
) {
  const headers = request.headers;

  const timestamp = headers.get('close-sig-timestamp');
  const providedSignature = headers.get('close-sig-hash');

  if (!timestamp) {
    throw new Error('[verifyCloseSignature] 缺少必需的时间戳头');
  }

  if (!providedSignature) {
    throw new Error('[verifyCloseSignature] 缺少必需的签名头');
  }

  const hmac = crypto.createHmac('sha256', Buffer.from(key, 'hex'));
  const cleanedPayload = toJSONWithSpaces(payload);
  hmac.update(timestamp + cleanedPayload);
  const calculatedSignature = hmac.digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(providedSignature, 'hex'),
    Buffer.from(calculatedSignature, 'hex'),
  );
}
英文:

@SUTerliakov's SO link helped me to find a good solution. It is indeed caused by the extra white space.

Here is the code that I ended up with:

import { pipe, replace } from Ramda;

export const toJSONWithSpaces = pipe(
  (object: unknown) => JSON.stringify(object, null, 1), // stringify with line-breaks and indents
  replace(/\n +/gm, ' '), // replace line breaks and following spaces with a single space
  replace(/:\s/g, ': '), // ensure a space after colon
  replace(/{\s/g, '{'), // remove space after opening brace
  replace(/\s}/g, '}'), // remove space before closing brace
  replace(/\[\s/g, '['), // remove space after opening bracket
  replace(/\s]/g, ']'), // remove space before closing bracket
  replace(/,\s/g, ', '), // ensure a space after comma
);

export function verifyCloseSignature(
  request: Request,
  key: string,
  payload: any,
) {
  const headers = request.headers;

  const timestamp = headers.get('close-sig-timestamp');
  const providedSignature = headers.get('close-sig-hash');

  if (!timestamp) {
    throw new Error('[verifyCloseSignature] Required timestamp header missing');
  }

  if (!providedSignature) {
    throw new Error('[verifyCloseSignature] Required signature header missing');
  }

  const hmac = crypto.createHmac('sha256', Buffer.from(key, 'hex'));
  const cleanedPayload = toJSONWithSpaces(payload);
  hmac.update(timestamp + cleanedPayload);
  const calculatedSignature = hmac.digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(providedSignature, 'hex'),
    Buffer.from(calculatedSignature, 'hex'),
  );
}

huangapple
  • 本文由 发表于 2023年6月16日 01:09:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76484010.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定