Skip to content

Instantly share code, notes, and snippets.

@six8
Last active March 9, 2018 17:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save six8/9054056 to your computer and use it in GitHub Desktop.
Save six8/9054056 to your computer and use it in GitHub Desktop.
Hawk (https://github.com/hueniverse/hawk) Python example
"""
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