Implementation HMAC-SHA1 in python
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
Implementing SHA1-HMAC with Python
You should not use Base64 here. The site you link to gives you the hex values of the digest bytes. Use the HMAC.hexdigest()
method to get the same value in hex in Python:
>>> key = b'secret'
>>> message = b'123'
>>> digester = hmac.new(key, message, hashlib.sha1)
>>> digester.hexdigest()
'b14e92eb17f6b78ec5a205ee0e1ab220fb7f86d7'
put differently, your code outputs the correct value, but as Base64-encoded data:
>>> digester.digest()
b'\xb1N\x92\xeb\x17\xf6\xb7\x8e\xc5\xa2\x05\xee\x0e\x1a\xb2 \xfb\x7f\x86\xd7'
>>> base64.urlsafe_b64encode(digester.digest())
b'sU6S6xf2t47FogXuDhqyIPt_htc='
and the value you generated online contains the exact same bytes as the hex digest, so we can generate the same base64 output for that:
>>> bytes.fromhex('b14e92eb17f6b78ec5a205ee0e1ab220fb7f86d7')
b'\xb1N\x92\xeb\x17\xf6\xb7\x8e\xc5\xa2\x05\xee\x0e\x1a\xb2 \xfb\x7f\x86\xd7'
>>> base64.urlsafe_b64encode(bytes.fromhex('b14e92eb17f6b78ec5a205ee0e1ab220fb7f86d7'))
b'sU6S6xf2t47FogXuDhqyIPt_htc='
How to implement HMAC in python without using the hmac library?
I'm not understanding all the steps in your code, but here's a short example showing HMAC-SHA1 using only hashlib.sha1
, with a helper function xor
.
import hashlib
def xor(x, y):
return bytes(x[i] ^ y[i] for i in range(min(len(x), len(y))))
def hmac_sha1(key_K, data):
if len(key_K) > 64:
raise ValueError('The key must be <= 64 bytes in length')
padded_K = key_K + b'\x00' * (64 - len(key_K))
ipad = b'\x36' * 64
opad = b'\x5c' * 64
h_inner = hashlib.sha1(xor(padded_K, ipad))
h_inner.update(data)
h_outer = hashlib.sha1(xor(padded_K, opad))
h_outer.update(h_inner.digest())
return h_outer.digest()
def do_tests():
# test 1
k = b'\x0b' * 20
data = b"Hi There"
result = hmac_sha1(k, data)
print(result.hex())
# add tests as desired
problem implementing HMAC-SHA1 giving wrong Hash?
I would recommend using hmac library python provides unless you are learning cryptography.
Here's the fixed code:
""" An Hmac Implementation of SHA-1 """
from Crypto.Hash import SHA1
hasher = SHA1.new()
password = b"test"
message = b"NAME"
pad_length = 64 - len(password) # TODO: support longer key
key = password + bytes(pad_length) # padding should be on the right
ipad_num = "00110110" * 64 # ipad should be 0x36
opad_num = "01011100" * 64 # opad should be 0x5c
ipad = int.from_bytes(key, byteorder="big") ^ int(ipad_num, base=2)
opad = int.from_bytes(key, byteorder="big") ^ int(opad_num, base=2)
ipad = int.to_bytes(ipad, length=64, byteorder="big")
opad = int.to_bytes(opad, length=64, byteorder="big")
hasher.update(ipad + message)
inner_hash = hasher.digest()
print("inner hash {}".format(inner_hash.hex()))
hasher = SHA1.new()
hasher.update(opad + inner_hash)
print("final hash {}".format(hasher.hexdigest()))
python - hmac new sha1
Your version is missing the required ?
component before the Application=
parameter. You probably want to add in a &
if there are other parameters, however, and you need to remove the newline that .encode("base64")
adds to the end of the value:
def sign_url(url, key, secret):
sep = '&' if '?' in url else '?'
url = '{}{}ApplicationKey={}'.format(url, sep, key)
signature = hmac.new(secret, url, sha1).digest().encode("base64")
return '{}&Signature={}'.format(url, signature[:-1])
I note however that when the URL contains URL-encoded elements then the signature appears to be applied to the URL-decoded version (with +
interpreted as spaces), so you really want to add a urllib.unquote_plus()
(Python 2) / urllib.parse.unquote_plus()
(Python 3) call when signing:
try:
from urllib.parse import unquote_plus
except ImportError:
from urlib import unquote_plus
def sign_url(url, key, secret):
sep = '&' if '?' in url else '?'
url = '{}{}ApplicationKey={}'.format(url, sep, key)
signature = hmac.new(secret, unquote_plus(url), sha1).digest().encode("base64")
return '{}&Signature={}'.format(url, signature[:-1])
I've confirmed that this is what their PHP example code does and verified sample query parameters in the online signature tool, e.g. when entering the path and parameters foo%20bar?foo%20bar
into the tool, CCuMYpCDznH4vIv95+NrN+RHEK0=
is produced as the signature, but using foo+bar?foo+bar
produces the exact same signature even though +
should only be decoded as a space in form data.
I'd parse out the URL, add the ApplicationKey
parameter to the parsed parameters, and then construct a new URL to sign.
Here's a version that does just that, and works both on Python 2 and on Python 3:
import hmac
import base64
from hashlib import sha1
try:
# Python 3
from urllib.parse import parse_qsl, unquote_plus, urlencode, urlparse
except ImportError:
# Python 2
from urlparse import urlparse, parse_qsl
from urllib import unquote_plus, urlencode
def sign_url(url, key, secret):
parsed = urlparse(url)
query = parse_qsl(parsed.query)
query.append(('ApplicationKey', key))
to_sign = unquote_plus(parsed._replace(query=urlencode(query)).geturl())
if not isinstance(secret, bytes):
secret = secret.encode()
if not isinstance(to_sign, bytes):
to_sign = to_sign.encode()
signature = base64.b64encode(hmac.new(secret, to_sign, sha1).digest())
if not isinstance(signature, str):
signature = signature.decode()
query.append(('Signature', signature))
return parsed._replace(query=urlencode(query)).geturl()
Python hmac (sha1) calculation
Try using binascii.hexlify()
on the HMAC:
>>> from binascii import hexlify
>>> print hexlify(hmac.new(key, msg=msg, digestmod=hashlib.sha1).digest())
8c5a42f91479bfbaed8dd538db8c4a76b44ee5a9
Or you may just use str.encode('hex')
:
>>> print hmac.new(key, msg=msg, digestmod=hashlib.sha1).digest().encode('hex')
8c5a42f91479bfbaed8dd538db8c4a76b44ee5a9
HMAC-SHA1 in Python3 'bytes' object has no attribute 'encode'
That is because it is a byte and you are trying to encode like string. I fixed it:
from base64 import encodebytes
def sign_request():
from hashlib import sha1
import hmac
key = b"CONSUMER_SECRET&"
basestr = b"BASE_STRING"
hashed = hmac.new(key, basestr, sha1)
return str(encodebytes(hashed.digest())).rstrip('\n')
print(sign_request())
Verifying incoming requests on Python3 by HMAC-SHA1
From my understanding, you are not asking about OAuth 1.0 requests, you are asking about the sign and verify function, right?
If you this is what you are asking, I'm not sure if there are any libraries, but in Authlib's code, there is a module to do sign and verify signatures: https://github.com/lepture/authlib/blob/master/authlib/oauth1/rfc5849/signature.py
Checkout:
- sign_hmac_sha1 https://github.com/lepture/authlib/blob/master/authlib/oauth1/rfc5849/signature.py#L350
def sign_hmac_sha1(client, request):
"""Sign a HMAC-SHA1 signature."""
base_string = generate_signature_base_string(request)
return hmac_sha1_signature(
base_string, client.client_secret, client.token_secret)
- verify_hmac_sha1 https://github.com/lepture/authlib/blob/master/authlib/oauth1/rfc5849/signature.py#L368
def verify_hmac_sha1(request):
"""Verify a HMAC-SHA1 signature."""
base_string = generate_signature_base_string(request)
sig = hmac_sha1_signature(
base_string, request.client_secret, request.token_secret)
return hmac.compare_digest(sig, request.signature)
You can learn from Authlib code. But if you are just want to send OAuth 1.0 requests, you can use Authlib directly. Documentation is here: https://docs.authlib.org/en/latest/client/oauth1.html
Related Topics
Python Regex Escape Operator \ in Substitutions & Raw Strings
How to Run Pygame or Pyglet in a Browser
How to Run a Function Periodically in Python
Expected Conditions in Protractor
Python: Find_Element_By_Css_Selector
How to Hide the Console Window in a Pyqt App Running on Windows
Python Urllib2 Basic Auth Problem
Differencebetween Encode/Decode
Gradient Descent Using Python and Numpy
How to Use Jupyter Notebooks in a Conda Environment
Find the Most Frequent Number in a Numpy Array
Sorting Python List Based on the Length of the String
Speed of Calculating Powers (In Python)
Meaning of Inter_Op_Parallelism_Threads and Intra_Op_Parallelism_Threads