Search code examples
pythonnode.jshashrsahttp-signature

Converting Nodejs signature hashing function to Python


I'm trying to connect to an API that only has Nodejs doc, but I need to use Python.

The official doc states the hhtp request need to be signed like this and only gives this code:

var pk = ".... your private key ....";
var data = JSON.strigify( {some JSON object} );
var signature = crypto.createSign('RSA-SHA256').update(data).sign(pk, 'base64');

So far, I am blocked there:

from Crypto.PublicKey import RSA 
from Crypto.Signature import PKCS1_v1_5 
from Crypto.Hash import SHA256 
import base64

private_key_loc='D:\\api_key'
BODY={ some JSON }
data=json.dumps(BODY)

def sign_data(private_key_loc, data):
    key = open(private_key_loc, "r").read() 
    rsakey = RSA.importKey(key,passphrase='c9dweQ393Ftg') 
    signer = PKCS1_v1_5.new(rsakey) 
    digest = SHA256.new()
    digest.update(data.encode())
    sign = signer.sign(digest) 
    return base64.b64encode(sign)

headers={}
headers['X-Signature']=sign_data(private_key_loc, data)
response = requests.post(url, headers=headers, data=BODY)

Esentially, I cannot make their code run on Nodejs; I don't know it and I get error because of the private key format. So I can't really compare what I do. Yet, I'm getting an forbidden message with the python.

Any idea?

----------------------------------------------------------------------

EDIT 1

Ok after two days, here is were I am: 1/ I managed to produce a valid signature with Nodejs using:

const crypto = require('crypto');
const fs = require('fs');
var pk = fs.readFileSync('./id_rsa4.txt').toString();
let sampleRequest = {accessKey: 'TESTKEY',reportDate: '2016-09-27T14:25:54.386Z'};
var data = JSON.stringify(sampleRequest);
var signature = crypto.createSign('RSA-SHA256').update(data).sign(pk, 'base64');

2/ Impossible to reproduce in Python.... Even worse, the whatever i try the hash is always half the size of the one in Nodejs:

import hmac
import hashlib
import base64
import json

private_key="""PRIVATE KEY SAME FORMAT BUT WITH LINE BREAKS LIKE \n"""
data=json.dumps({"accessKey": "TESTKEY","reportDate": "2016-09-27T14:25:54.386Z"})
dig = hmac.new(private_key, msg=data, digestmod=hashlib.sha256).digest()
print base64.b64encode(dig) #not valid
dig = hmac.new(private_key, msg=data, digestmod=hashlib.sha256).hexdigest()
print base64.b64encode(dig) #not valid either

This is SO frustrating, any more idea?


Solution

  • Ok, finally found my mistake.

    Basically I didn't know that there was a difference between RSA-SHA256 and SHA256...

    Here is the piece of code if needed:

    import base64
    from Crypto.Hash import SHA256
    from Crypto.PublicKey import RSA
    from Crypto.Signature import PKCS1_v1_5
    def _get_key(PEM_LOCATION):
        with open(PEM_LOCATION, 'rb') as secret_file:
            secret=secret_file.read()
            if (secret.startswith('-----BEGIN RSA PRIVATE KEY-----') or
                secret.startswith('-----BEGIN PRIVATE KEY-----')):
                # string with PEM encoded key data
                k = secret
                rsa_key = RSA.importKey(k)
                return PKCS1_v1_5.new(rsa_key)
            else:
                return None
    
    def get_signature(body):
        data=json.dumps(body)
        h=SHA256.new()
        h.update(data)
        return PRIVATE_KEY.sign(h)
    
    def get_signed_headers(body):
        sig=get_signature(body)
        headers={}
        headers['Content-type']='application/json'
        headers['X-Signature']=base64.b64encode(sig)
        return headers