Request Signing

To preventing packet tampering, some of the API endpoints requires Request Signing. The request signature is created with an HMAC using SHA256. The hash should be created using a concatenation of the following strings using the credential secret comma-separated:

  • The uppercase HTTP method being used, e.g. GET or POST.
  • The request path, e.g. /consumers.
  • The UNIX timestamp in seconds for the request. This must be within 30 seconds of the time at which the request is received at the server or the request will not be considered valid.
  • If present, the request body as a single line stringified JSON.

Then, pass the signature information as HTTP Headers in the request:

  • X-Request-Timestamp: The timestamp of the request signing.
  • X-Request-Signature: The computed hash for the request.

Troubleshooting

The request signature is needed for account protection, so it is very restrictive with its parameters. Please make sure you have:

  • Set your device clock to automatic date time, synchronized with a trusted NTP (we use Google's Time);
  • A valid set and active of OAuth Client Credentials (the one used as a Basic Authorization header);
  • No paddings added to the signing params around the commas;
  • A "fresh" signature (it will only be accepted within 30s from the server current timestamp).

Handling Dates and Timezones

For date values in the request body, you must use the ISO 8601 standard, this ensure consistency across different localization techniques and timezones.

For more information, refer to the specific Dates and Timezones guidelines.


Sample Codes

You can check the Request Signing Toolbox in the Bitcapital Labs for more information and sample codes.

import * as CryptoJS from 'crypto-js';

export interface RequestSigningHeaders {
  'X-Request-Signature': string;
  'X-Request-Timestamp': string;
}

export interface RequestSigningOptions {
  method: string;
  url: string;
  body?: string;
  timestamp?: string;
}

export class RequestUtil {
  /**
   * Generates headers for request signing.
   */
  public static sign(secret: string, req: RequestSigningOptions): RequestSigningHeaders {
    const now = req.timestamp ? req.timestamp : Date.now();
    const payload = [req.method, req.url, now];

    // Check if should sign body as well
    if ((req.body && req.method.toUpperCase() === 'POST') || req.method.toUpperCase() === 'PUT') {
      payload.push(req.body);
    }

    // Generate signature using HMAC SHA 256
    const signature = CryptoJS.HmacSHA256(payload.join(','), secret);

    return {
      'X-Request-Signature': signature.toString(),
      'X-Request-Timestamp': now.toString(),
    };
  }

  /**
   * Verify request signing.
   */
  public static verify(secret: string, req: RequestSigningOptions, signature: string): boolean {
    const payload = [req.method, req.url, req.timestamp];

    // Check if should sign body as well
    if ((req.body && req.method.toUpperCase() === 'POST') || req.method.toUpperCase() === 'PUT') {
      payload.push(req.body);
    }

    // Generate signature using HMAC SHA 256
    const _signature = CryptoJS.HmacSHA256(payload.join(','), secret);

    return _signature.toString() === signature;

  }
}
package app.btcore.java;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

public class RequestSigning {
    private static final char[] digits = "0123456789ABCDEF".toCharArray();

    public String sign(String secret, String method, String url) throws InvalidKeyException {
        return this.sign(secret, method, url, null, System.currentTimeMillis());
    }

    public String sign(String secret, String method, String url, String body) throws InvalidKeyException {
        return this.sign(secret, method, url, body, System.currentTimeMillis());
    }

    public String sign(String secret, String method, String url, String body, long timestamp) throws InvalidKeyException {
        List<String> segments = new ArrayList<>();

        segments.add(method);
        segments.add(url);
        segments.add(String.valueOf(timestamp));

        if (body != null && body.length() > 0) {
            segments.add(body);
        }

        return this.raw(secret, String.join(",", segments));
    }

    public String raw(String key, String content) throws InvalidKeyException {
        String algorithm = "HmacSHA256";

        try {
            return this.generateSignature(content, key, algorithm);
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException exception) {
            // Algorithm or encoding is not available, maybe we should crash
            throw new RuntimeException(exception);
        }
    }

    private static String bytesToHex(final byte[] bytes) {
        final StringBuilder buf = new StringBuilder();
        for (byte aByte : bytes) {
            buf.append(RequestSigning.digits[(aByte >> 4) & 0x0f]);
            buf.append(RequestSigning.digits[aByte & 0x0f]);
        }
        return buf.toString();
    }

    protected String generateSignature(String content, String key, String algorithm)
        throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        // 1. Get an algorithm instance.
        Mac sha256_hmac = Mac.getInstance(algorithm);

        // 2. Create secret key.
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);

        // 3. Assign secret key algorithm.
        sha256_hmac.init(secret_key);

        // You can use any other encoding format to get hash text in that encoding.
        return RequestSigning.bytesToHex(sha256_hmac.doFinal(content.getBytes(StandardCharsets.UTF_8))).toLowerCase();
    }
}
using System;
using System.Collections.Generic;
using System.Text;


namespace BitCapitalSample
{
    public static class RequestUtil
    {
        public static string Sign(string method, string path, long timestamp, string body, string secret)
        {
            var payload = new List<String> { method, path, timestamp.ToString(), body };
            return Sign(String.Join(",", payload), secret);
        }

        public static string Sign(string message, string secret)
        {
            ASCIIEncoding encoding = new ASCIIEncoding();
            byte[] keyBytes = encoding.GetBytes(secret);
            byte[] messageBytes = encoding.GetBytes(message);
            System.Security.Cryptography.HMACSHA256 cryptographer = new System.Security.Cryptography.HMACSHA256(keyBytes);
            byte[] bytes = cryptographer.ComputeHash(messageBytes);
            return BitConverter.ToString(bytes).Replace("-", "").ToLower();
        }
    }
}
import hashlib
import hmac
​
class RequestUtil:
    """ Generates the request signature based on request params"""
	@staticmethod
    def sign(self, method, path, timestamp, body, secret):
      payload = [method, path, timestamp]
      # Add body signing if supplied
      if body is not None: payload.append([body])
      
      signature = hmac.new(secret, ",".join(payload), digestmod=hashlib.sha256)
      return signature

For more language examples, you can check the samples repository at Bit Capital's GitHub.