I am playing around implementing an API. Usually that is really simple, but this one gives me problems. However, I am pretty sure the problem is me, and not the API.
Url to this specific API:
https://www.bitstamp.net/api/
I want to make POST call to the "Account balance". Currently I get the following answer:
{"error": "Missing key, signature and nonce parameters"}
and I try to do it with the following code:
var path = "https://www.bitstamp.net/api/user_transactions";
var nonce = GetNonce();
var signature = GetSignature(nonce);
using (WebClient client = new WebClient())
{
byte[] response = client.UploadValues(path, new NameValueCollection()
{
{ "key", Constants.ThirdParty.BitStamp.ApiKey },
{ "signature", signature },
{ "nonce", nonce},
});
var str = System.Text.Encoding.Default.GetString(response);
}
return 0.0m;
This is my two helper functions:
private string GetSignature(int nonce)
{
string msg = string.Format("{0}{1}{2}", nonce,
Constants.ThirdParty.BitStamp.ClientId,
Constants.ThirdParty.BitStamp.ApiKey);
return HelperFunctions.sign(Constants.ThirdParty.BitStamp.ApiSecret, msg);
}
public static int GetNonce()
{
return (int) (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
}
My crypto sign function is this one:
public static String sign(String key, String stringToSign)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
HMACSHA256 hmacsha256 = new HMACSHA256(keyByte);
return Convert.ToBase64String(hmacsha256.ComputeHash(encoding.GetBytes(stringToSign)));
}
Any idea why i get the "missing key" error? Is there something obvious I am doing wrong (probably is :) )?
Edit:
Fiddler tells me I post the following data:
key=mykeymykey&signature=PwhdkSek6GP%2br%2bdd%2bS5aU1MryXgrfOD4fLH05D7%2fRLQ%3d&nonce=1382299103%2c21055
Edit #2:
Updated code on generating the signature:
private string GetSignature(int nonce)
{
string msg = string.Format("{0}{1}{2}", nonce,
Constants.ThirdParty.BitStamp.ClientId,
Constants.ThirdParty.BitStamp.ApiKey);
return HelperFunctions.ByteArrayToString(HelperFunctions.SignHMACSHA256(
Constants.ThirdParty.BitStamp.ApiSecret, msg)).ToUpper();
}
public static byte[] SignHMACSHA256(String key, byte[] data)
{
HMACSHA256 hashMaker = new HMACSHA256(Encoding.ASCII.GetBytes(key));
return hashMaker.ComputeHash(data);
}
public static byte[] StrinToByteArray(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
I think that the problem is that you are using base64 (Convert.ToBase64String), but in the relevant section of the API docs, it is written:
Signature is a HMAC-SHA256 encoded message containing: nonce, client ID and API key. The
HMAC-SHA256 code must be generated using a secret key that was generated with your API key.
This code must be converted to it's hexadecimal representation (64 uppercase characters).
So you have to convert the byte array to a hexadecimal string. See this question to get some examples of how to do it.
Related
I have a javascript backend that use CryptoJS to generate a hash, I need to generate the same hash on C# Client but can't reproduce the same result than javascript.
The backend code are this:
function generateHash (str, cypherkey) {
return CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(str, CryptoJS.enc.Base64.parse(cypherkey)))
}
console.log(generateHash("testString", "UTI5dVozSmhkSE1zSUhsdmRTZDJaU0JtYjNWdVpDQnBkQ0VnUVhKbElIbHZkU0J5WldGa2VTQjBieUJxYjJsdUlIVnpQeUJxYjJKelFIZGhiR3hoY0c5d0xtTnZiUT09"))
And print: "FwdJUHxt/xSeNxHQFiOhmPDRh73NFfuWK7LG6ssN9k4="
Then when I try to do the same on my C# client with this code:
public static string generateHash(string str, string cypherkey)
{
var keyenc = new System.Text.ASCIIEncoding();
byte[] keyBytes = keyenc.GetBytes(cypherkey);
var key = BitConverter.ToString(keyBytes);
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
byte[] messageBytes = encoding.GetBytes(str);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
Print other result: "SiEjJASvYWfO5y+EiSJAqamMcUyBSTDl5Sy1zXl1J/k="
The problem are on the process to convert to Base64 the cypherkey, probably it's wrong.
Anyone know how can solve this?
Greetings and a lot of thanks ^^
I haven't seen the source of CryptoJs so there are assumptions here (from method names, encoding, etc):
public static string generateHash(string str, string cypherkey)
{
// based on CryptoJS.enc.Base64.parse
byte[] keyBytes = System.Convert.FromBase64String(cypherkey);
using (var hmacsha256 = new HMACSHA256(keyBytes))
{
byte[] hashmessage = hmacsha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str));
return Convert.ToBase64String(hashmessage);
}
}
Result:
FwdJUHxt/xSeNxHQFiOhmPDRh73NFfuWK7LG6ssN9k4=
Hth
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.
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.
I need to create a hash-signature in c#.
The pseudo-code example that i need to implement in my c# code:
Signatur(Request) = new String(encodeBase64URLCompatible(HMAC-SHA-256(getBytes(Z, "UTF-8"), decodeBase64URLCompatible(getBytes(S, "UTF-8")))), "UTF-8")
Z: apiSecret
S: stringToSign
The coding for expectedSignatur and apiSecret is Base64 URL Encoding [RFC 4648 Section 5]
My problem is that I always get the wrong result.
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
return Encoding.UTF8.GetString(base64EncodedBytes);
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(plainTextBytes);
}
private static byte[] HmacSha256(string data, string key)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
}
static void Main(string[] args)
{
var apiSecret = "JrXRHCnUegQJAYSJ5J6OvEuOUOpy2q2-MHPoH_IECRY=";
var stringToSign = "f3fea5f3-60af-496f-ac3e-dbb10924e87a:20160201094942:e81d298b-60dd-4f46-9ec9-1dbc72f5b5df:Qg5f0Q3ly1Cwh5M9zcw57jwHI_HPoKbjdHLurXGpPg0yazdC6OWPpwnYi22bnB6S";
var expectedSignatur = "ps9MooGiTeTXIkPkUWbHG4rlF3wuTJuZ9qcMe-Y41xE=";
apiSecret = apiSecret.Replace('-', '+').Replace('_', '/').PadRight(apiSecret.Length + (4 - apiSecret.Length % 4) % 4, '=');
var secretBase64Decoded = Base64Decode(apiSecret);
var hmac = Convert.ToBase64String(HmacSha256(secretBase64Decoded, stringToSign));
var signatur = hmac.Replace('+', '-').Replace('/', '_');
Console.WriteLine($"signatur: {signatur}");
Console.WriteLine($"expected: {expectedSignatur}");
Console.WriteLine(signatur.Equals(expectedSignatur));
Console.ReadLine();
}
You're assuming that your key was originally text encoded with UTF-8 - but it looks like it wasn't. You should keep logically binary data as binary data - you don't need your Base64Encode and Base64Decode methods at all. Instead, your HmacSha256 method should take a byte[] as a key, and you can just use Convert.FromBase64String to get at those bytes from the base64-encoded secret:
using System;
using System.Text;
using System.Security.Cryptography;
class Test
{
private static byte[] HmacSha256(byte[] key, string data)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
}
}
static void Main(string[] args)
{
var apiSecret = "JrXRHCnUegQJAYSJ5J6OvEuOUOpy2q2-MHPoH_IECRY=";
var stringToSign = "f3fea5f3-60af-496f-ac3e-dbb10924e87a:20160201094942:e81d298b-60dd-4f46-9ec9-1dbc72f5b5df:Qg5f0Q3ly1Cwh5M9zcw57jwHI_HPoKbjdHLurXGpPg0yazdC6OWPpwnYi22bnB6S";
var expectedSignatur = "ps9MooGiTeTXIkPkUWbHG4rlF3wuTJuZ9qcMe-Y41xE=";
apiSecret = apiSecret.Replace('-', '+').Replace('_', '/').PadRight(apiSecret.Length + (4 - apiSecret.Length % 4) % 4, '=');
var secretBase64Decoded = Convert.FromBase64String(apiSecret);
var hmac = Convert.ToBase64String(HmacSha256(secretBase64Decoded, stringToSign));
var signatur = hmac.Replace('+', '-').Replace('/', '_');
Console.WriteLine($"signatur: {signatur}");
Console.WriteLine($"expected: {expectedSignatur}");
Console.WriteLine(signatur.Equals(expectedSignatur));
}
}
Personally I'd change your HmacSha256 method to:
private static byte[] ComputeHmacSha256Hash(byte[] key, byte[] data)
{
using (var hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(data);
}
}
so that it's more general purpose, maybe adding another method to compute the hash after encoding as UTF-8 for convenience. That way you can sign any data, not just strings.
Hi I am trying to stuff a digital signature inside the Authorization header of a POST request.
I need to use powershell by requirement, but the actual digital signature is done in a c# class which I call from inside of my powershell.
$obj = new-object DigitalSignatureApplication.DigitalSignature
$signature = $obj.GetSignature($certificateName,$Message)
Same with the authentication part
$verification = $obj.verifySignature($signature64,$certificateName,$Message)
The idea is to Base 64 encode the signature in powershell and send it across in the POST request and then decode it back and authenticate the signature.
$signatureBytes = [System.Text.Encoding]::UTF8.GetBytes($signature)
$signature64 = [System.Convert]::ToBase64String($signatureBytes)
$headers = #{Authorization = "DSignature "+$signature64}
The problem is that between encoding it to base64 and decoding it back, something is messing up the signature and the authentication fails, I have tried encoding and decoding purely in c# too but even that dose not work.
The following is my signing logic in C#
byte[] buffer = Encoding.Default.GetBytes(UnsignedMessage);
byte[] signature = privateKey.SignData(buffer, new SHA1Managed());
return EncodeTo64(GetString(signature)); // converting to base 64 before returning
// return GetString(signature); // just converting the byte [] to a string
This is my verification/ authentication logic in C#
byte[] buffer = Encoding.Default.GetBytes(message);
string decodedSigneture = DecodeFrom64(encodedsignature);
byte[] signaturebytes = GetBytes(decodedSigneture);
bool verify = publicKey.VerifyData(buffer, new SHA1Managed(), signaturebytes);
The following is my logic for Get string and get bytes.
static byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
static string GetString(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
The following is the two different ways of encoding and decoding to base 64 processes I have tried ....
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
static public string EncodeTo64(string toEncode)
{
byte[] toEncodeAsBytes
= System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
string returnValue
= System.Convert.ToBase64String(toEncodeAsBytes);
return returnValue;
}
static public string DecodeFrom64(string encodedData)
{
byte[] encodedDataAsBytes
= System.Convert.FromBase64String(encodedData);
string returnValue =
System.Text.ASCIIEncoding.ASCII.GetString(encodedDataAsBytes);
return returnValue;
}
Thanks in advance,
I am new to cryptography however I need to use HMACSHA256 to has a message.
I wrote a test method to test my hash method. I used online generators to hash the word: Paul and they all have the same base64 value, however when I do it using the code below, I get a different value. I tried using different encodings but I can't get back the value, can you let me know where I maybe going wrong?
[TestClass]
public class HashGeneratorUnitTest
{
[TestMethod]
public void TestMethod1()
{
string message = "Paul";
//Pass a string to method.
string hashedMessage = ShaGenerator.GetHash(message);
Assert.AreEqual("gYtcxfIdPm5OYHHAYpRSjURZUCIhhEbYt5ME0rdmMno=",
hashedMessage);
}
}
public static class ShaGenerator
{
public static string GetHash(string message, string secret = "")
{
var enc = new System.Text.ASCIIEncoding();
byte[] secretBytes = enc.GetBytes(secret);
byte[] messageBytes = enc.GetBytes(message);
using (var hmac = new HMACSHA256(secretBytes))
{
byte[] hashedBytes = hmac.ComputeHash(messageBytes);
string hashedString = Convert.ToBase64String(hashedBytes);
//Return HMACSHA256 string.
return hashedString; //returns: "g9gc9FI2RcI3N9ApYePF+si9Uh0p0Q4u2Vm0Wy5qphk="
}
}
}
The web-based generator you are testing against is incorrect.
Your implementation is returning the correct result.
Here is a web-based tool that returns the proper result: https://quickhash.com/