我应该使用HMAC摘要的Base64还是仅使用HMAC hex digest?

时间:2016-03-09 19:57:19

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

图例

我公开了一个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

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标头值的格式 是:

Authorization: Base64(username:password)

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

正如您在我的代码段和亚马逊的文档中看到的那样,亚马逊也没有为Authorization标题的自定义值执行此操作。 将整个对包装成Base64(access_key:signature)以更接近这个RFC或者它根本不重要是一种更好的风格吗?

1 个答案:

答案 0 :(得分:3)

亚马逊确实使用签名版本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

您的示例来自签名版本2,这是较旧的算法,它使用Base-64编码进行签名(在最新的AWS区域也不支持)。

因此,您担心AWS知道您不会放错位,因为他们的新算法会使用它。

Authorization:标题中,除了几个额外的八位字节之外,它确实没有什么区别。

Base-64变得混乱的地方是签名在查询字符串中传递,因为+和(取决于你问的人)/=需要特殊处理 - 他们需要分别以%2B%2F%3D进行网址转义(&#34;百分比编码&#34;)...或者您必须为服务器上的可能变体...或者您必须要求使用非标准的Base-64字母表,其中+ / =变为- {{1 }} _ the way CloudFront does it。 (就个人而言,我发现CloudFront的解决方案很棒,但我离题了......这个特殊的非标准字母只是多种非标准选项中的一种,所有&#34;解决&#34;同样的魔术问题使用Base-64的URL中的字符。)

使用十六进制编码。

你几乎不可避免地会发现你的API的潜在消费者认为Base-64很难。&#34;