7

I need to make a connection to an API using a complicated authentication process that I don't understand. I know it involves multiple steps and I have tried to mimic it, but I find the documentation to be very confusing...

The idea is that I make a request to an endpoint which will return a token to me that I need to use to make a websocket connection.

I did get a code sample which is in Python that I don't know the syntax of, but I can use it as a guide to convert it to C#-syntax.

This is the Python code sample:

import time, base64, hashlib, hmac, urllib.request, json

api_nonce = bytes(str(int(time.time()*1000)), "utf-8")
api_request = urllib.request.Request("https://www.website.com/getToken", b"nonce=%s" % api_nonce)
api_request.add_header("API-Key", "API_PUBLIC_KEY")
api_request.add_header("API-Sign", base64.b64encode(hmac.new(base64.b64decode("API_PRIVATE_KEY"), b"/getToken" + hashlib.sha256(api_nonce + b"nonce=%s" % api_nonce).digest(), hashlib.sha512).digest()))

print(json.loads(urllib.request.urlopen(api_request).read())['result']['token'])

So I have tried to convert this into C# and this is the code I got so far:

    static string apiPublicKey = "API_PUBLIC_KEY";
    static string apiPrivateKey = "API_PRIVATE_KEY";
    static string endPoint = "https://www.website.com/getToken";

    private void authenticate()
    {
        using (var client = new HttpClient())
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;


            // CREATE THE URI
            string uri = "/getToken";


            // CREATE THE NONCE
            /// NONCE = unique identifier which must increase in value with each API call
            /// in this case we will be using the epoch time
            DateTime baseTime = new DateTime(1970, 1, 1, 0, 0, 0);
            TimeSpan epoch = CurrentTime - baseTime;
            Int64 nonce = Convert.ToInt64(epoch.TotalMilliseconds);


            // CREATE THE DATA
            string data = string.Format("nonce={0}", nonce);

            // CALCULATE THE SHA256 OF THE NONCE
            string sha256 = SHA256_Hash(data);


            // DECODE THE PRIVATE KEY
            byte[] apiSecret = Convert.FromBase64String(apiPrivateKey);



            // HERE IS THE HMAC CALCULATION

        }
    }

    public static String SHA256_Hash(string value)
    {
        StringBuilder Sb = new StringBuilder();

        using (var hash = SHA256.Create())
        {
            Encoding enc = Encoding.UTF8;
            Byte[] result = hash.ComputeHash(enc.GetBytes(value));

            foreach (Byte b in result)
                Sb.Append(b.ToString("x2"));
        }

        return Sb.ToString();
    }

So the next part is where I'm really struggling. There needs to be some HMAC-calculation that needs to be done but I'm completely lost there.

3
  • can you provide a link to the documentation? Commented Apr 4, 2020 at 21:29
  • Couldn't you just use HMACSHA512 class? Commented Apr 4, 2020 at 23:17
  • You need to debug a static example in python so you can check the progress along the way and see where it deviates in your C# implementation. Use a hardcoded time value that you will use in both the Python example, and in your C# implementation. You should only need to run the Python example once and print out the key variables along the way, then save that for comparison with your C# implementation. Commented Apr 9, 2020 at 2:44

1 Answer 1

8
+50

The main task here is to reverse the API-Sign SHA-512 HMAC calculation. Use DateTimeOffset.Now.ToUnixTimeMilliseconds to get the API nonce, it will return a Unix timestamp milliseconds value. Then it all boils down concating byte arrays and generating the hashes. I'm using a hardcoded api_nonce time just to demonstrate the result; you'll have to uncomment string ApiNonce = DateTimeOffset.Now.ToUnixTimeMilliseconds to get the current Unix timestamp milliseconds each time the API-Sign key is calculated.

Python API-Sign generation:

import time, base64, hashlib, hmac, urllib.request, json

# Hardcoce API_PRIVATE_KEY base 64 value
API_PRIVATE_KEY = base64.encodebytes(b"some_api_key_1234")

# time_use = time.time()
# Hardcode the time so we can confirm the same result to C#
time_use = 1586096626.919

api_nonce = bytes(str(int(time_use*1000)), "utf-8")

print("API nonce: %s" % api_nonce)

api_request = urllib.request.Request("https://www.website.com/getToken", b"nonce=%s" % api_nonce)
api_request.add_header("API-Key", "API_PUBLIC_KEY_1234")

print("API_PRIVATE_KEY: %s" % API_PRIVATE_KEY)

h256Dig = hashlib.sha256(api_nonce + b"nonce=%s" % api_nonce).digest()

api_sign = base64.b64encode(hmac.new(base64.b64decode(API_PRIVATE_KEY), b"/getToken" + h256Dig, hashlib.sha512).digest())

# api_request.add_header("API-Sign", api_sign)
# print(json.loads(urllib.request.urlopen(api_request).read())['result']['token'])

print("API-Sign: %s" % api_sign)

Will output:

API nonce: b'1586096626919'
API_PRIVATE_KEY: b'c29tZV9hcGlfa2V5XzEyMzQ=\n'
API-Sign: b'wOsXlzd3jOP/+Xa3AJbfg/OM8wLvJgHATtXjycf5EA3tclU36hnKAMMIu0yifznGL7yhBCYEwIiEclzWvOgCgg=='

C# API-Sign generation:

static string apiPublicKey = "API_PUBLIC_KEY";
// Hardcoce API_PRIVATE_KEY base 64 value
static string apiPrivateKey = Base64EncodeString("some_api_key_1234");
static string endPoint = "https://www.website.com/getToken";

public static void Main()
{
    Console.WriteLine("API-Sign: '{0}'", GenApiSign());
}

static private string GenApiSign()
{
    // string ApiNonce = DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString();
    // Hardcode the time so we can confirm the same result with Python
    string ApiNonce = "1586096626919";

    Console.WriteLine("API nonce: {0}", ApiNonce);
    Console.WriteLine("API_PRIVATE_KEY: '{0}'", apiPrivateKey);

    byte[] ApiNonceBytes = Encoding.Default.GetBytes(ApiNonce);

    byte[] h256Dig = GenerateSHA256(CombineBytes(ApiNonceBytes, Encoding.Default.GetBytes("nonce="), ApiNonceBytes));
    byte[] h256Token = CombineBytes(Encoding.Default.GetBytes("/getToken"), h256Dig);

    string ApiSign = Base64Encode(GenerateSHA512(Base64Decode(apiPrivateKey), h256Token));

    return ApiSign;
}

// Helper functions ___________________________________________________

public static byte[] CombineBytes(byte[] first, byte[] second)
{
    byte[] ret = new byte[first.Length + second.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    return ret;
}

public static byte[] CombineBytes(byte[] first, byte[] second, byte[] third)
{
    byte[] ret = new byte[first.Length + second.Length + third.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    Buffer.BlockCopy(third, 0, ret, first.Length + second.Length,
                     third.Length);
    return ret;
}


public static byte[] GenerateSHA256(byte[] bytes)
{
    SHA256 sha256 = SHA256Managed.Create();
    return sha256.ComputeHash(bytes);
}

public static byte[] GenerateSHA512(byte[] key, byte[] bytes)
{
    var hash = new HMACSHA512(key);
    var result = hash.ComputeHash(bytes);

    hash.Dispose();

    return result;
}

public static string Base64EncodeString(string plainText)
{
    var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
    return System.Convert.ToBase64String(plainTextBytes);
}

public static string Base64Encode(byte[] bytes)
{
    return System.Convert.ToBase64String(bytes);
}

public static byte[] Base64Decode(string base64EncodedData)
{
    var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
    return base64EncodedBytes;
}

Will output:

API nonce: 1586096626919
API_PRIVATE_KEY: 'c29tZV9hcGlfa2V5XzEyMzQ='
API-Sign: 'wOsXlzd3jOP/+Xa3AJbfg/OM8wLvJgHATtXjycf5EA3tclU36hnKAMMIu0yifznGL7yhBCYEwIiEclzWvOgCgg=='

You can see it working and the result in this .NET Fiddle.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.