Implementation HMAC-SHA1 in python

67,978

Solution 1

Pseudocodish:

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')

Signature errors usually reside in the base-string, make sure you understand this (as stated by the OAuth1.0 spec here: https://datatracker.ietf.org/doc/html/draft-hammer-oauth-10#section-3.4.1).

The following inputs are used to generate the Signature Base String:

  1. HTTP Method (for example GET)

  2. Path (for example http://photos.example.net/photos)

  3. Parameters, alphabetically, such as (line breaks for readability):

     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
    

Concatenate and URL encode each part and it ends up as:

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

Solution 2

For the love of God, if you do ANYTHING with oauth, use the requests library for Python! I tried to implement HMAC-SHA1 using the hmac library in Python and it's a lot of headaches, trying to create the correct oauth base string and such. Just use requests and it's as simple as:

>>> 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 Authentication

Requests Oauth Library

Solution 3

You can try following method.

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')

Solution 4

It's already there Keyed-Hashing for Message Authentication

Solution 5

Finally here's an actually working solution (tested with Python 3) utilizing oauthlib.

I use the first OAuth step given as an example in the official RTF 1:

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"

The value for oauth_signature is what we would like to calculate.

The following defines what we want to sign:

# 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"

And here we go:

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
Share:
67,978
xiaohan2012
Author by

xiaohan2012

Updated on July 13, 2020

Comments

  • xiaohan2012
    xiaohan2012 almost 4 years

    I am trying to use the OAuth of a website, which requires the signature method to be 'HMAC-SHA1' only.

    I am wondering how to implement this in Python?

  • Priya Narayanan
    Priya Narayanan almost 12 years
    What's the [:-1] at the end for?
  • Jon Nylander
    Jon Nylander almost 12 years
    Oh, the last character is a \n, should not be part of the signature.
  • tterrace
    tterrace about 10 years
    The [:-1] at the end DOES do something, it removes the trailing \n as Jon Nylander said. It's a particularly nasty detail because if you leave it out your signature will match all the way to the last character, making it very easy to miss.
  • amacleod
    amacleod about 10 years
    You could use .rstrip('\n') in place of the [:1] if you wanted to sacrifice some brevity to make it quite clear that you are chopping off a trailing newline.
  • Tim Pogue
    Tim Pogue over 9 years
    Could you please explain why you use binascii.b2a_base64 and just couldn't simply do hashed.digest().encode("base64")?
  • Gobi Dasu
    Gobi Dasu over 7 years
    after 2 days of searching for a solution to what's wrong with my api call (stackoverflow.com/questions/39164472/…) i finally just used your trick and moved on
  • KayCee
    KayCee over 6 years
    This answered a few questions of my own; I had to add my private key and signature type to the OAuth1 object instantiation (see info here link eg: ` token_auth = OAuth1(consumer_key, consumer_secret, auth_token,token_secret, rsa_key=pkeystring,signature_type='auth_header')` [caveat, I was looking for RSA signing, so had to add signature_method=SIGNATURE_RSA, too]
  • Blairg23
    Blairg23 over 6 years
    @KayCee Glad I could help. Remember in Python: Simple > Complex.
  • Adam Strauss
    Adam Strauss over 3 years
    @JonNylander how to add pagination in NetSuite because I'm getting invalid login attempt while hitting next 1000 records