在Python中实现HMAC-SHA1

58

我想使用一个要求签名方法必须为'HMAC-SHA1'的网站的OAuth,不知道如何在Python中实现?

8个回答

95

伪代码:

def sign_request():
    from hashlib import sha1
    import hmac

    # key = b"CONSUMER_SECRET&" #If you dont have a token yet
    key = b"CONSUMER_SECRET&TOKEN_SECRET" 


    # The Base String as specified here: 
    raw = b"BASE_STRING" # as specified by OAuth
       
    hashed = hmac.new(key, raw, sha1)
    
    # The signature
    return hashed.digest().encode("base64").rstrip('\n')

签名错误通常位于基本字符串中,请确保您理解这一点(正如OAuth1.0规范在此处所述:https://datatracker.ietf.org/doc/html/draft-hammer-oauth-10#section-3.4.1)。

以下输入用于生成签名基本字符串:

  1. HTTP方法(例如GET)

  2. 路径(例如http://photos.example.net/photos

  3. 按字母顺序排列的参数,例如(为了易读,使用换行符):

     file=vacation.jpg
     &oauth_consumer_key=dpf43f3p2l4k3l03
     &oauth_nonce=kllo9940pd9333jh
     &oauth_signature_method=HMAC-SHA1
     &oauth_timestamp=1191242096
     &oauth_token=nnch734d00sl2jdk
     &oauth_version=1.0
     &size=original
    

将每个部分连接起来并进行URL编码,最终生成的字符串如下:

GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal


1
结尾的 [:-1] 是什么意思? - David d C e Freitas
2
哦,最后一个字符是\n,不应该是签名的一部分。 - Jon Nylander
末尾的[:-1]确实有作用,它会删除换行符\n,就像Jon Nylander所说的那样。这是一个特别讨厌的细节,因为如果你不加它,你的签名将匹配到最后一个字符,很容易被忽略。 - tterrace
5
如果你想明确地去掉末尾的换行符,你可以使用 .rstrip('\n') 代替 [:1],虽然这样会稍微影响简洁。 - amacleod
1
请问您为什么使用 binascii.b2a_base64 而不是直接使用 hashed.digest().encode("base64") 呢? - Tim Pogue
@JonNylander,我在NetSuite中如何添加分页功能,因为当我点击下一个1000条记录时,出现了“无效的登录尝试”错误。 - Adam Strauss

41

天啊,如果你想在oauth中做任何事情,请使用Python的requests库!我曾尝试使用Python中的hmac库来实现HMAC-SHA1,但是这是一个很大的头痛,需要创建正确的oauth基本字符串等。只需使用requests,就像这样简单:

>>> import requests
>>> from requests_oauthlib import OAuth1

>>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET', 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')

>>> requests.get(url, auth=auth)

Requests身份验证

Requests OAuth库


4
搜索了两天以寻找解决API调用问题的方案(http://stackoverflow.com/questions/39164472/verification-of-signature-failed-oauth-1-upwork-api),最终我采用了你的技巧,顺利解决了问题。 - Gobi Dasu
1
这回答了我自己的一些问题;我必须将我的私钥和签名类型添加到OAuth1对象实例化中(请参见此处的信息[链接](http://requests-oauthlib.readthedocs.io/en/latest/oauth1_workflow.html)例如:`token_auth = OAuth1(consumer_key,consumer_secret,auth_token,token_secret,rsa_key = pkeystring,signature_type ='auth_header') [注意,我正在寻找RSA签名,因此还必须添加signature_method = SIGNATURE_RSA,` ] - KayCee
@KayCee 很高兴能帮到你。记住在Python中:简单 > 复杂。 - Blairg23
虽然第一个可以帮助你理解 ^,但这绝对是正确的方法。 - John

13

你可以尝试以下方法。

def _hmac_sha1(input_str):
        raw = input_str.encode("utf-8")
        key = 'your_key'.encode('utf-8')
        hashed = hmac.new(key, raw, hashlib.sha1)
        return base64.encodebytes(hashed.digest()).decode('utf-8')

9

最终这里是一个真正可行的解决方案(使用 Python 3 进行测试),利用 oauthlib

我使用官方 RTF 1 中给出的第一步 OAuth 示例作为示例:

Client Identifier: dpf43f3p2l4k3l03
Client Shared-Secret: kd94hf93k423kf44

POST /initiate HTTP/1.1
Host: photos.example.net
Authorization: OAuth realm="Photos",
    oauth_consumer_key="dpf43f3p2l4k3l03",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="137131200",
    oauth_nonce="wIjqoS",
    oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
    oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

oauth_signature的值是我们想要计算的内容。

以下是我们想要签名的定义:

# There is no query string present.
# In case of http://example.org/api?a=1&b=2 - the value
# would be "a=1&b=2".
uri_query=""

# The oauthlib function 'collect_parameters' automatically
# ignores irrelevant header items like 'Content-Type' or
# 'oauth_signature' in the 'Authorization' section.
headers={
    "Authorization": (
        'OAuth realm="Photos", '
        'oauth_nonce="wIjqoS", '
        'oauth_timestamp="137131200", '
        'oauth_consumer_key="dpf43f3p2l4k3l03", '
        'oauth_signature_method="HMAC-SHA1", '
        'oauth_callback="http://printer.example.com/ready"'
    )
}

# There's no POST data here - in case it was: x=1 and y=2,
# then the value would be '[("x","1"),("y","2")]'.
data=[]

# This is the above specified client secret which we need
# for calculating the signature.
client_secret="kd94hf93k423kf44"

好的,让我们开始吧:

import oauthlib.oauth1.rfc5849.signature as oauth

params = oauth.collect_parameters(
    uri_query="",
    body=data, 
    headers=headers,
    exclude_oauth_signature=True, 
    with_realm=False
)

norm_params = oauth.normalize_parameters(params)

base_string = oauth.construct_base_string(
    "POST", 
    "https://photos.example.net/initiate", 
    norm_params
)

sig = oauth.sign_hmac_sha1(
    base_string, 
    client_secret, 
    '' # resource_owner_secret - not used
)

from urllib.parse import quote_plus

print(sig)
# 74KNZJeDHnMBp0EMJ9ZHt/XKycU=

print(quote_plus(sig))
# 74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D

8

2

OAuth网站上有多个Python库可用,但如果你只对特定的实现感兴趣,可以看看其中之一


0
在Python 3.7中,有一种优化的方法来实现这个。HMAC(key, msg, digest).digest()使用一个经过优化的C或内联实现,对于适合放入内存的消息,速度更快。

返回给定秘密密钥和摘要的消息的摘要。该函数等效于HMAC(key, msg, digest).digest(),但使用了经过优化的C或内联实现,对于适合放入内存的消息,速度更快。参数key、msg和digest与new()中的含义相同。

CPython实现细节,只有在digest是字符串和openssl支持的摘要算法的名称时,才使用优化的C实现。

https://docs.python.org/3/library/hmac.html#hmac.digest


0
在Python 3.7中有一种优化的方法来完成这个任务。HMAC(key, msg, digest).digest() 使用了一个优化的C或内联实现,对于适合存储在内存中的消息速度更快。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接