I accept payments through bitcoinpay.com. In the documentation I ran into an example of a payment verification. Before using this method, I decided to do this in the example. And no matter how hard I tried, it doesn’t work for me to get the same hash that they get. Here is an example from the documentation . But I will bring it here. Example response from server:

{"data": {"status": "pending", "payment_id": "rvnEvs3pzGVlBQaE", "settled_currency": "USD", "server_time": 1438681512, "paid_amount": "0.03545288", "reference": "{\"customer_email\": \"john.doe@example.org\", \"order_number\": 1234, \"customer_name\": \"John Doe\"}", "payment_url": "https://bitcoinpay.com/en/sci/invoice/btc/rvnEvs3pzGVlBQaE/", "price": "10.00", "confirmations": -1, "settled_amount": "6.95", "txid": "", "currency": "USD", "create_time": 1438688711, "item": "Order #1234", "address": "172eh5xbTW9Fu4EvaNZt1ymoUz9pV2snYG", "timeout_time": 1438689611, "paid_currency": "BTC", "description": "Order #1234 description"}} 

It is known that callback password is what I understand. QKG7m{dzv32mmYGN Key - QKG7m{dzv32mmYGN

the hash is calculated from the line like <answer> <password> and get the hash 3ee96c641d5fe230343950839aff469fa7c79b52bfefc5790bb9308ddeab605a

I brought everything to the string and then I calculated the hash, tried it and so on.

 j = {"data": {.....}} d = j["data"] 

Then I came across other documentation from the same site, where the hash was calculated not from the answer entirely, but from the date section, and the values ​​were sorted by key in alphabetical order. I tried to do as an example above.

 import hashlib d = {"status": "pending", "payment_id": "rvnEvs3pzGVlBQaE", "settled_currency": "USD", "server_time": 1438681512, "paid_amount": "0.03545288", "reference": "{\"customer_email\": \"john.doe@example.org\", \"order_number\": 1234, \"customer_name\": \"John Doe\"}", "payment_url": "https://bitcoinpay.com/en/sci/invoice/btc/rvnEvs3pzGVlBQaE/", "price": "10.00", "confirmations": -1, "settled_amount": "6.95", "txid": "", "currency": "USD", "create_time": 1438688711, "item": "Order #1234", "address": "172eh5xbTW9Fu4EvaNZt1ymoUz9pV2snYG", "timeout_time": 1438689611, "paid_currency": "BTC", "description": "Order #1234 description"} a = "QKG7m{dzv32mmYGN" s = "" l = d.keys() l.sort() for c in l: s+=str(d[c]) s +=a h = hashlib.sha256(s).hexdigest() print h 

Hash anyway did not match. Tell me how they calculated this hash in the first example? And then I guess how to count it at home. It seems that it is written that the string is from whole data + callback password, but in fact it turns out wrong. Maybe someone already implemented in python? I would be glad to see examples.

PS I came across a ready-made example from a module to some kind of cms, there it looked something like this:

 hash('sha256', '$RESPONSE_HTTP' . '$CALLBACK_PASSWORD') 

I am not familiar with PHP, but I think that there is a concatenation of the answer with a password and the calculation of the hash.

PPS I tried on a live example (listing below) to count. What is the first, that the second method, the hashes do not converge.

 #!/usr/bin/python #--*--coding: utf-8--*-- import json import hashlib from urllib2 import Request, urlopen pwd = "мой_callback_password" login = "zxcv432fg" email = "zxc@dfg.ru" satoshi = 0.001 values = { "settled_currency": "BTC", "return_url": "http://site.ru/buy/thankyou.html", "notify_url": "http://site.ru/cgi-bin/btc/order-received.cgi", "notify_email": "mail@mail.ru", "price": satoshi, "currency": "BTC", "reference": { "customer_name": login, "order_number": 123, "customer_email": email }, "item": "la2coin", "description": "buy la2coin" } data = json.dumps(values) headers = { 'Content-Type': 'application/json', 'Authorization': 'Token мой_ключ_апи' } request = Request('https://www.bitcoinpay.com/api/v1/payment/btc', data=data, headers=headers) response = urlopen(request) response_body = urlopen(request).read() signature = response.info()['BPSignature'] data_validate = response_body + pwd hash_string = hashlib.sha256(data_validate).hexdigest() print "ответ - ", response_body, '\n' print "строка для хеширования - ", data_validate, '\n' print "Хеш с заголовка - ", signature, '\n' print "Рассчитаный хеш - ", hash_string, '\n' if signature == hash_string: print "Validated succesfully!" else: print "Wrong Signature" 
  • one
    applied to support, but didn't get an answer - Sergey White
  • I brought everything to the string, and then I calculated the hash , show me how to lead to the string. the documentation says to take the response from the server as a string, which means j={...} definitely not suitable. I don’t know how in python, but if the text can be enclosed in single quotes there, then I would do a='{"data": {"status": "pending", ... i.e. with all the spaces like theirs. well, or somehow screen quotes that would be in one-to-one line of their anonymity - Mike
  • why do you think the hash for the server response? In theory, this technology is used to send requests. Those. formed a request, counted a hash for it and indicated in the request - so that the server could verify that nothing was "lost" on the way - newman
  • one
    @newman In payment systems, it is also customary to check server responses. because they are not answers, but requests. the processing server calls the http callback address when passing a payment, i.e. client-side script and reports payment. this is where he should be checked so that the attacker would not send his request about the money that had allegedly arrived - Mike
  • Please rename the question title. The title does not understand the meaning of the question. - MichaelPak

1 answer 1

So far, I have found one possible place for the occurrence of an "error" - an incorrect hash calculation

Namely field description. In the given example of data, it is, and therefore its value falls into the line for calculating the hash. This field is not mentioned in the documentation.

I would not form an array of keys for the sample based on the response received, as you did l = d.keys() , but would form it manually in the code, as a list of fields that should participate in the calculation of the hash. At the same time, you can additionally check that all the necessary fields were received from the service.

UPDATE: By the link https://bitcoinpay.com/api/v1/

In Python it is not strong, therefore I did it in PHP (by the way, the example given in the question is incorrect. Does not know PHP about the hash algorithm hash256)

  <?php $str = '1ADeusgHpfeB5wY3YbgCZty4zdxMF9BcLE31399569216EUR0.12231BTCtE8ZHEqEzJWUCGSDhttps://bitcoinpay.com/sci/invoice/btc/tE8ZHEqEzJWUCGSD/17.5{"customer_email": "customer@example.com", "order_number": 123, "customer_name": "Customer Name"}139956267417.31EURconfirmed1399569276f1be3a5df76864e3b7b13ded87ceac1d5c10887af6f7b1f1541f208f10d970daxxx'; echo(hash('sha256', $str)."\n"); ?> 

As a result, I get

 88fe519e4fae530df49b566d2b493a8f3836d51d2b9cf656ff4859ad9920656b 

This matches the value of the secret.callback_password_hash field secret.callback_password_hash

Those. it can be assumed that there are errors in the documentation and the bea ... e25 hash specified there is incorrect. Most likely remained from some editing.

Well, in any case, it is necessary to try on a lively website - the documentation is not always 100% correct and correct.

  • In the documentation for the first link I give, there is a description field. I consider exactly that example. I form an array of keys in order to sort the keys in alphabetical order and take their value (to sum up for example from the second link). - Sergey White
  • I would still rely on the second link. This is still information from the site itself, and not from a third-party resource. Although they have a mistake there too. In the documentation itself, the hash is incorrect, but in the sample data correctly. - newman
  • @SergeyWhite, completed the answer. - newman
  • with the second link it turned out. And yes, I also noticed that the correct one is only in the secret field. Now checked on a live example. that the first option (in the first option, by the way, the answer is already a string, no need to convert), which is the second. hashes do not match. besides in the answer of the secret did not see the field. Now I will add the code to the question, where I looked. - Sergey White