3

I'm looking to implement a public (edit: rather, an authenticated api exposed to the internet) API on top of Django, and Django Rest Framework seems to be a popular choice. The APIs that I'm used to working with usually require that requests contain an API key, a nonce, and then a HMAC.

Looking at the authentication page, I see that the closest thing to an API key that's built in is JSON web token authentication.

I'd like to design an API that's secure against eavesdropping and replay attacks. If I were to implement this by hand, my instinct would be to generate API key & secret pairs for users and make them sign requests something like this:

import hmac, hashlib, time

API_KEY, API_SECRET = # loaded from environment
BASE_URL = "https://myservice/api/v1/"
def signed_request(method, endpoint, body=None):
    nonce = str(int(round(time.time() * 1000)))
    to_sign = nonce + method + endpoint + (body or '')
    sig = hmac.new(API_SECRET, to_sign, hashlib.sha256).hexdigest()

    headers = {
        'key' : API_KEY,
        'signature' : sig,
        'nonce' : nonce
    }

    # Actually make the request
    do_the_request(method, BASE_URL + endpoint, body, headers)

I would then verify on the server side that:

def verify_request(method, endpoint, body, headers):
    secret = get_secret_from_database(api_key=headers['key'])
    verify = headers['nonce'] + method + endpoint + body

    valid_sig = hmac.new(secret, verify, hashlib.sha256).hexdigest() == headers['sig']
    valid_nonce = int(headers['nonce']) > get_last_nonce_from_database(api_key=headers['key'])

    if valid_sig and valid_nonce:
        # Valid message
    else:
        raise Exception("Bad signature")

I'd like to know

  • Can I do this via the Django Rest Framework, and if not is there a package for Django that does this? (If this isn't possible, is there a reason it's not implemented?)
  • Is this a safe authentication scheme given my goals (only the person in possession of the token can generate valid requests, no replays, etc.)?
  • Is there a better, more idiomatic approach?
  • Is it even necessary to seek a third-party library, or are the edge cases limited enough that a home brewed solution--accompanied by unit tests--is sufficient?
  • 2
    A curiousity; your verify function is vulnerable to a timing attack. See if you can spot it :) – Dog eat cat world Oct 9 '17 at 15:22
  • Why not just implement SSL, and have that cover the eavesdropping and replay attack concerns? A cryptographically random API key should cover the rest of your concerns. – user52472 Oct 9 '17 at 18:41
  • 1
    @Dogeatcatworld Wow that is so cool!! Thank you. See, this is why I want to do this with a library, audited by people like yourself. :) – John Dorian Oct 10 '17 at 6:01
  • @user52472 Yeah that's fair. I guess my train of thought is that in a secure scheme, if a message is uncovered somehow it should be a privacy breach rather than a full compromise. I.e. if the client is mitm'd, the attacker should still have no ability to sign a valid message or replay the seen message. Is that overkill? – John Dorian Oct 10 '17 at 6:04
  • That's really a question of your requirements, and not something we can answer. – user52472 Oct 10 '17 at 14:38
2

Can I do this via the Django Rest Framework, and if not is there a package for Django that does this? (If this isn't possible, is there a reason it's not implemented?)

This cannot be done via the Django Rest Framework, and the djangohmac package suffers from the timing attack vulnerability identified by @Dogeatcatworld.

Is this a safe authentication scheme given my goals (only the person in possession of the token can generate valid requests, no replays, etc.)?

No, not completely. In order to be safe, the timing attack vulnerability needs to be removed. I.e. rather than checking signature == expected_sig which will terminate at different times due to where in the string a discrepancy is found, an algorithm that checks the whole string regardless needs to be used.

def is_equal(str_a, str_b):
    equal = True
    for i in range(min(len(str_a), len(str_b))):
        if str_a[i] != str_b[i]:
            equal = False
    return len(str_a) == len(str_b) and equal

Note that this still leaks the expected signature length, potentially.

Is there a better, more idiomatic approach?

That looks like it.

Is it even necessary to seek a third-party library, or are the edge cases limited enough that a home brewed solution--accompanied by unit tests--is sufficient?

Use the python libraries for all of the cryptography and do the length check yourself--that's fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.