【问题标题】:Should I use Base64 of HMAC digest or just HMAC hex digest?我应该使用 HMAC 摘要的 Base64 还是只使用 HMAC 十六进制摘要?
【发布时间】:2016-06-24 09:45:27
【问题描述】:

传奇

我公开了一个 API,它要求客户端通过发送两个标头来签署请求:

Authorization: MyCompany access_key:<signature>
Unix-TimeStamp: <unix utc timestamp in seconds>

要创建签名部分,客户端应使用我的 API 服务发布的密钥。

在 Python (Py3k) 中,它可能看起来像:

import base64
import hmac
from hashlib import sha256
from datetime import datetime

UTF8 = 'utf-8'
AUTH_HEADER_PREFIX = 'MyCompany'

def create_signature(access_key, secret_key, message):
    new_hmac = hmac.new(bytes(secret_key, UTF8), digestmod=sha256)
    new_hmac.update(bytes(message, UTF8))
    signature_base64 = base64.b64encode(new_hmac.digest())
    return '{prefix} {access_key}:{signature}'.format(
        prefix=AUTH_HEADER_PREFIX,
        access_key=access_key,
        signature=str(signature_base64, UTF8).strip()
    )


if __name__ == '__main__':
    message = str(datetime.utcnow().timestamp())
    signature = create_signature('my access key', 'my secret key',  message)
    print(
        'Request headers are',
        'Authorization: {}'.format(signature),
        'Unix-Timestamp: {}'.format(message),
        sep='\n'
    )
    # For message='1457369891.672671', 
    # access_key='my access key' 
    # and secret_key='my secret key' will ouput:
    #
    # Request headers are
    # Authorization: MyCompany my access key:CUfIjOFtB43eSire0f5GJ2Q6N4dX3Mw0KMGVaf6plUI=
    # Unix-Timestamp: 1457369891.672671

我想知道是否可以避免将字节的摘要编码为 Base64,而只使用 HMAC.hexdigest() 来检索字符串。 这样我的功能将变为:

def create_signature(access_key, secret_key, message):
    new_hmac = hmac.new(bytes(secret_key, UTF8), digestmod=sha256)
    new_hmac.update(bytes(message, UTF8))
    signature = new_hmac.hexdigest()
    return '{prefix} {access_key}:{signature}'.format(
        prefix=AUTH_HEADER_PREFIX,
        access_key=access_key,
        signature=signature
    )

但后来我发现Amazon uses similar approach 在我的第一个代码 sn-p 中:

Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;

Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );

看到亚马逊不使用十六进制摘要,我停下来继续前进,因为也许他们知道一些我不知道的事情。


更新

我测量了性能,发现十六进制摘要更快:

import base64
import hmac
import string
from hashlib import sha256


UTF8 = 'utf-8'
MESSAGE = '1457369891.672671'
SECRET_KEY = 'my secret key'
NEW_HMAC = create_hmac()


def create_hmac():
    new_hmac = hmac.new(bytes(SECRET_KEY, UTF8), digestmod=sha256)
    new_hmac.update(bytes(MESSAGE, UTF8))
    return new_hmac


def base64_digest():
    return base64.b64encode(NEW_HMAC.digest())


def hex_digest():
    return NEW_HMAC.hexdigest()



if __name__ == '__main__':
    from timeit import timeit
    
    print(timeit('base64_digest()', number=1000000,
                  setup='from __main__ import base64_digest'))
    print(timeit('hex_digest()', number=1000000,
                 setup='from __main__ import hex_digest'))

结果:

3.136568891000934
2.3460130329913227

问题 #1

有人知道他们为什么坚持使用 Base64 字节摘要而不只使用十六进制摘要吗?是否有充分的理由继续使用这种方法而不是十六进制摘要?

问题 #2

根据RFC2716的格式Authorization使用Basic Authentication时的头部值 是:

Authorization: Base64(username:password)

所以基本上你用 Base64 包装两个用冒号分隔的值(用户 ID 和密码)。

正如您在我的代码 sn-p 和 Amazon 的文档中看到的那样,我也没有,Amazon 也没有为 Authorization 标头的自定义值这样做。 将整对包装为Base64(access_key:signature) 以更接近此 RFC 是否会是一种更好的样式,或者根本不重要?

【问题讨论】:

  • Base64 字符串将比十六进制短得多,在这种情况下为 44 对 64 个字符。如果您控制交易的双方,那么任何一方都应该工作。
  • 谢谢,@MarkRansom。好点子。虽然我刚刚测量了性能,但看起来 hexdigest 更快(请参阅问题中的更新部分)。
  • 与 HMAC 本身相比,编码时间可以忽略不计。
  • @kichik 我完全同意。尽管对于客户端和服务器上的每个请求,此操作节省了大约 30% 的 CPU 时间。 :)
  • 那是 15%,在我的机器上我看到 9%。我无法解释这里的巨大差异。我没想到。无论哪种方式,这仅适用于 HMAC 部分。我不会根据这么小的差异做出决定,尤其是因为您在这里谈论的是网络操作。

标签: python python-3.x digital-signature encode hmac


【解决方案1】:

亚马逊确实在签名版本 4 中使用十六进制摘要。

Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7

http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html

您的示例来自 Signature Version 2,这是较旧的算法,它确实使用 Base-64 编码作为签名(并且在最新的 AWS 区域也不支持)。

因此,您担心 AWS 知道您不知道的东西是错误的,因为他们的新算法使用它。

Authorization: 标头中,除了一些额外的八位字节之外,它确实没有什么不同。

Base-64 变得混乱的地方是在查询字符串中传递签名时,因为 + 和(取决于你问谁)/= 需要特殊处理——它们需要是 url-转义(“百分比编码”)分别为%2B%2F%3D...或者您必须针对服务器上可能的变化进行调整...或者您必须要求使用非标准 Base-64 字母表,其中 + / = 变为 - ~ _ the way CloudFront does it。 (这个特殊的非标准字母表只是多个非标准选项之一,它们都“解决”了使用 Base-64 的 URL 中的魔术字符的相同问题。

使用十六进制编码。

您几乎不可避免地会发现您的 API 的潜在消费者认为 Base-64 是“困难的”。

【讨论】:

  • 啊,我的问题中居然忘了说 url 编码。好点子。这是促使我使用十六进制的另一件事,因此客户端也可以轻松地在查询字符串中签署请求。
  • 所以我选择了十六进制摘要并发布了代码来帮助文明。 :) pypi.python.org/pypi/signit/0.1.0
猜你喜欢
  • 1970-01-01
  • 2020-09-06
  • 2012-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 2013-08-18
  • 2022-12-09
相关资源
最近更新 更多