-1

I have a short CURL script that I'm trying to convert to Python, but I'm having a decent amount of trouble doing so. I'm targeting an API that requires an auth string that is formatted <public_key>.<sha256 hmac of url and body>.

My legacy setup is a simple PHP script that generates the hmac and runs a CURL command with the output. I'm trying to port this over to Python for a separate project.


Legacy code:

hmac.php

<?php

$public = 'PUBLIC_KEY';
$private = 'PRIVATE_KEY';

$url = $argv[1];
$body = $argv[2];

$hmac = hash_hmac('sha256', $url . $body, $private, true);

print($public . '.' . base64_encode($hmac));

Example command:

$ DATA='{"command": "say Just testing the API, ignore this..."}'
$ URL='https://website.url/api/user/<UUID>/command'
$ curl -X POST -d $DATA -H "Content-Type: application/json" -H "Authorization: Bearer $(php hmac.php $URL $DATA)" --url $URL

The Python Port

So far, I've figured out how to create the correct hmac digest for GET requests, but I'm having trouble with the POST requests I detailed above.

send_command.py:

def generate_bearer(pubkey, privkey, url, body):
    h = hmac.new(privkey.encode('utf8'), (url + body).encode('utf8'), sha256)
    encoded = b64encode(h.digest())
    return pubkey + '.' + encoded.decode('utf-8')

def send_command(server, command):
    target = '{}/server/{}/command'.format(url, server)
    data = {'command': command}

    bearer = generate_bearer(public, private, target, str(data))
    headers = {'Authorization': 'Bearer {}'.format(bearer),
               'content-type': 'application/json'}

    r = requests.post(target, headers=headers, data=data)

if __name__ == '__main__':
    print(send_command('<UUID>', 'say Just testing the API, ignore this...'))

As far as I can tell, I need to pass the full JSON string both to generate_bearer() and as the data I'm POSTing in the request. However, with this current code, I get the error "The HMAC for the request was invalid." What am I doing wrong here?

RalphORama
  • 355
  • 1
  • 3
  • 11

3 Answers3

1

It's very hard to tell from what you're describing what the problem might be. Is this API publicly available so others can test and be of better help?

I was comparing the PHP vs Python code and noticed that the URL you do not follow the same pattern:

but again is very hard to tell since the examples are incomplete or out of context.

Yoanis Gil
  • 3,022
  • 2
  • 15
  • 22
  • The API is publicly available, but not very well documented [here](https://docs.pterodactyl.io/v0.6/reference#send-command). The output of that formatted python string is identical to the PHP script, i.e. `https://website.url/api/user//command`, just using function parameters. – RalphORama Nov 30 '17 at 21:25
0

After a little bit of experimentation, I figured it out. When passing my data dict to the hashing function, I was using str(data). I switched to json.dumps(data) for both the hash and the request, and everything now works properly.

The new code looks like this:

def send_command(server, command):
    target = '{}/server/{}/command'.format(url, server)
    d = {'command': command}

    bearer = generate_bearer(public, private, target, json.dumps(d))
    h = {'Authorization': 'Bearer {}'.format(bearer),
         'content-type': 'application/json'}

    r = requests.post(target, headers=h, data=json.dumps(d))

    return r.text
RalphORama
  • 355
  • 1
  • 3
  • 11
0

I took the example here and converted it to Python like this:

import hmac
import hashlib
import base64

public = 'JkAFq7M47kLN0xVD';
private = 'E6X9FyZvMFeJbqtq.IwjlTuR.MKDoicB';

url = 'http://pterodactyl.app/api/admin/users';
body = '';

dig = hmac.new(private, msg='{}{}'.format(url, body), digestmod=hashlib.sha256).digest()

print '{}.{}'.format(public,  base64.b64encode(dig).decode()),

which returns the same output as the PHP code: JkAFq7M47kLN0xVD.wgIxj+V8RHgIetcQg2lRM0PRSH/y5M21cPz9zVhfFaQ

Yoanis Gil
  • 3,022
  • 2
  • 15
  • 22
  • The Python code is coming from this: https://stackoverflow.com/questions/1306550/calculating-a-sha-hash-with-a-string-secret-key-in-python – Yoanis Gil Nov 30 '17 at 21:39