Implementation of Kraken API in Java
Asked Answered
R

2

7

I currently working on an implementation of the Kraken API for Java. I am using this sample code I found on http://pastebin.com/nHJDAbH8.

The general usage as described by Kraken (https://www.kraken.com/help/api) is:

API-Key = API key

API-Sign = Message signature using HMAC-SHA512 of
( URI path + SHA256( nonce + POST data ) ) and base64 decoded secret API key

and

nonce = always increasing unsigned 64 bit integer
otp = two-factor password ( if two-factor enabled, otherwise not required )

however I am facing the following response:

{"error":["EAPI:Invalid key"]}

I already tried a couple of ways ( getting a new API, trying to change the sha256 methods, because I thought something is wrong with the way it is hashed )

So this is the code:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
 
import org.apache.commons.codec.binary.Base64;
 
public class KrakenClient {
 
        protected static String key = "myAPIKey";     // API key
        protected static String secret = "MySecret====";  // API secret
        protected static String url = "api.kraken.com";     // API base URL
        protected static String version = "0"; // API version
 
 
        public static void main(String[] args) throws Exception {
                queryPrivateMethod("Balance");
        }
 
        public static void queryPrivateMethod(String method) throws NoSuchAlgorithmException, IOException{
 
                long nonce = System.currentTimeMillis();
 
                String path = "/" + version + "/private/" + method; // The path like "/0/private/Balance"
 
                String urlComp = "https://"+url+path; // The complete url like "https://api.kraken.com/0/private/Balance"
 
                String postdata = "nonce="+nonce;
 
                String sign = createSignature(nonce, path, postdata);
 
                postConnection(urlComp, sign, postdata);
        }
 
        /**
         * @param nonce
         * @param path
         * @param postdata
         * @return
         * @throws NoSuchAlgorithmException
         * @throws IOException
         */
        private static String createSignature(long nonce, String path,
                        String postdata) throws NoSuchAlgorithmException, IOException {
 
                return hmac(path+sha256(nonce + postdata),  new String(Base64.decodeBase64(secret)));
        }
 
        public static String sha256Hex(String text) throws NoSuchAlgorithmException, IOException{
                return org.apache.commons.codec.digest.DigestUtils.sha256Hex(text);
        }
 
        public static byte[] sha256(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException{
                MessageDigest md = MessageDigest.getInstance("SHA-256");
 
                md.update(text.getBytes());
                byte[] digest = md.digest();
 
                return digest;
        }
 
        public static void postConnection(String url1, String sign, String postData) throws IOException{
 
                URL url = new URL( url1 );
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 
                connection.addRequestProperty("API-Key", key);
                connection.addRequestProperty("API-Sign", Base64.encodeBase64String(sign.getBytes()));
                //              connection.addRequestProperty("API-Sign", sign);
                connection.addRequestProperty("User-Agent", "Mozilla/4.0");
                connection.setRequestMethod( "POST" );
                connection.setDoInput( true );
                connection.setDoOutput( true );
                connection.setUseCaches( false );
                //              connection.setRequestProperty( "Content-Type",
                //                              "application/x-www-form-urlencoded" );
                connection.setRequestProperty( "Content-Length", String.valueOf(postData.length()) );
 
                OutputStreamWriter writer = new OutputStreamWriter( connection.getOutputStream() );
                writer.write( postData );
                writer.flush();
 
 
                BufferedReader reader = new BufferedReader(
                                new InputStreamReader(connection.getInputStream()) );
 
                for ( String line; (line = reader.readLine()) != null; )
                {
                        System.out.println( line );
                }
 
                writer.close();
                reader.close();
        }
 
 
        public static String hmac(String text, String secret){
 
                Mac mac =null;
                SecretKeySpec key = null;
 
                // Create a new secret key
                try {
                        key = new SecretKeySpec( secret.getBytes( "UTF-8"), "HmacSHA512" );
                } catch( UnsupportedEncodingException uee) {
                        System.err.println( "Unsupported encoding exception: " + uee.toString());
                        return null;
                }
                // Create a new mac
                try {
                        mac = Mac.getInstance( "HmacSHA512" );
                } catch( NoSuchAlgorithmException nsae) {
                        System.err.println( "No such algorithm exception: " + nsae.toString());
                        return null;
                }
 
                // Init mac with key.
                try {
                        mac.init( key);
                } catch( InvalidKeyException ike) {
                        System.err.println( "Invalid key exception: " + ike.toString());
                        return null;
                }
 
 
                // Encode the text with the secret
                try {
 
                        return new String( mac.doFinal(text.getBytes( "UTF-8")));
                } catch( UnsupportedEncodingException uee) {
                        System.err.println( "Unsupported encoding exception: " + uee.toString());
                        return null;
                }
        }
}
Renown answered 4/7, 2016 at 16:32 Comment(6)
Have you had a look at the example client linked on the kraken API page? I think the C# client is easy to understand and should be portable to Java without major effort.Shornick
I tried to understand the go example but had some problems, I will try the C# example tonight - thank you very much.Renown
Just out of curiosity you replaced the values for key and secret with some real values that you obtained from kraken, yes?Shornick
Yes I did - i even tried two different values. The C# couldn't help me. I think there is something wrong with the api key, since it gives me the error message for the key. If I change the secret key with same random stuff though, it gives me the error message for the signature. As far as I understood the C# implementation the key is just copied into the http header, which I have doneRenown
You could try to run one of the official examples with your API key, to see if it's actually the key, that has issues...Shornick
as of today i would suggest using an api like org.knowm.xchangePyroelectric
R
15

Here is a working example:

static String key = "---myKey---";
static String secret = "---mySecret---";
String nonce, signature, data, path;
static String domain = "https://api.kraken.com";

void account_balance() {
    nonce = String.valueOf(System.currentTimeMillis());
    data = "nonce=" + nonce;
    path = "/0/private/Balance";
    calculateSignature();
    String answer = post(domain + path, data);
    // on empty accounts, returns {"error":[],"result":{}}
    // this is a known Kraken bug
    ...
}

String post(String address, String output) {
    String answer = "";
    HttpsURLConnection c = null;
    try {
        URL u = new URL(address); 
        c = (HttpsURLConnection)u.openConnection();
        c.setRequestMethod("POST");
        c.setRequestProperty("API-Key", key);
        c.setRequestProperty("API-Sign", signature);
        c.setDoOutput(true);
        DataOutputStream os = new DataOutputStream(c.getOutputStream());
        os.writeBytes(output);
        os.flush();
        os.close();
        BufferedReader br = null;
        if(c.getResponseCode() >= 400) {
            System.exit(1);
        }
        br = new BufferedReader(new InputStreamReader((c.getInputStream())));
        String line;
        while ((line = br.readLine()) != null)
            answer += line;
    } catch (Exception x) {
        System.exit(1);
    } finally {
        c.disconnect();
    }
    return answer;        
}

void calculateSignature() {
    signature = "";
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update((nonce + data).getBytes());
        Mac mac = Mac.getInstance("HmacSHA512");
        mac.init(new SecretKeySpec(Base64.decodeBase64(secret.getBytes()), "HmacSHA512"));
        mac.update(path.getBytes());
        signature = new String(Base64.encodeBase64(mac.doFinal(md.digest())));
    } catch(Exception e) {}
    return;
}
Rocha answered 28/3, 2017 at 23:10 Comment(0)
P
0
private String postingData(Map<String, String> data) {
    final StringBuilder postData = new StringBuilder();
    for (final Map.Entry<String, String> param : data.entrySet()) {
        if (postData.length() > 0) {
            postData.append("&");
        }
        postData.append(param.getKey());
        postData.append("=");
        postData.append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8));
    }

    return postData.toString();
}

    public Mono<KrakenPostResult> postOrder(String symbol, double volume, double price, KrakenBuySell buySell) {
            String nonce = String.valueOf(System.currentTimeMillis());
            Map<String, String> params = new HashMap<>();
    
            params.put("nonce", nonce);
            params.put("ordertype", ORDER_TYPE);
            params.put("pair", symbol);
            params.put("type", buySell.name().toLowerCase());
            params.put("volume", String.valueOf(volume));
    
            try {
    
                return krakenCall.postOrder(new KrakenOrderPost(
                        props.apiKey(),
                        signature(props.apiSecret(), postingData(params), nonce, PATH),
                        postingData(params)
                ));
    
            } catch (Exception e) {
                logger.warn(e.getMessage());
                return Mono.just(new KrakenPostResult(false, e.getMessage()));
            }
        }
    
        public String signature(String privateKey, String encodedPayload, String nonce, String endpointPath) throws NoSuchAlgorithmException, InvalidKeyException {
            final byte[] pathInBytes = endpointPath.getBytes(StandardCharsets.UTF_8);
            final String noncePrependedToPostData = nonce + encodedPayload;
            final MessageDigest md = MessageDigest.getInstance("SHA-256");
    
            md.update(noncePrependedToPostData.getBytes(StandardCharsets.UTF_8));
    
            final byte[] messageHash = md.digest();
            final byte[] base64DecodedSecret = Base64.getDecoder().decode(privateKey);
            final SecretKeySpec keyspec = new SecretKeySpec(base64DecodedSecret, "HmacSHA512");
    
            Mac mac = Mac.getInstance("HmacSHA512");
            mac.init(keyspec);
    
            mac.reset();
            mac.update(pathInBytes);
            mac.update(messageHash);
    
            return Base64.getEncoder().encodeToString(mac.doFinal());
        }

    public Mono<KrakenPostResult> postOrder(KrakenOrderPost orderPost) {
            return client.post()
                    .uri(uriBuilder -> uriBuilder.path(PRIVATE_URL).build()
                    )
                    .header(API_KEY_HEADER, orderPost.apiKey())
                    .header(API_SIGN_HEADER, orderPost.signature())
                    .header("Content-Type", "application/x-www-form-urlencoded")
                    .bodyValue(orderPost.data())
                    .retrieve()
                    .bodyToMono(Map.class)
                    .map(it -> {
    
                        Map response = it;
    
                        if (!((List) response.get("error")).isEmpty()) {
                            logger.warn("Kraken order failed");
                            return new KrakenPostResult(false, "Kraken order failed");
                        }
    
                        logger.info(String.format("Order Created %s", orderPost.data()));
                        return new KrakenPostResult(true, String.format("Order Created %s", orderPost.data()));
    
                    })
                    .doOnError(e -> {
                        logger.warn(e.getMessage());
                        logger.warn(String.format(String.format("Kraken post failed: %", orderPost)));
                    });
        }

Test the method that is generating the signature....

    @Test
    void WillGenerateSignature() throws NoSuchAlgorithmException, InvalidKeyException {
        String pk = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==";
        String nonce = "1616492376594";
        String data = "nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25";
        String path = "/0/private/AddOrder";

        assertThat(service.signature(pk, data, nonce, path)).isEqualTo("4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==");
    }
Postimpressionism answered 19/6 at 10:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.