Implementation HMAC-SHA1 in python
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:
-
HTTP Method (for example GET)
-
Path (for example http://photos.example.net/photos)
-
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)
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
xiaohan2012
Updated on July 13, 2020Comments
-
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 almost 12 yearsWhat's the [:-1] at the end for?
-
Jon Nylander almost 12 yearsOh, the last character is a
\n
, should not be part of the signature. -
tterrace about 10 yearsThe [:-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 about 10 yearsYou 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 over 9 yearsCould you please explain why you use
binascii.b2a_base64
and just couldn't simply dohashed.digest().encode("base64")
? -
Gobi Dasu over 7 yearsafter 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 over 6 yearsThis 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 over 6 years@KayCee Glad I could help. Remember in Python: Simple > Complex.
-
Adam Strauss over 3 years@JonNylander how to add pagination in NetSuite because I'm getting
invalid login attempt
while hitting next 1000 records