57

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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
xiaohan2012
  • 9,870
  • 23
  • 67
  • 101

8 Answers8

94

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

Community
  • 1
  • 1
Jon Nylander
  • 8,743
  • 5
  • 34
  • 45
  • 1
    What's the [:-1] at the end for? – David d C e Freitas Jun 28 '12 at 08:19
  • 2
    Oh, the last character is a `\n`, should not be part of the signature. – Jon Nylander Jun 28 '12 at 20:20
  • 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. – tterrace Mar 29 '14 at 01:56
  • 5
    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. – amacleod May 15 '14 at 20:52
  • 1
    Could you please explain why you use `binascii.b2a_base64` and just couldn't simply do `hashed.digest().encode("base64")`? – Tim Pogue Oct 08 '14 at 15:12
  • @JonNylander how to add pagination in NetSuite because I'm getting `invalid login attempt` while hitting next 1000 records – Adam Strauss Feb 01 '21 at 12:22
41

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

Blairg23
  • 11,334
  • 6
  • 72
  • 72
  • 4
    after 2 days of searching for a solution to what's wrong with my api call (http://stackoverflow.com/questions/39164472/verification-of-signature-failed-oauth-1-upwork-api) i finally just used your trick and moved on – Gobi Dasu Aug 26 '16 at 11:18
  • 1
    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](http://requests-oauthlib.readthedocs.io/en/latest/oauth1_workflow.html) 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] – KayCee Oct 18 '17 at 16:16
  • @KayCee Glad I could help. Remember in Python: Simple > Complex. – Blairg23 Oct 18 '17 at 22:08
  • While the first help you understand ^ this is definitely the way to go. – John Jan 07 '23 at 06:28
13

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')
Qy Zuo
  • 2,622
  • 24
  • 21
9

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
Community
  • 1
  • 1
Raffael
  • 19,547
  • 15
  • 82
  • 160
8

It's already there Keyed-Hashing for Message Authentication

Sinux
  • 1,728
  • 3
  • 15
  • 28
Andrey Atapin
  • 7,745
  • 3
  • 28
  • 34
2

There are multiple python libraries available at the oauth website, but if you're just interested in a specific implementation you could have a look at one of them.

Till
  • 3,084
  • 17
  • 18
0

In Python 3.7 there is an optimized way to do this. HMAC(key, msg, digest).digest() uses an optimized C or inline implementation, which is faster for messages that fit into memory.

Return digest of msg for given secret key and digest. The function is equivalent to HMAC(key, msg, digest).digest(), but uses an optimized C or inline implementation, which is faster for messages that fit into memory. The parameters key, msg, and digest have the same meaning as in new().

CPython implementation detail, the optimized C implementation is only used when digest is a string and name of a digest algorithm, which is supported by OpenSSL.

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

SuperNova
  • 25,512
  • 7
  • 93
  • 64
0

In Python 3.7 there is an optimized way to do this. HMAC(key, msg, digest).digest() uses an optimized C or inline implementation, which is faster for messages that fit into memory.