Implementation Hmac-Sha1 in Python

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:

  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

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:

  1. 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)

  1. 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



Leave a reply



Submit