ssara has asked for the wisdom of the Perl Monks concerning the following question:

Hello, I am trying to use POST method in perl to send information to an API. I would like to call the below api which requires following inputs:

URI: https://www.cryptopia.co.nz/api/GetBalance

Input:

Currency: (optional) The currency symbol of the balance to return e.g. 'DOT' (not required if 'CurrencyId' supplied)

CurrencyId: (optional) The Cryptopia currency identifier of the balance to return e.g. '2' (not required if 'Currency' supplied).

Request Structure:

REQUEST_SIGNATURE: API_KEY + "POST" + URI + NONCE + HASHED_POST_PARAMS

API_KEY: Your Cryptopia api key

URI: the request uri. e.g. https://www.cryptopia.co.nz/api/GetBalance

HASHED_POST_PARAMS: Base64 encoded MD5 hash of the post parameters

NONCE: unique indicator for each request.

Below is my code snippet:

use LWP::UserAgent; use JSON; use Digest::MD5 qw(md5); use Digest::SHA qw(hmac_sha256_base64); use MIME::Base64; my $api_key ='PUBLIC KEY'; my $api_private_key ='PRIVATE KEY'; my $ua = LWP::UserAgent->new; my $url = "https://www.cryptopia.co.nz/api/GetBalance"; my %req = ( Currency => "PAC" ); my $nonce = int(rand(1000000)); my $post_data = encode_json(\%req); my $post_data_enc = encode_base64(md5($post_data)); my $req_signature = sprintf("%sPOST%s%s%s", $api_key, lc(urlencode($ur +l)), $nonce, $post_data_enc); # Sign request signature with private key. my $req_signature_hmac = hmac_sha256_base64($req_signature, decode_bas +e64($api_private_key)); # Generate value for 'Authorization' header field. my $auth_header_value = sprintf("amx %s:%s:%s", $api_key, $req_signatu +re_hmac, $nonce); my $response = $ua->post($url, Content => $post_data, 'Content-Type' => 'application/json', 'charset' => 'utf-8', Authorization => $auth_header_value ); die "Request failed: ", $response->content unless $response->is_succes +s(); print $response->content, $/; sub urlencode { my $s = shift; $s =~ s/ /+/g; $s =~ s/([^A-Za-z0-9\+-])/sprintf("%%%02X", ord($1))/seg; return $s; }

This code gives the following error: {"Success":false,"Error":"Signature does not match request parameters."}.

I have also checked the PUBLIC and PRIVATE key and it works when i use a java or php code. But when I am doing with perl it just provides me error. Please can you help me on providing pointers.

Thank You in advance.

Replies are listed 'Best First'.
Re: POST API in Perl using LWP::UserAgent with authentication providing errors
by hippo (Archbishop) on Jan 20, 2018 at 12:03 UTC
    it works when i use a java or php code

    For testing only, change both the perl code and the java/php code to use a known nonce instead of a random one. Then compare the signatures you get from both perl and java/php for the same JSON request. If they differ, compare how you create them in both. Alter the perl code until they match. Then test with the API. Once that works switch the nonces back to random.

    FWIW, there are enough tried and tested URL encoding functions on CPAN that I would not bother to write my own. YMMV.

      Hello, thank you for your reply. The java code uses the below nonce value:  final String nonce = String.valueOf(System.currentTimeMillis());

      In perl i am using now

      use Time::HiRes qw(gettimeofday); my $timestamp = int (gettimeofday * 1000);

      Even after this change it still fails.:(.

Re: POST API in Perl using LWP::UserAgent with authentication providing errors
by poj (Abbot) on Jan 20, 2018 at 12:49 UTC

    2 problems I can see so far

    1. your urlencode() is escaping dots. As hippo said use a module. See the difference

    #!perl use strict; use URI::Escape; my $url = "https://www.cryptopia.co.nz/api/GetBalance"; print uri_escape($url)."\n"; print urlencode($url)."\n"; sub urlencode { my $s = shift; $s =~ s/ /+/g; $s =~ s/([^A-Za-z0-9\+-])/sprintf("%%%02X", ord($1))/seg; return $s; }

    2. With MIME::Base64 encode_base64( $bytes ) by default adds line endings.
    Try using encode_base64( $bytes,'' )

    poj

      I also tried using

      my $post_data_enc = encode_base64(md5($post_data),'');

      still it does not work!!

        When I run the node.JS code below I get a header value of

        amx PUBLIC KEY:leFWRGAm2eH6rm9t8hoHifpMpWPV5xMZcXtuhrQbUP0=:1516453770998

        Try the same values for keys and nonce in your code and compare

        var nonce = 1516453770998; var API_KEY = 'PUBLIC KEY' var API_SECRET = 'PRIVATE KEY'; var md5 = crypto.createHash('md5').update( JSON.stringify( {'Currency' +:"PAC"} ) ).digest(); var requestContentBase64String = md5.toString('base64'); var signature = API_KEY + "POST" + encodeURIComponent( 'https://www.cr +yptopia.co.nz/api/GetBalance' ).toLowerCase() + nonce + requestConten +tBase64String; var hmacsignature = crypto.createHmac('sha256', new Buffer( API_SECRET +, "base64" ) ).update( signature ).digest().toString('base64'); var header_value = "amx " + API_KEY + ":" + hmacsignature + ":" + nonc +e;
        poj