I am getting a Cryptographic operation error using the Protection function shown below, but it doesn't indicate what is going wrong.
The error is occurring on MachineKey.Unprotect() when I'm parsing in the bytes.
When the purpose is "username" the parse text value is ??;?W???Qzz<&???Sc??]7\\??\u001d???=?0\u007fN??g&\u001a?p?w?jJ?q?O&~c??!w???;\bv which is converted to byte[64]
I have also tried UTF8 conversion from string/bytes bytes/string with the same result.
What could be causing this?
Test Method:
var username = PasswordStorage.Protection("wheels", "username", true);
var password = PasswordStorage.Protection("legman", "password", true);
var decodeUN = PasswordStorage.Protection(username, "username", false);
var decodePass = PasswordStorage.Protection(password, "password", false);
Protection Method:
public static string Protection(string text, string purpose, bool protect)
{
try
{
if (string.IsNullOrEmpty(text))
return null;
byte[] stream = Encoding.ASCII.GetBytes(text);
var encodedBytes = new Byte[] { };
encodedBytes = protect ? MachineKey.Protect(stream, purpose) : MachineKey.Unprotect(stream, purpose);
return Encoding.ASCII.GetString(encodedBytes);
}
catch (Exception e)
{
// error logging omitted
}
return null;
}
Related
I am working on a bluetooth based application where I create a string in Swift and sign it with RSA private key. The string is sent to a WPF app via bluetooth. The wpf app already has its public key. I want to verify the string the in WPF app. But I am not able to do it. I am getting error for algorithm type.
Following is my Swift code:
func signWithPrivateKey(textToEncrypt: String) -> Data? {
guard let privateKey: SecKey = privateKey else {
return nil
}
let algorithm: SecKeyAlgorithm = .rsaSignatureMessagePSSSHA512
guard SecKeyIsAlgorithmSupported(privateKey, .sign, algorithm) else {
return nil
}
var error: Unmanaged<CFError>?
let data = textToEncrypt.data(using: .utf8)!
guard let signature = SecKeyCreateSignature(privateKey,
algorithm,
data as CFData,
&error) as Data? else {
return nil
}
return signature
}
C# code to verify the string:
var deferral = args.GetDeferral();
var request = await args.GetRequestAsync();
var reader = DataReader.FromBuffer(request.Value);
byte[] fileContent = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(fileContent);
string text = Encoding.UTF8.GetString(fileContent, 0, fileContent.Length);
Debug.WriteLine(Convert.ToBase64String(fileContent));
String EncryptedMessage = Convert.ToBase64String(fileContent);
Debug.WriteLine(text);
using (var rsa = new RSACryptoServiceProvider())
{
var encoder = new UTF8Encoding();
byte[] bytesToVerify = encoder.GetBytes("Guten Morgan");
//byte[] signedBytes = Convert.FromBase64String(text);
try
{
bool success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA-512"), fileContent);
Debug.WriteLine(success);
}catch (CryptographicException e)
{
Debug.WriteLine(e.Message);
}
}
Getting following error:
System.ArgumentNullException: 'Value cannot be null. (Parameter 'hashAlg')'
I have mentioned the algorithm type as rsaSignatureMessagePSSSHA512 in Swift app and trying to verify it with SHA-512 but getting error.
Can someone please help me how to verify the signature in C#.
Edit:
I have modified the WPF code as following and it is not crashing now, but I am still not able to verify it
Credential credential = CredentialManager.ReadCredential("CredentialManagerTests");
String publicKey = credential.Password;
Debug.WriteLine(publicKey);
var deferral = args.GetDeferral();
var request = await args.GetRequestAsync();
var reader = DataReader.FromBuffer(request.Value);
byte[] fileContent = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(fileContent);
string text = Encoding.UTF8.GetString(fileContent, 0, fileContent.Length);
Debug.WriteLine(Convert.ToBase64String(fileContent));
byte[] privateKeyPkcs1DER = ConvertPKCS1PemToDer(publicKey);
using (var rsa = new RSACryptoServiceProvider())
{
var encoder = new UTF8Encoding();
byte[] bytesToVerify = encoder.GetBytes("Guten Morgan");
rsa.ImportRSAPublicKey(privateKeyPkcs1DER, out _);
try
{
bool success = rsa.VerifyData(bytesToVerify, fileContent, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
Debug.WriteLine(success);
}
catch (CryptographicException e)
{
Debug.WriteLine(e.Message);
}
}
Edit2:
String to sent: "Guten Morgan"
Signature: "G8ePlCyPeivAHPygMHXeCTszTy9EV4y6Gsku9GQJV71t+M7ATFZjxkh9u0DVjR0ei76a4dixzgXKLZLQNy/mxwrvUvolPHE0HUFAErkzfeHcrB8/QlHlEoEdt9vf34L8KNy6yVOHWC2sytgvw009EsKUr3LYb8kSkCeaCVEEEAwAELt3z/YzfnbwR0GXatlNpmaoLOXh0mEep8Xh5AHf6RhbgS0VBFSXSbz3ALbwu5DtlaIv4P1nFOP80w2NBs7HrHOMdR0PKdgiD+suErCYWKINzToKDsdO8XNaKdw3jekYBRXl6GNfAwQeADCq0MDsvptCRVZ//fzU2fXRcaYsRA=="
Algo: SHA512
**Public Key: **
MIIBCgKCAQEA4lS13W+TXZhUXy5yB2NpeulCVb1ZSaReopeKrahjKmUx4NQxVXruEYCY3LpjZcSy8xiudVG3GBIMnPLtaMbc5WAYDj1M2OwnpNHdQ8SKtZ1tdA6iRjfOXGUa1n8FMIMKU5ynTSAiSoh+8gGrY0L6jTsCSdLO5ZU53LQFHSESM8JuBeNZozolb/cKb38ylercVeVpo8egoA8UqHezK23VUJ23faxMmMZDJxVn5pfFedxBTLxwU65KQY8Z4izaFjuPLoGe5JZkXyYNMcrYloDCBG5m9BFiXVkoLEDmGFGfWLMJcqL+D2CszqJ4h712ZR6LYRNtIbo/HG9KR7XCtap3QwIDAQAB.
Private Key:
MIIEowIBAAKCAQEAuAoCUOejL7EBI0P4nfZQkAr8/a37SR/umJB5sUOyy2IyaWuSX1tBOco4XThTkCYj2ubgbf0fGsQ7FRgF6bwbeynhDYS7ptPkvUHF4BaSUsu2KtKkoslvl67M83ESKtOSm0s0CImw60MDlPDr/1SPhkYgSfoQ81CXytrZPkWtB/YekT2ph3PRqyOeMNl0O88Clt2mdR4dZeZWv6WGaaoP1kr8RY9iB6hrg2RdOFXixIOdo7cP3+3w9s4svnIX7RNLIIrUxJHQUYA0oxkiZ9y0peWR5mElywjkg/AFm8RxZsXjHowrqlYGkFKzPTq2/dhwId0Yit95aYVIDE5Dj9n3nQIDAQABAoIBAAFiDd9mxjqrBVuq/JjPS46xjnInlw5XH5dk6o0y+Yp+u+s/5DM0P9q70s2ciUA5kSZpesFI2C1+0QTZD95QTBKSX38XAsP/rqXfcym6cbIOltleiN8yTVTh+udPb7gDrAPfvk3cHwi9ka7SWquqCoQTTdXQe8UgU2uyVlSZ+HFpXFE55ltp4CbnFBH39Sa8pqbpwIwb2+PCFkx7xVUIJ2t1vJd2yBUjEBFJv0SiUy9DWwbXiyoqtZXtVft8Vm7xhimSDN2o6KcnXYEq2eb3UhOKI09tRsYLA3bF66bvVxvcnykuUpLHFOxhBKfD8Vsh+SWHdv6zT52gkF7KDUxDYLECgYEA25iGkCcYx+cfzIdVmIbqa662IzkgUlCdpX37tBNHjyE+72Ehw6odZ0e3iTewm/vYBI6hyCW+T/G6ksl2enjOxBH0DhaQEYLqhSGq63/XOsFsmW7tH4YmInURv4RL4KEIB8HNGLfhllJt92KsrBNzei+SjdtElQhULhw4Mle6PWUCgYEA1ox8DxPTE8HQ5Jw+7D0ut+FyYkysciMSFYpmFX5qlbMlfl0WRL4mVFv/tOBerahsCDdaaOKJKafQVbJIU8G9U0m8PAttcM4OMjexwHzT0EBdf9cNACAXQP/arJEbf8YkPr7/IwI6YGiMFzDKRqcrYMNzl9rZXQEyJGj6u4x/6dkCgYBYIWiv5eD+KXYLoazqoAro2J9kl4KvRodeaadg5/PqL4+Qhs0EN/vA/XldaqpIj9RsT8oCB5PPhdY5Hv2bvWxOKF5oYQnE3WO9tntgNFhuzj4Ffg1Qf4hCf/V1hWTma/pLEq57YyD4MXDMvh9KmCvaN8l7gSqPHV6betva6HZoOQKBgQCwZMxSsR/nrIAMlRF+tUbF08txWkylgoQJxcHshgUnkySOYgY++n8U+JahpZ7x8/juQGRKu4W+A8Tb0Dp68lywL31deJ/AEQnG69duxLJ5E5JL2wlLQxcbT8AABUWwpb2DARFPPTO1s/8JyglkUWjuo4NUJJB1UNhi6xTKQdeg8QKBgESvEWQKqvk3YCpVwCNv5C+1aAVS6Fq/sGmLlVsbQ5W6EENYDg4pq2fgghH9lcRb1BdHjFm25ikADbGWgBOG1fvrD9KsR9RY98x8XM3EQ5EJ2Y2vjQfWBA2da8nr9ETRzY00IjHCzb5AhISWIit7Itaa9H202v1zZCIUY+O/YGGX
Edit3:
My below method was wrong:
private byte[] ConvertPKCS1PemToDer(string pemContents)
{
return Convert.FromBase64String(pemContents
.TrimStart("-----BEGIN PUBLIC KEY-----".ToCharArray())
.TrimEnd("-----END PUBLIC KEY-----".ToCharArray())
.Replace("\r\n", ""));
}
Instead of BEGIN PUBLIC KEY it was BEGIN PRIVATE KEY.
I have fixed it as mentioned above but getting following error on Convert line in above function:
System.FormatException: 'The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.'
Edit4:
I have now created a new project only to test my code and have added following code:
Private key for reference:
"MIIEogIBAAKCAQEArMO9cJHbKYKkozTtqPI9fhecyiibJrvsv5KLLPlQcIi0uo6yfLD4MUFS5UbwaGATnCrLRKr27iQLWwWKxsJdEQMZDMF6EiCQSXEB2AmSEPW4NOh5kbj05cJoDrvSeac3lllsjPlOaVQSrgmiOFoJ1t98utfOJ6FCzam9DZZR7r/OwTZ0754rAp4dqwkamcTaSkW2zQhr++mw4WZZiKGJCPYKKXguGJbRTtr+dcZ4JwrvdYudCguuCVfa+1D6k0NswWEu58DELZ0XPuLu8qFiIA9MI5K/ECHXwozi8UxmDhG9NJuh+uRZZ4s7LVnkNDN8np6vhuwKNL5rP0msUciItwIDAQABAoIBAAgEaSZoyepbD2fVC3TMPB/2df9dpSUbSxrixhto541UBIPtVzQVkoi6ne2uqMu3LpAB3mKfcYvAkJDImqrkQc/tWNHbG4lCXtGIxrZoMtjKFJQ824/UAknQzpCeP7UjpHRYapEb+qW2/JJDU9oDRd2B4S+FZXdVAPJXjPutCy40IziykyhU9Fu0fwCN/ry764EyI4gtN/WgDHwc2C8B7sriW81KtB8vJW10waoot3MRBfXxxhk3wyiNputBg1KDz+O2+iUrO4p1OKdvL9S9u65MN9Klm+i03aexNrjbMHu/lxDUa6buSgfXh5+Na6PLHur/3aCqfZdpJ/OO7AxarqECgYEA4884gGubJmiq8OayGdpnaZa4L/2NEwOEnN2G09ySXuUhIyOzOJmeiw3R0BTOB0ZRzrk/Ozq3/T2z1ScUGr3a7hmgGBm3azNjJCbYZ51/XJPE7B5UhDFejZPGRT8vVnUzjYah4Eipp4hSIe79Ja17vTFTtY7bGGl11a6VaPvHuCECgYEAwiTAs60JqUIFw8qmBzWwIHybbKSS0Pc8AWdMYLVSnqiQKZVDnyypUDv14+VEMbCdW21ShMCrNK0QTWnqkV7ln91jxPNvQbSoLBTXgpxbvoxg57jg6sdtiUuPjdBBc74kV9oW1zX2saLvPOO4fjUw3z1b+6/v1SjRlhIqtQrsRdcCgYBeDgYz7zmFaB17jKPnzKZ5j8LH/ZUrTn6IDWZHPoAoMc22plyud65flvsTQCO4GS5ZfV4/5ARmx/zhelrwl4Y2W9ofWS7DUdoS6P7b+MjGvjPFkNgwI/n31hU8LdQrjAQW4IkhAp8ZDk1quTNHRRMbj6wR/8MxlwkRih0h1SImQQKBgGnuQdMH9ICNDLYzKXo/mhVvyCJ0fcNVU0F0yqDt7uGxGdAGqLn+VXf474bkvtvaAVI0iVT0B7abQ4zp4NpnDCW5V8nMBgW0/BnpWVnj1M9YqztkjhysqiDCwNZhLoVn1060KchNooh0XdM8cZszjLISOdFPwy3ssscOrIzSI+9LAoGAMDcGC5P9gVBCNd8ox5OlozNwAmHmHwpqFJcOxNx4lCE5DkT+6DEscoLMZ4LkNVzMHb0LmyhDnF24eIn/v7FRb6ZF5YzlpIdQC2tvXaJoyPwLPee7XGGr9IRkupbrNzPWVhTUYK9kJvI7SWPF2DBFK8fiC5S96USVp++kOjp/faE="
String publicKey = "MIIBCgKCAQEArMO9cJHbKYKkozTtqPI9fhecyiibJrvsv5KLLPlQcIi0uo6yfLD4MUFS5UbwaGATnCrLRKr27iQLWwWKxsJdEQMZDMF6EiCQSXEB2AmSEPW4NOh5kbj05cJoDrvSeac3lllsjPlOaVQSrgmiOFoJ1t98utfOJ6FCzam9DZZR7r/OwTZ0754rAp4dqwkamcTaSkW2zQhr++mw4WZZiKGJCPYKKXguGJbRTtr+dcZ4JwrvdYudCguuCVfa+1D6k0NswWEu58DELZ0XPuLu8qFiIA9MI5K/ECHXwozi8UxmDhG9NJuh+uRZZ4s7LVnkNDN8np6vhuwKNL5rP0msUciItwIDAQAB";
String signature = "ntGm9Vx38l26ZSEitc1L+G6YZw3Dt4dmXoQQUjSAcKD1d4bFm8QI6CIncTBQ0+rGWUh7fr2DWQBB1OPgoZRoDs23SGvl+KTqUBo6Ym+kIK4KXwBrMt3YXNFpXuXVk36+4+oBPFhcMAwG4fIG5MnMEHqv/F+NNA71a10irB0frMpwpU/FZDGEbCnvOxGe6qIUsYt/dMbf1UT1AjKqQA0EmNqvIsU5DuHcpScb5xuV8aPJg6vPPKafLa8LbBiCA4dyxUcUYSjSaTQ6D9YoZ3Ich/dRBCyIdw9vESBSrOUGvwOmNl+5X+r2UyFN8RgYwW61M3iYdmkwJYYVUbMA69vCOA==";
byte[] signatureBytes = Encoding.UTF8.GetBytes(signature);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportRSAPublicKey(Convert.FromBase64String(publicKey), out _);
var encoder = new UTF8Encoding();
byte[] bytesToVerify = encoder.GetBytes("Guten Morgen");
try
{
bool success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA512"), signatureBytes);
Debug.WriteLine(success);
}
catch (CryptographicException e)
{
Debug.WriteLine(e.Message);
}
}
But success variable is still false.
I am trying to communicate with the Apple Music API from a Xamarin.Forms project. Due to the Microsoft ECDsa implementation not being available on Xamarin.Android and Xamarin.iOS I am trying to work around that limitation using the Portable.BouncyCastle Nuget package. Overall the process seems to be working as intended, but when trying to call the Apple Music API with the signed developer token I always receive a HTTP/2 401.
I have a working MS ECDsa implementation that I use in a ASP.NET Core project for communication with the Apple Push Notification Service, so I wrote a quick little demo tool that would generate the keys in both approaches, the result: The signatures do not match and the MS ECDsa variant actually works, I am getting the proper API response from Apple.
I looked at a lot of samples on the net and I can not see what I am actually doing wrong, so perhaps someone here can point me into the right direction.
Bouncy Castle approach:
public static AsymmetricCipherKeyPair GetKeys(string data)
{
var tag = $"{_className}.GetECDsa";
try
{
byte[] byteArray = Encoding.ASCII.GetBytes(data);
MemoryStream stream = new MemoryStream(byteArray);
using (TextReader reader = new StreamReader(stream))
{
var ecPrivateKeyParameters =
(ECPrivateKeyParameters)new PemReader(reader).ReadObject();
var q = ecPrivateKeyParameters.Parameters.G.Multiply(ecPrivateKeyParameters.D).Normalize();
var ecPublicKeyParameters = new ECPublicKeyParameters(q, ecPrivateKeyParameters.Parameters);
return new AsymmetricCipherKeyPair(ecPublicKeyParameters, ecPrivateKeyParameters);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
public static string CreateToken(AsymmetricCipherKeyPair keyPair, string p8privateKeyId, string teamId, DateTime date)
{
var tag = $"{_className}.CreateJwtToken";
try
{
var header = JsonHelper.Serialize(new { alg = "ES256", kid = p8privateKeyId });
var payload = JsonHelper.Serialize(new { iss = teamId, iat = ToEpoch(date), exp = ToEpoch(date.AddSeconds(15777000)) });
var headerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(header));
var payloadBasae64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtData = $"{headerBase64}.{payloadBasae64}";
var signature = GetSignature(unsignedJwtData, keyPair);
return $"{unsignedJwtData}.{Convert.ToBase64String(signature)}";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
private static int ToEpoch(DateTime time)
{
var span = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToInt32(span.TotalSeconds);
}
private static byte[] GetSignature(string plainText, AsymmetricCipherKeyPair key)
{
var encoder = new UTF8Encoding();
var inputData = encoder.GetBytes(plainText);
var signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(true, key.Private);
signer.BlockUpdate(inputData, 0, inputData.Length);
return signer.GenerateSignature();
}
MS ECDsa approach:
public static string GetPrivateKey(string p8privateKey)
{
var tag = $"{_className}.GetPrivateKey";
try
{
var dsa = GetECDsa(p8privateKey);
var keyBytes = dsa.ExportPkcs8PrivateKey();
return Convert.ToBase64String(keyBytes);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
private static ECDsa GetECDsa(string p8privateKey)
{
var tag = $"{_className}.GetECDsa";
try
{
byte[] byteArray = Encoding.ASCII.GetBytes(p8privateKey);
MemoryStream stream = new MemoryStream(byteArray);
using (TextReader reader = new StreamReader(stream))
{
var ecPrivateKeyParameters =
(ECPrivateKeyParameters)new PemReader(reader).ReadObject();
var q = ecPrivateKeyParameters.Parameters.G.Multiply(ecPrivateKeyParameters.D).Normalize();
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.CreateFromValue(ecPrivateKeyParameters.PublicKeyParamSet.Id),
D = ecPrivateKeyParameters.D.ToByteArrayUnsigned(),
Q =
{
X = q.XCoord.GetEncoded(),
Y = q.YCoord.GetEncoded()
}
});
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
public static string CreateJwtToken(string p8privateKey, string p8privateKeyId, string teamId, DateTime date)
{
var tag = $"{_className}.CreateJwtToken";
try
{
var header = JsonHelper.Serialize(new { alg = "ES256", kid = p8privateKeyId });
var payload = JsonHelper.Serialize(new { iss = teamId, iat = ToEpoch(date), exp = ToEpoch(date.AddSeconds(15777000)) });
using var dsa = ECDsa.Create("ECDsa");
var keyBytes = Convert.FromBase64String(p8privateKey);
dsa.ImportPkcs8PrivateKey(keyBytes, out _);
var headerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(header));
var payloadBasae64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtData = $"{headerBase64}.{payloadBasae64}";
var unsignedJwtBytes = Encoding.UTF8.GetBytes(unsignedJwtData);
var signature = dsa.SignData(unsignedJwtBytes, 0, unsignedJwtBytes.Length, HashAlgorithmName.SHA256);
return $"{unsignedJwtData}.{Convert.ToBase64String(signature)}";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
I am looking forward to your feedback.
Thanks to the comment of Topaco I tested with SHA-256withPLAIN-ECDSA and it solved the issue. The signatures are not identical, however the Apple Music API accepts the signed JWT Token and responds properly. So thanks Topaco for pointing this out.
Needto verify the signature of below JSON payload using RSA256 in C#
User Payload with Signature
{
"name": {
"value": "Test",
"signature": "0b33ce0b9c09f286ddef4016bcfa046191d268247d5df85aa4ebc3e25c42db592a2bcb2b15e83005e33c81a6645d296a8e76cc21874fd7134f307593a117569d51f6d1794c2e351ec8341134532be47d1c3303a2a47bd5aa5015380585420b44ed726050a5db3f14191beb09074fd69fa1f3c48520e7368d4b6f65acd9ea95029260570d4d7d6166310c8cdb82cf4e55f9a658c76916290bfb9e68f867185dc8ee22e3f56c2d6730d9570a4806dc1bc9a2d987250a0ed13196d944912fd3bf6c7038756996863690adbd829374ff7588a56d3cfd795e9c6e188c65594a633b7201dd3eb2d1b089f28cd5143a5dcfbd211c4ff428fb8050415f022f4910bf48c0"
}
}
VerifyData Code Block
{
bool success = false;
string nameValue = userInformation.name.value.ToString();
string nametobeverify = JsonConvert.SerializeObject(new { name = nameValue });
string key = RemoveHeaderFooterFromKey(publicKey);
var keyBytes = Convert.FromBase64String(key); // your key here
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
using (var rsa = new RSACryptoServiceProvider())
{
var encoder = new UTF8Encoding();
byte[] bytesToVerify = encoder.GetBytes(nametobeverify);
byte[] signedBytes = Convert.FromBase64String(userInformation.name.signature);
try
{
rsa.ImportParameters(rsaParameters);
SHA256Managed Hash = new SHA256Managed();
byte[] hashedData = Hash.ComputeHash(signedBytes);
success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA256"), signedBytes);
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return success;
}
Here I am trying verify the signature of Name data field in User Payload but I am always getting false. Am I doing it the wrong way?
For each data field, there is a field name, value, and signature.
To verify a data field, need to create a json object of field name to value, stringify the object, then verify it against the signature using the verification_key(public key), using RS256.
User Payload format { name: { value: "John", signature: "xyz"}
dataToVerify = stringify({name: "John"})
isValid = RS256(dataToVerify, verification_key, "xyz")
I am new here.
I am learning the digital signature in C#. The certificates are generated followed by this document. Other documents I read: RSACng,
X509Certificate2.
I am working on Windows 10 Pro 1809, .Net Core 2.1, VSCode.
class Program
{
static void Main(string[] args)
{
var passwd = "password";
// Get client certificate.
var clientCertPath = #"./Certificates/test.pfx";
var clientCert = new X509Certificate2(clientCertPath, passwd);
// Get server certificate.
var serverCertPath = #"./Certificates/test.cer";
var serverCert = new X509Certificate2(serverCertPath);
// Generate data.
var translateResultData = BuildData();
var content = String.Join('&', translateResultData.Select(p => String.Join('=', p.Key, p.Value)));
// Sign
var sign = SignatureUtil.Sign(data: content, clientCert: clientCert);
// translateResultData.TryAdd(key: "sign", value : sign);
// Copy content ONLY for test.
var checkSign = sign;
var checkContent = content;
// Verify
var valid = SignatureUtil.Verify(data: checkContent, signature: checkSign, serverCert: serverCert);
System.Console.WriteLine(valid);
}
}
public class SignatureUtil
{
public static string Sign(string data, X509Certificate2 clientCert)
{
using(var privateKey = clientCert.GetRSAPrivateKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = privateKey.SignData(
data: dataByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureByteArray);
}
}
public static bool Verify(string data, string signature, X509Certificate2 serverCert)
{
try
{
using(var publicKey = serverCert.GetRSAPublicKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = Convert.FromBase64String(signature);
return publicKey.VerifyData(
data: dataByteArray,
signature: signatureByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
}
}
catch (System.Exception)
{
return false;
}
}
}
Expected result: valid should be true because I am checking the original data.
Fact: The Verify method always returns false even the original data are passed.
Can you tell me what I did wrong?
I cannot tell you what is going wrong in your code since I cannot reproduce it. Here is a very detailed answer how you can sign using RSA and SHA256. Your approach and the one described in this answer are conceptually the same, but maybe there is a difference in your code compared to this answer.
And here is a example how in my company certificates associated with smart cards are used for signing and verifying signatures. One big difference is that we do not store the signature as a string but rather keep it as an array of bytes.
public byte[] SignData(byte[] data)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPrivateKey())
{
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}
public bool VerifySignature(byte[] data, byte[] signature)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPublicKey())
{
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}
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.