I have been given the following nodejs code the generates a signature. This signature is generated by a third party and I need to validate their signature on our backend API in C#. Their code is:
const crypto = require("crypto");
const secret = "..."; //do not share!
const actualSignature = crypto
.createHmac("sha256", Buffer.from(secret , "hex"))
.update(request.body, "utf-8")
.digest("hex");
I have tried to implement in C# but I am uanble to get my signature to match theirs. Here's my code so far:
public byte[] GetHash(string body, string secret)
{
var secretBytes = Encoding.UTF8.GetBytes(secret);
var hexSharedSecretBytes = ByteToHexadecimalString(secretBytes);
var hexSecretBytes = Encoding.UTF8.GetBytes(hexSharedSecretBytes);
var hmac = new HMACSHA256(hexSecretBytes);
var bodyBytes = Encoding.UTF8.GetBytes(body);
return hmac.ComputeHash(bodyBytes);
}
private string ByteToHexadecimalString(byte[] buff)
{
return buff.Aggregate("", (current, i) => current + i.ToString("x2"));
}
I would greatly appreciate some help.
Related
I am C# developer and I have the below code works well in C# application. I am trying to use the same functionality in the VueJS and Node Js application. I have done with serialization and conversion to fromBase64String, but I am stuck here with generating signature to send it to API. please suggest.
byte[] privateKey = Convert.FromBase64String(File.ReadAllText(appConfig.PrivateKey));
using (CngKey signingKey = CngKey.Import(privateKey, CngKeyBlobFormat.Pkcs8PrivateBlob))
using (RSACng rsaCng = new RSACng(signingKey))
{
request.Data.Signature = Convert.ToBase64String(rsaCng.SignData(Encoding.UTF8.GetBytes(request.Data.Content), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1));
}
var response = (Response)SendRequest(request, 1);
return response;
I have tried the below in Vue and node Js code
var utf8 = (encodeURIComponent(serializedProductInfo));
var dataInfo = [];
for (var i = 0; i < utf8.length; i++) {
dataInfo.push(utf8.charCodeAt(i));
}
let base64Contenet = window.btoa((encodeURIComponent(dataInfo)));
//let base64Contenet = Buffer.toString((encodeURIComponent(dataInfo)));
console.log(base64Contenet);
var privateKey = [];
privateKey = Buffer.from("My PrivateKey", "base64");
var crypto = require('crypto');
var sha1 = crypto.createHash('sha1');
sha1.key = privateKey;
sha1.update(dataInfo);
var hash = sha1.digest();
var signature = crypto.privateEncrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, hash);
console.log(signature);
I am getting the below error
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in v-on handler: "TypeError: Cannot read properties of null (reading '2')"
Dear Community,
fast forward. After an initial request I receive an random string from a server.
The client(ME) needs to generate an Digital-Signature via C# and send it back to the Node.js API (Express;TypeScript).
Then the server validats the signature and returns true, if the signature is successfully verified or false if not.
To sign the string I use C# default RSA object and SHA256 for Hashing.
The Server knows my public key and user identifier to make sure the digital-signature belongs to me.
Use-Case Tl;Dr:
Client(C#):
Receive a string from the server. Generate an digital-signature(RSA;SHA256) of this string and send it back via API-Request towards the Server.
Server(NodeJS;Express;TypeScript):
Receive the request with the digital-signature of the string inside and validats it.
Send an answer with true if the signature is successfully verified or false if not.
**The Problem:**
The result turns nerver out as true.
Generating an Signature of the string and Verify it localy, both systems (client and server) will return true.
Means if the Client generate a signature of the string and verifys it the process results localy in true.
If the server uses the same Key-Pair as the client and generate a signature of the same string the process will result in true.
BUT using the same Key-Pair both systems generate different signatures for the same string!
If I use the digital-signature of the string generated by the server and send it with the client, the server returns TRUE.
Code is a bit different. Names has changed.
Client (C#)
Request Methode
<!-- language: c# -->
private async Task<string> APIRequestSignature(RSA my_rsa, string randomString)
{
byte[] randomBytes = Encoding.UTF8.GetBytes(randomString);
byte[] signature = Crypto.GetDigitalSignatur256(randomBytes, my_rsa);
MeRequest meRequest = new(identifier, Convert.ToBase64String(signature));
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = APIUri.ValidME,
Content = new StringContent(meRequest.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json),
};
var response = await client.SendAsync(request).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Console.WriteLine(responseBody);
}
internal class MeRequest: Request
{
private string sign;
public MeRequest(string identifier, string sign) : base(identifier, "")
{
this.sign = sign;
}
public string signature
{
get
{
return this.sign;
}
}
}
Crypto Class:
<!-- language: c# -->
public static byte[] GetDigitalSignatur256(byte[] str, RSA rsa)
{
byte[] hash = Hash256(str);
RSAPKCS1SignatureFormatter rsaFormatter = new(rsa);
rsaFormatter.SetHashAlgorithm("SHA256");
return rsaFormatter.CreateSignature(hash);
}
public static byte[] Hash256(byte[] arr) =>SHA256.Create().ComputeHash(arr);
Messi JavaScript Server Code: (Node.JS;Express;TypeScript)
<!-- language: lang-js -->
import \{Encryption\} from "../class/Encryption";
var randomString = "TESTTESTTESTTEST";
<!-- language: lang-js -->
//If User Exists need Ident and PKey /Get an random encrypted string
UserRoutes.get('/UserExist', function(req, res) {
//Create Hash from randomString
var hash = crypto.createHash("SHA256");
hash.update(randomString, "base64");
var randomStringHash = hash.digest("base64");
//Read Private Key from Client for Test Signature
var clientPrivateKey = fs.readFileSync(config.server.clientKeyPath + "identlarspk.txt", 'utf8');
//Create Sign with Hash from RandomString
var sign = crypto.createSign("SHA256");
sign.update(randomStringHash, "base64");
sign.end();
var signature = sign.sign({
key: clientPrivateKey,
padding: crypto.constants.RSA_PKCS1_PADDING
});
console.log("Signature Created By Server: " + signature);
res.json({
randomString: randomString
})
});
<!-- language: lang-js -->
//Validate User by Ident and Manipulated random encrypted String By Signature
UserRoutes.get('/UserSigning', async function(req, res) {
var encryption = new Encryption();
//Formating Public key
var userPKeyDB = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqdKBCUssRLefK9EzzRKxm+ftQ26PLmI5utmmGY6LgwEnKuIrJw/cWA5Fn+2ebJNAgdH9uFBAh8CEtHHjnfMB0SWCl6Qv8R62x8wJs8xNmoTIVLENe5lvHGi2FLVmzLg1LInfwqfsLBLmCM6re5WtQPX4BiXjfDaeYxYCLg3plfaQyYe9/PCzpswC4U4R3yMKzW3ptj4D+L0BGwp4EKFkh2qGJ1Bnm2PkDwott7CT8a8ef36vS7x1fb0XUuw4b5c87ilQZ+mG85aWDK45R7Tl2ZMebZsAe/kpLIRDsG3nnxdzpBelePFCGWSdCCwvFjygOcLyKXQtqJyp3tHvENGTjQIDAQAB";
var formatedEncryptionKey = await encryption.FormatePKey(userPKeyDB);
//Create Hash from RandomString
var hash = crypto.createHash("SHA256");
hash.update(randomString, "base64");
var randomStringHash = hash.digest("base64");
var bufferRandomStringHash = Buffer.from(randomStringHash, "base64");
var bufferClientSignature = Buffer.from(req.body.signature, "base64");
var verify = crypto.createVerify("SHA256");
verify.update(randomStringHash);
verify.end();
var isValidated = verify.verify(formatedEncryptionKey, bufferClientSignature);
res.json({
token: isValidated
})
});
<!-- language: lang-js -->
//Create start and end points for the Public Key in Encryption Class
public async FormatePKey(pKey: string): Promise<string> {
var bufferStartKey = Buffer.from("-----BEGIN PUBLIC KEY-----\n", "utf8");
var bufferStartKeyUTF8 = bufferStartKey.toString("utf8");
var bufferPubKey = Buffer.from(pKey, "base64");
var pubKeyBase64 = bufferPubKey.toString("base64");
var bufferEndKey = Buffer.from("\n-----END PUBLIC KEY-----", "utf8");
var bufferEndKeyUTF8 = bufferEndKey.toString("utf8");
var formatedPublicKey = bufferStartKeyUTF8 + pubKeyBase64 + bufferEndKeyUTF8;
var bufferPubicKey = Buffer.from(formatedPublicKey, "utf8");
var bufferPublicKeyUTF8 = bufferPubicKey.toString("utf8");
return bufferPublicKeyUTF8;
}
I am trying to authenticate to the Nominex API, I cannot find any C# examples of this request to Nominex. Documentation only has node.js example. I'm trying to convert it to C# but not having much luck, not sure what I'm doing wrong. The result I get is invalid credentials
Example in JavaScript
const crypto = require('crypto')
const request = require('request')
const apiKey = '...'
const apiSecret = '...'
const apiPath = '/api/rest/v1/private/wallets'
const nonce = Date.now().toString()
const queryParams = ''
const body = undefined
let signature = `/api${apiPath}${nonce}${body ? JSON.stringify(body) : ''}`
const sig = crypto.createHmac('sha384', apiSecret).update(signature)
const shex = sig.digest('hex')
const options = {
url: `https://nominex.io/${apiPath}?${queryParams}`,
headers: {
'nominex-nonce': nonce,
'nominex-apikey': apiKey,
'nominex-signature': shex
},
body: body,
json: true
}
request.get(options, (error, response, body) => {
console.log(body);
})
My attempt in C#
private async Task<string> GetRequest()
{
HttpClient client = new HttpClient();
string secretKey = apiSecret;
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
long nonce = (long)t.TotalMilliseconds;
string body = "";
string requestPath = "/api/rest/v1/private/orders/BTC/USDT";
string prehashString = string.Join("", requestPath, nonce.ToString(), body);
string digest = "";
using (var hmacsha384 = new HMACSHA384(Encoding.Default.GetBytes(secretKey)))
{
byte[] hashmessage = hmacsha384.ComputeHash(Encoding.Default.GetBytes(prehashString));
bool upperCase = false;
StringBuilder result = new StringBuilder(hashmessage.Length * 2);
for (int i = 0; i < hashmessage.Length; i++)
result.Append(hashmessage[i].ToString(upperCase ? "X2" : "x2"));
digest = result.ToString();
}
HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}{requestPath}");
msg.Content = new StringContent(body, Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Add("nominex-apikey", apiKey);
client.DefaultRequestHeaders.Add("nominex-nonce",nonce.ToString());
client.DefaultRequestHeaders.Add("nominex-signature", digest);
var response = await client.SendAsync(msg);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
The signature is:
let signature = `/api${apiPath}${nonce}${body ? JSON.stringify(body) : ''}`
so they add a /api before the apiPath that in the nodejs example is /api/rest/v1/private/wallets, so in that case the signature would be (with a nonce of 1609874861073):
/api/api/rest/v1/private/wallets1609874861073
so
string prehashString = string.Concat("/api", requestPath, nonce.ToString(), body);
And replace all the Encoding.Default to Encoding.UTF8. They shouldn't give you any problem, but something wrong is something wrong.
Next time, if you have a nodejs installation on your pc, you can try their code on your nodejs, adding freely some console.log, otherwise there is a site, repl.it, where you can try some node.js code (that is what I did to solve your problem).
Ι try to create a valid jwt token
From settings i create an RSA keypairs and i get the private key without the "-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----
"
var rsaPrivateKey = #"MIIEogIBAAKCAQEAoGujdXbYVy68a4CSWz963SpYxVs20/..............HQ/jW8pFom6gJreCDkca5axYo/gXp3W3rQHFTkooTNbOk2MyFMZUqRD3aCG1wuUW3w8TgGX4slrLDV0pP4=";
var jwt = Sign(rsaPrivateKey);
I follow the instructions here https://developers.docusign.com/docs/admin-api/admin101/application-auth/ and after a lot of hours i create this method
public string Sign(string privateKey)
{
List<string> segments = new List<string>();
var header = new { alg = "RS256", typ = "JWT" };
//For production environments, use account.docusign.com
var payload = new
{
iss = "4f489d61-dc8b------a828-3992e670dcbc",
iat = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds,
aud = "account-d.docusign.com",
scope = "signature impersonation"
};
byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));
string stringToSign = string.Join(".", segments.ToArray());
byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
byte[] keyBytes = Convert.FromBase64String(privateKey);
var privKeyObj = Asn1Object.FromByteArray(keyBytes);
var privStruct = RsaPrivateKeyStructure.GetInstance((Asn1Sequence)privKeyObj);
ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
sig.Init(true, new RsaKeyParameters(true, privStruct.Modulus, privStruct.PrivateExponent));
sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length);
byte[] signature = sig.GenerateSignature();
segments.Add(Base64UrlEncode(signature));
return string.Join(".", segments.ToArray());
}
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
When i check the JWT validation in this tool https://jwt.io/#debugger-io, i get invalid signature error.
How can i fix the token ?? I cant proceed with Step 2 Obtain the access token...
I'm sorry you having problems with JWT. I would recommend you use the DocuSign C# SDK instead of trying to write your own code.
Then you can find the example of how to use JWT here - https://github.com/docusign/code-examples-csharp.
The specific code relevant to JWT is here - https://github.com/docusign/code-examples-csharp/blob/38c2eb46948a3cbf55edcce758f88d775f80cae9/launcher-csharp/Common/RequestItemService.cs under the UpdateUserFromJWT() method.
Common problems with JWT:
Not obtaining consent.
Using public token instead of private.
Using malform token. Token must be exactly, including new-lines, as provided.
Not using correct UserId (GUID) in the request.
Not requesting "impersonation" scope in consent (#1 above).
I am trying to write an interface to our Phabricator install to allow out internal improvement system to create tasks. However, I cannot figure out why I keep getting a certificate error.
"{\"result\":null,\"error_code\":\"ERR-INVALID-CERTIFICATE\",\"error_info\":\"Your authentication certificate for this server is invalid.\"}"
The following is my code;
private void CreateSession()
{
int token = (int)((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds);
var result = this.Do(
"conduit.connect",
new
{
client = this.ClientName,
clientVersion = this.ClientVersion,
clientDescription = "HIS to Fabricator Connector",
user = this.User,
authToken = token,
authSignature = SHA1HashStringForUTF8String(token + this.Certificate)
});
this.m_SessionKey = result.sessionKey;
this.m_ConnectionID = result.connectionID;
}
public static string SHA1HashStringForUTF8String(string s)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
var sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(bytes);
return HexStringFromBytes(hashBytes);
}
public static string HexStringFromBytes(byte[] bytes)
{
var sb = new StringBuilder();
foreach (byte b in bytes)
{
var hex = b.ToString("x2");
sb.Append(hex);
}
return sb.ToString();
}
This returns the following JSON;
"{\"client\":\"HIS\",\"clientVersion\":\"1\",\"clientDescription\":\"HIS to Fabricator Connector\",\"user\":\"KYLIE\",\"authToken\":1449486922,\"authSignature\":\"ec020edbd5082d3971c2c11ef4f4917244fc4a78\"}"
I think the issue is the certificate I am passing. I am using;
api-3ydcae2gtmf6u6uer2zow465j6px
which I obtained from the Conduit API Tokens page.
Any pointers?
You have to get the token via .../conduit/token
Use that token to query .../api/conduit.getcertificate
As result you get the certificate -> profit! :)
PS: it's neither api- nor cli- token to query the certificate! ;)