1

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?

Breathe
  • 714
  • 5
  • 21

2 Answers2

2

You should be able to use the python standard library to generate the hashed signature. This will encode the data with the signed key. Depending on the server you may have to manually set header values in your request as well.

import hmac
import hashlib
import base64
import json

private_key = '12345'
data = json.dumps({'foo': 'bar'})
dig = hmac.new(private_key, msg=data, digestmod=hashlib.sha256).digest()
b64data = base64.b64encode(dig)
request.post(url, data=b64data)
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • thanks; I'm still getting: {'data': {'code': 403, 'message': 'Forbidden'}, 'status': 'fail'}. Could it be the format of the "private_key"? I tried with the "-----BEGIN RSA PRIVATE KEY-----[..]-----END RSA PRIVATE KEY-----", all in one line, also without, etc. – Breathe Oct 10 '16 at 19:19
  • I don't think it should include the "begin" and "end" stuff, and there shouldn't be any spaces (these get added at the end sometimes). – Brendan Abel Oct 10 '16 at 20:18
  • Tried again and again, but still unsuccessful. Is it possible that the implementation are different? It works on nidejs 6.5.0, and my python is 2.7.12 – Breathe Oct 16 '16 at 10:22
0

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
Breathe
  • 714
  • 5
  • 21