i don't know if this question is very easy and I just didn't figure it out how to sign with HashiCorp-VaultĀ“s Api VaultSharp, but I am despairing.
The entire Documentation with examples can be found here: https://github.com/rajanadar/VaultSharp
Encryption and Decryption works fine. Only Signing is a problem.
Code for Encryption:
public byte[] EncryptData(byte[] data, string keyName)
{
SecretsEngine transitSecretsEngine = new SecretsEngine
{
Type = SecretsEngineType.Transit,
Path = path
};
Client.V1.System.MountSecretBackendAsync(transitSecretsEngine).Wait();
Client.V1.Secrets.Transit.CreateEncryptionKeyAsync(keyName, new CreateKeyRequestOptions()
{
Exportable = true
}, path).Wait();
EncryptRequestOptions encryptOptions = new EncryptRequestOptions
{
Base64EncodedPlainText = Convert.ToBase64String(data),
ConvergentEncryption = true,
};
Secret<EncryptionResponse> encryptionResponse = Client.V1.Secrets.Transit.EncryptAsync(keyName,
encryptOptions, path).Result;
string cipherText = encryptionResponse.Data.CipherText;
return Encoding.Unicode.GetBytes(cipherText);
}
Code for Decryption:
public byte[] DecryptData(string ciphertext, string keyName)
{
DecryptRequestOptions decryptOptions = new DecryptRequestOptions
{
CipherText = ciphertext,
};
Secret<DecryptionResponse> decryptionResponse = Client.V1.Secrets.Transit.DecryptAsync(keyName,
decryptOptions, path).Result;
return Convert.FromBase64String(decryptionResponse.Data.Base64EncodedPlainText);
}
Here is my Code Trial for signing:
public byte[] Sign(byte[] plaintextBytes, string keyName)
{
byte[] hash = ComputeHash(plaintextBytes,SHA256.Create());
GCKMS.SignatureOptions options = new GCKMS.SignatureOptions()
{
Digest = Convert.ToBase64String(hash),
};
Secret<GCKMS.SignatureResponse> result = Client.V1.Secrets.GoogleCloudKMS.SignAsync(keyName,
options).Result;
return Encoding.Unicode.GetBytes(result.Data.Signature);
}
The Error is:
VaultSharp.Core.VaultApiException: {"errors":["no handler for route
'gcpkms/sign/Manuel'"]}
Last but not least my Code for validating the signature:
public bool ValidateSignature(byte[] plaintextByte, byte[] signature, string keyName)
{
GCKMS.VerificationOptions option = new GCKMS.VerificationOptions
{
Digest = Encoding.Unicode.GetString(ComputeHash(plaintextByte)),
Signature = Encoding.Unicode.GetString(signature)
};
Secret<GCKMS.VerificationResponse> result =
Client.V1.Secrets.GoogleCloudKMS.VerifyAsync(keyName, option).Result;
return result.Data.Valid;
}
I am not sure but this could be because I don't use a SecretsEngine with a Path. I could not find any SecretsEngine for GoogleCloudKms.
Useful information:
I generate the Path with Guid.NewGuid().ToString();.
ComputeHash is a self written Function that computes the Hash with a give Algorithm. The
default algorithm is SHA256.
GCMS is a short version of the Namespace VaultSharp.V1.SecretsEngines.GoogleCloudKMS
Any ideas and suggestions are very welcome.
Thanks in advance!
Although Vault offers convenient signature with Transit, the C# wrapper you are using does not support it.
Google KMS does offer signature, but its interface is more complex: you have to do the hash yourself and keep track of the key versions.
What I suggest is that you play a trick on your API wrapper:
Leave your encryption and decryption code as-is
Write to the the Transit backend as if it was a KV store version 1
Get your signature by sending your payload as the input parameter
You still have to base64 your data before sending it to Vault, to avoid binary encoding issues.
So assuming that:
You want to sign the text StackOverflow
The transit back-end is mounted under transit
Your signature key is named my-key
This should get you started:
var value = new Dictionary<string, object> { "input", Convert.ToBase64String(Encoding.UTF8.GetBytes("StackOverflow")) } };
var writtenValue = await vaultClient.V1.Secrets.KeyValue.V1.WriteSecretAsync("sign/my-key", value, "transit");
Related
I am trying to RSA Encrypt a string with a given key. (I can not change the key, since I request it from another system)
I get the key as a string and it looks like this:
-----BEGIN CERTIFICATE-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSehIDcXho52VvAQAFfVD2CzOFAYKSfnRsuKE/nqE1O4l/X6opYgjxj/mSNk1bvNobwiRBL4uWfFnsHoQSiv1Gqzl+JQ2QQ2rUVGuNZ7d7agjYcb1LCpKPE1Q0kqLvbGyDWQx8ULC7/FJ49mMwbzIE4C9ovfdOBa0er6IDNSW0IQIDAQAB-----END
CERTIFICATE-----
Now I tried to encrypt the string on many ways, but none of them worked.
The last solution I tried was the following:
public static string RSA(string payload, string publicKey)
{
byte[] toEncryptData = Encoding.ASCII.GetBytes(payload);
RSACryptoServiceProvider rsaPublic = new RSACryptoServiceProvider();
rsaPublic.FromXmlString(publicKey);
byte[] encryptedRSA = rsaPublic.Encrypt(toEncryptData, false);
string EncryptedResult = Encoding.Default.GetString(encryptedRSA);
return EncryptedResult;
}
It always throws an Exception when it comes to:
rsaPublic.FromXmlString(publicKey);
So what can I do to encrypt the string with RSA?
I need it because I want to implement an API: https://www.loxone.com/dede/wp-content/uploads/sites/2/2016/08/0903_Communicating-with-the-Miniserver.pdf
On Page 5 under 6. you can see the requirement!!
public static string ConvertToXmlPublicJavaKey(string publicJavaKey)
{
RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicJavaKey));
string xmlpublicKey = string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",
Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
return xmlpublicKey;
}
The PublicKeyFactory class is from BouncyCastle.Crypto, you can Google it for more information.
Then your rsaPublic.FromXmlString(publicKey) will work fine.
I'm trying to hit the Coinspot REST API, but I'm getting an error returned. I'm having no trouble talking to Bittrex and Independent Reserve, but Coinspot is a bit different. This is my code:
protected override RESTClient RESTClient { get; } = new RESTClient(new NewtonsoftSerializationAdapter(), new Uri("https://www.coinspot.com.au/api"));
public class postdata
{
public string nonce { get; set; }
}
public string CalculateMD5Hash(string input)
{
//step 1, calculate MD5 hash from input
MD5 md5 = MD5.Create();
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = md5.ComputeHash(inputBytes);
// step 2, convert byte array to hex string
var sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
/// <summary>
/// Private IR Call: GetAccounts
/// </summary>
/// <returns></returns>
private async Task<List<AccountHolding>> Balances()
{
//https://github.com/geekpete/py-coinspot-api/blob/master/coinspot/coinspot.py
//var nonce = new Date().getTime();
//var postdata = postdata || { };
//postdata.nonce = nonce;
//var stringmessage = JSON.stringify(postdata);
//var signedMessage = new hmac("sha512", self.secret);
//signedMessage.update(stringmessage);
// 'sign': sign,
//'key': self.key
var nonce = APIHelpers.GetNonce();
var postdata = new postdata { nonce = nonce };
var json = JsonConvert.SerializeObject(postdata);
System.Diagnostics.Debug.WriteLine(json);
var sign = APIHelpers.GetHMACSHAHash(ApiSecret, json, APIHelpers.HMACSHAType.NineBit);
//Do we do this?
//The JavaScript samples seem to hash with MD5 afterwards for double encryption?
sign = CalculateMD5Hash(sign);
RESTClient.Headers.Clear();
RESTClient.Headers.Add("sign", sign);
RESTClient.Headers.Add("key", ApiKey);
try
{
var retVal = await RESTClient.PostAsync<string, postdata>(postdata, "/my/balances");
System.Diagnostics.Debug.WriteLine(retVal);
}
catch (Exception ex)
{
}
throw new NotImplementedException();
}
The doco is very scant! I'm stuck.
https://www.coinspot.com.au/api
I don't have the error handy right now, but it was a completely non-descript error with information about what went wrong. It was something like "invalid call". But, I know that it is accepted my posted data to some extent, because if I change the name of the property "nonce" to "noncey", I get a meaningful error back that says "no nonce".
Did you ever manage to get this API working. CoinSpot are not very supportive of this. I can only get 3 of the coins API working which isn't much help
I managed to get it working recently and put together a simple SDK in .NET
https://github.com/QuintinHumphreys/CoinspotAPI
tl:dr It's undocumented but you need to use port 443, I found it by digging through their node SDK.
I was having the same issue, getting the very non-descriptive {status: invalid} response, in my case using Elixir not C#. I got it to work by peeking into their node SDK - my details worked using their SDK so I knew it had to be something I wasn't doing properly (although their documentation is pretty shocking). They use port 443 and as soon as I set that it worked.
I tried 2 things, I'm 90% sure it was the port number but half way through my getting it to work I printed the sha512 sign created by their node sdk and compared it to the one I generating using Cryptex I saw that they were generating the same sha512 signature, but my one was in capital letters while the node one was in lowercase - this may or may not end up mattering but I did use String.downcase() on mine in the end.
I'm writing custom forms authentication for ASP.NET MVC 5 (no, I don't want to use ASP.NET Identity). I'm trying to hash my passwords using a randomly-generated salt and then hashing salt+password using SHA512. Here are the methods I've written:
private static User SetPassword(User newUser, string password)
{
var rand = RNGCryptoServiceProvider.Create();
var saltBytes = new byte[128];
var passwordBytes = Encoding.UTF8.GetBytes(password);
rand.GetNonZeroBytes(saltBytes);
var passHash = SHA512Managed.Create().ComputeHash(saltBytes.Concat(passwordBytes).ToArray());
var hash = Encoding.UTF8.GetString(passHash);
var salt = Encoding.UTF8.GetString(saltBytes);
newUser.PasswordHash = hash;
newUser.Salt = salt;
return newUser;
}
private static bool ValidatePassword(User user, string passwordTry)
{
var actualPasswordBytes = Encoding.UTF8.GetBytes(user.PasswordHash);
var passwordTryBytes = Encoding.UTF8.GetBytes(passwordTry);
var saltBytes = Encoding.UTF8.GetBytes(user.Salt);
var passwordTryHashBytes = SHA512Managed.Create().ComputeHash(saltBytes.Concat(passwordTryBytes).ToArray());
if (passwordTryHashBytes == actualPasswordBytes)
{
return true;
}
return false;
}
If I step through the code, the registration (SetPassword()) method appears to work successfully. The user's record gets set with a UTF-8 encoded password hash and salt.
If I step through the password validation method, everything also appears to be operating normally. The user's record (the salt and hash) are checked against the password try.
The problem is, when I register with a password and then try to log in as that user, the password validation fails. I'm probably not understanding how one of the Cryptography classes works... can anyone explain why this doesn't work?
The following code will always fail because both objects do not point to the same reference:
if (passwordTryHashBytes == actualPasswordBytes)
Try using LINQ's SequenceEqual() method
passwordTryHashBytes.SequenceEqual(actualPasswordBytes)
I'm using Facebook as a login provider for my web application (ASP.NET MVC).
My login works similar to another StackOverflow post How to securely authorize a user via Facebook's Javascript SDK. I also share the user's concerns.
The flow for my login is as Follows:
1. The user presses the login button.
2. The user must accept the app.
3. A javascript callback retrieves the response.
var authResponse = response.authResponse;
Object returned:
{
accessToken: "...",
expiresIn: 1234,
signedRequest: "...",
userID: "123456789"
}
I've heard that I can used the signed_request to validate the user's request, but all the examples online are for PHP. How do I do this in .NET?
To compile Rowan's answer into its final code:
public static string DecodeSignedRequest(string signed_request)
{
try
{
if (signed_request.Contains("."))
{
string[] split = signed_request.Split('.');
string signatureRaw = FixBase64String(split[0]);
string dataRaw = FixBase64String(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
string data = Encoding.UTF8.GetString(dataBuffer);
byte[] appSecretBytes = Encoding.UTF8.GetBytes(app_secret);
System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
if (expectedHash.SequenceEqual(signature))
{
return data;
}
}
}
catch
{
// error
}
return "";
}
private static string FixBase64String(string str)
{
while (str.Length % 4 != 0)
{
str = str.PadRight(str.Length + 1, '=');
}
return str.Replace("-", "+").Replace("_", "/");
}
Thanks Rowan!
Yes, the signed_request can be used to verify that an incoming login request is genuine. If you're logging in a user with Javascript (via AJAX, for example) you can use the signed_request to ensure that the data isn't false.
According to Parsing the Signed Request, there are 3 major steps, however I'll be a little more specific.
Take the signed_request string and split it into two strings. There is a period character (full stop) which is a delimiter.
The first part of the string (the signature) is a hash of the second part.
The second part contains some information about the user and the request (user ID, timestamp).
The strings are in Base64, but cannot be decoded straight away.
They are Base64-URL-encoded which means that + and / characters have been replaced with URL-friendly - and _ characters. Replace - characters with + and _ characters with /.
The strings may not be fully Base64 padded. Base64 strings should be divisible by 4; pad the strings out as necessary.
Hash the signature using HMAC (SHA256) using your app secret as the key and compare the result to the signature that was provided.
Use the .NET class HMACSHA256.
1. Split and decode
Code
string response = ""; // the signed_request
string[] split = response.Split('.');
string signatureRaw = FixBase64String(split[0]);
string dataRaw = FixBase64String(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
string data = Encoding.UTF8.GetString(dataBuffer);
FixBase64String()
static string FixBase64String(string str)
{
string result = str;
while (result.Length % 4 != 0)
{
result = result.PadRight(result.Length + 1, '=');
}
result = result.Replace("-", "+").Replace("_", "/");
return result;
}
2. Compare the hashes
byte[] appSecretBytes = Encoding.UTF8.GetBytes("my_app_secret_here");
HMAC hmac = new HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(dataRaw));
bool areEqual = expectedHash.SequenceEqual(signature);
If areEqual is true then you can be sure that the signed request is valid and has not been tampered with (assuming your app secret is secure).
Remember to keep your app secret secure, otherwise malicious users can do bad things.
I have:
x509 certificate (Base64);
String data;
Signature of string data (Base64).
Is it possible to check signature?
My code:
bool valid = false;
var signature = Convert.FromBase64String(base64Signature);
var data = Encoding.UTF8.GetBytes(stringData);
var x509 = new X509Certificate2(Convert.FromBase64String(certificate));
var dsa = x509.PublicKey.Key as DSACryptoServiceProvider;
if (dsa!=null)
valid = dsa.VerifySignature(data, signature);
else {
var rsa = x509.PublicKey.Key as RSACryptoServiceProvider;
if (rsa!=null)
valid = rsa.VerifyHash(data, ???, signature);
}
I don't know what I should use instead of ???. It is possible to get hash algorithm from certificate?
The sender of the original message may use whatever algorithm he likes to sign his message, using the private key that corresponds to the certificate. While you can get the OID of the algorithm used to sign the certificate from its the SignatureAlgorithm property, nothing prevents the sender to use a different signing or hashing algorithm.
According to the documentation, the only valid hashing algorithms for the RSA provider are SHA1 and MD5. Perhaps you should try VerifyHash with both algorithms and check which one succeeds. You can get the proper OID for each one using the CryptoConfig.MapNameToOID method like this:
string sha1Oid = CryptoConfig.MapNameToOID("SHA1");
string md5Oid = CryptoConfig.MapNameToOID("MD5");
bool sha1Valid = rsa.VerifyHash(data, sha1Oid, signature);
bool md5Valid = rsa.VerifyHash(data, md5Oid, signature);
valid = sha1Valid || md5Valid;