Last active
March 9, 2018 17:50
-
-
Save six8/9054056 to your computer and use it in GitHub Desktop.
Hawk (https://github.com/hueniverse/hawk) Python example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Hawk (https://github.com/hueniverse/hawk) Python example | |
The client attempts to access a protected resource without authentication, | |
sending the following HTTP request to the resource server: | |
GET /resource/1?b=1&a=2 HTTP/1.1 | |
Host: api.example.com:443 | |
The resource server returns an authentication challenge. | |
HTTP/1.1 401 Unauthorized | |
WWW-Authenticate: Hawk | |
The client has an API key and secret: | |
Key: g.love | |
Secret: specialsauce | |
Algorithm: sha256 | |
The client generates the authentication header by calculating a timestamp | |
(e.g. the number of seconds since January 1, 1970 00:00:00 GMT), | |
generating a nonce, and constructing the normalized request string | |
(each value followed by a newline character): | |
hawk.1.header | |
1392654871 | |
G8CQKgKs | |
GET | |
/resource/1?b=1&a=2 | |
api.example.com | |
443 | |
The request MAC is calculated using HMAC with the specified hash | |
algorithm "sha256" and the key over the normalized request string. | |
The result is base64-encoded to produce the request MAC: | |
VKur3+LbwCEW+7qY4WXNoR1uW8vgh8SU9yMZFgwIXVI= | |
The client includes the Hawk key identifier, timestamp, nonce, application | |
specific data, and request MAC with the request using the HTTP Authorization | |
request header field: | |
GET /resource/1?b=1&a=2 HTTP/1.1 | |
Host: api.example.com:443 | |
Authorization: Hawk id=g.love, ts=1392654871, nonce=G8CQKgKs, mac=VKur3+LbwCEW+7qY4WXNoR1uW8vgh8SU9yMZFgwIXVI= | |
The server validates the request by calculating the request MAC again based | |
on the request received and verifies the validity and scope of the Hawk | |
credentials. If valid, the server responds with the requested resource. | |
""" | |
import hmac, hashlib, base64, time, os | |
def signature(secret, options): | |
""" | |
Returns a MAC signature. | |
:param secret: API secret | |
:param options: dict with keys: | |
- ts -- UTC timestamp in seconds | |
- nonce -- Random string, min 6 chars, random for each request | |
- method -- HTTP method | |
- resource -- URL with GET query (ex: /index.html?x=1) | |
- host -- Hostname (ex: www.example.com) | |
- port -- Port (ex: 80, 443) | |
Java HMAC example: http://jokecamp.wordpress.com/2012/10/21/examples-of-creating-base64-hashes-using-hmac-sha256-in-different-languages/#java | |
""" | |
hash_items = [ | |
"hawk.1.header", | |
options["ts"], # Timestamp | |
options["nonce"], # Random nonce | |
options.get("method", "").upper(), # UPPERCASE HTTP method | |
options.get("resource", ""), # Request URL | |
options["host"].lower(), # Request host | |
options["port"], # Request port (must supply 80 for http, 443 for https unless specified otherwise) | |
"", # Payload hash, we're not using this right now | |
"", # LB after Payload hash | |
"", # Closing new line | |
] | |
# Make sure everything is a string before joining | |
hash_items = map(str, hash_items) | |
hash_str = '\n'.join(hash_items) | |
# Generate HMAC using sha256 | |
h = hmac.new(secret, hash_str, hashlib.sha256) | |
return base64.b64encode(h.digest()) | |
def make_hawk_header(key, secret, options): | |
mac = signature(secret, options) | |
header_items = [ | |
'id="%s"' % key, | |
'ts="%s"' % options["ts"], | |
'nonce="%s"' % options["nonce"], | |
] | |
if options.get("ext", None): | |
header_items.append('ext="%s"' % options["ext"]) | |
header_items.append('mac="%s"' % mac) | |
return 'Hawk ' + ', '.join(header_items) | |
key = 'g.love' | |
secret = 'specialsauce' | |
header = make_hawk_header(key, secret, { | |
"ts": int(time.time()), | |
"nonce": base64.urlsafe_b64encode(os.urandom(6)), # Some cryptographically random string | |
"method": "GET", | |
"resource": "/resource/1?b=1&a=2", | |
"host": "api.example.com", | |
"port": "443", | |
}) | |
print header |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment