I need to support the following signature:
Sign the UTF-8 representation of the input using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key obtained from the Google Developers Console. The output will be a byte array.
The following code fails, with "Invalid algorithem specified". Is this a limitation of .NET? Here is a snippet of my code:
var rsa2 = new RSAPKCS1SignatureFormatter(rsa);
rsa2.SetHashAlgorithm("SHA256");
bytes = rsa2.CreateSignature(bytes);
The above requirement is from computing the signature for a Server to Server Applications for Google API.
https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
Thanks for any help.
Karl..
Here is the code that signs the JWT. I've removed the use of RSAPKC1SingatureFormatter class and using another HASHCompute method in an effort to get something to work (still not working)
I'm not sure this is correct and unfortunately my response from the Rest service is always the same "Invalid Grant" so hard to tell.
public string Generate(string HeadJWT, string ContentJWT, X509Certificate2 certificate)
{
var bytes = Utility.getBytes(HeadJWT);
var base64Head = Utility.Base64UrlEncode(bytes);
// base64 Url Encode Payload (Json Content)
bytes = Utility.getBytes(ContentJWT);
var base64Payload = Utility.Base64UrlEncode(bytes);
var secureInputValue = String.Format("{0}.{1}", base64Head, base64Payload);
bytes = Stub.Jwt.Utility.getBytes(secureInputValue);
bytes = Stub.Jwt.Utility.ComputeHMACSha265(bytes, certificate.PublicKey.EncodedKeyValue.RawData);
_signature = Stub.Jwt.Utility.Base64UrlEncode(bytes);
return String.Format("{0}.{1}.{2}", base64Head, base64Payload, _signature);
}
This cannot be a limitation of .NET in general, as the example Microsoft code seems to use "SHA256" itself. But it could be a limitation of your particular runtime.
Related
I am trying to encrypt the text in C# using google KeyManagementService.
I am not able to decrypt the cipher generated by KeyManagementServiceClient.Encrypt using KeyManagementServiceClient.Decrypt method. Decryption gives me:
Grpc.Core.RpcException: Status(StatusCode=InvalidArgument, Detail="Decryption failed: the ciphertext is invalid.")
I tried to use the google Try this API link to get the cipher and the length of the cipher generated by google API and the C# result are different.
Ex:
plaint text: text
cipher from C#:
TaRQSJ2KXrdmJJT6MmlD8RrcxzPJEa1jwAXWJ1puXg6nbl80aBcSLQBqSYOONfXhpZx8SyxCdB6mqTgr8uLJoAjva+Q4kN/p0+9RL2Sp2mHq4wjmZQ==
cipher from API:
TaRQSJ2KXv9ntnS7IszL077KNPtGJnqF9pSNiWANsq8gD0whezUSKwBqSYOOKKMifiWrfPDnHL5xETHPPlms0ztjkqa5hjdtkHwpzByLzi68A40
Has anyone phased the issue?
Here is my sample code
KeyManagementServiceClient keyManagementServiceClient =KeyManagementServiceClient.Create(channel);
byte[] plaintext=Encoding.ASCII.GetBytes("test");
EncryptRequest request = new EncryptRequest
{
CryptoKeyPathName = new CryptoKeyPathName("test-project", "global", "test-key", "encryption-key"),
Plaintext = ByteString.CopyFrom(plaintext),
};
EncryptResponse response =keyManagementServiceClient.Encrypt(request);
var cipher =
Convert.ToBase64String(response.Ciphertext.ToByteArray());
//Decrypt
ByteString ciphertext = ByteString.CopyFrom(Encoding.ASCII.GetBytes(cipher));
DecryptRequest req = new DecryptRequest
{
CryptoKeyName = new CryptoKeyName("test-project", "global", "test-key", "encryption-key"),
Ciphertext = ciphertext,
};
// Make the request
DecryptResponse res = keyManagementServiceClient.Decrypt(req);
You've base64-encoded the results of encrypting, but you haven't base64-decoded it before you try to decrypt it.
In the line
ByteString ciphertext = ByteString.CopyFrom(Encoding.ASCII.GetBytes(cipher));
It should instead look something like
ByteString ciphertext = ByteString.FromBase64(cipher);
(Note also that the ByteString class has built-in methods for going to/from Base64, if that's what you're looking to do.)
I have implement client certificate based security in C#. Everything is clear and working good for me. I was finding perfect way to verify client certificate on server side, through which i came to x509Chain. But i am not clear about How it exactly works? How can we configure it? Is it secure way to verify certificate?
Any help will be Appreciated! Thanks!
Note: By theoretical point of view, i have read my documents on that but i am not sure for it's functionality. Please provide practical example and guide for that.
A bit of theory I wrote some time ago: http://social.technet.microsoft.com/wiki/contents/articles/3147.certificate-chaining-engine-cce.aspx
This article describes what is certificate chaining engine (CCE) and how it works in Windows in general. It is based on RFC5280 and Microsoft-specific implementation of certificate chaining engine. .NET uses native CryptoAPI functions, so X509Chain behaves in the same way as in native CryptoAPI.
The X509Chain does not work reliably for scenarios where you do not have the root certificate in the trusted CA store on the machine.
Others will advocate using bouncy castle. I wanted to avoid bringing in another library just for this task, so I wrote my own.
As see in RFC3280 Section 4.1 the certificate is a ASN1 encoded structure, and at it's base level is comprised of only 3 elements.
The "TBS" (to be signed) certificate
The signature algorithm
and the signature value
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
C# actually has a handy tool for parsing ASN1, the System.Formats.Asn1.AsnDecoder.
Using this, we can extract these 3 elements from the certificate to verify the chain.
The first step was extracting the certificate signature, since the X509Certificate2 class does not expose this information and it is necessary for the purpose of certificate validation.
Example code to extract the signature value part:
public static byte[] Signature(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
var offsetSpan = certificateSpan[(tbsOffset + tbsLength)..];
AsnDecoder.ReadSequence(
offsetSpan,
encodingRules,
out var algOffset,
out var algLength,
out _
);
return AsnDecoder.ReadBitString(
offsetSpan[(algOffset + algLength)..],
encodingRules,
out _,
out _
);
}
The next step is to extract the TBS certificate. This is the original data which was signed.
example code to extract the TBS certificate data:
public static ReadOnlySpan<byte> TbsCertificate(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
// include ASN1 4 byte header to get WHOLE TBS Cert
return certificateSpan.Slice(tbsOffset - 4, tbsLength + 4);
}
You may notice that when extracting the TBS certiifcate I needed to include the ASN1 header in the data, this is because the signature of the TBS Certificate INCLUDES this data (this annoyed me for a while).
For the first time in history, the Microsoft does not impede us with their API design, and we are able to obtain the Signature Algorithm directly from the X509Certificate2 object. Then we just need to decide to what extend we are going to implement different hash algorithms.
var signature = signed.Signature();
var tbs = signed.TbsCertificate();
var alg = signed.SignatureAlgorithm;
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad
switch (alg)
{
case { Value: var value } when value?.StartsWith("1.2.840.113549.1.1.") ?? false:
return signedBy.GetRSAPublicKey()?.VerifyData(
tbs,
signature,
value switch {
"1.2.840.113549.1.1.11" => HashAlgorithmName.SHA256,
"1.2.840.113549.1.1.12" => HashAlgorithmName.SHA384,
"1.2.840.113549.1.1.13" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
RSASignaturePadding.Pkcs1
) ?? false;
case { Value: var value } when value?.StartsWith("1.2.840.10045.4.3.") ?? false:
return signedBy.GetECDsaPublicKey()?.VerifyData(
tbs,
signature,
value switch
{
"1.2.840.10045.4.3.2" => HashAlgorithmName.SHA256,
"1.2.840.10045.4.3.3" => HashAlgorithmName.SHA384,
"1.2.840.10045.4.3.4" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
DSASignatureFormat.Rfc3279DerSequence
) ?? false;
default: throw new UnsupportedSignatureAlgorithm(alg);
}
As shown in the code above, https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad is a good resource to see the mapping of algorithms and OIDs.
Another thing you should be aware of is that there are some articles out there that claim that for elliptical curve algorithms, microsoft expects a R,S formatted key instead of a DER formatted key. I tried to convert the key to this format but it ultimately didn't work. What I discovered was that it was necessary to use the DSASignatureFormat.Rfc3279DerSequence parameter.
Additional certificate checks, like "not before" and "not after", or CRL and OCSP checks can be done in addition to the chain verification.
I have created a wesite in asp.net and use ms-sql database to save the records. Now want to convert it in node.js application. And want to use same sql database. In asp.net application I have encrypt the password for registered user. Below is code.
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
data = x.ComputeHash(data);
return System.Text.Encoding.ASCII.GetString(data);
}
public static bool MatchHash(string HashData, string HashUser)
{
HashUser = CreateHash(HashUser);
if (HashUser == HashData)
return true;
else
return false;
}
Now problem is that how I use same encryption in node.js. So when node application is ready old user can also make login. It only possible if node app also use same encryption that I have use in asp.net.
For node I have created all environment and use mssql module for database communication. Please help me fix that. Thanks!!
First of all MD5 shall no longer be used if you are serious about security.
Based on your comment and code, I fear there is a 'data loss' in the initial ASP.net code.
Let us have a look at CreateHash function again, I've added comments:
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
// Convert unHashed string to bytes using ASCII coding
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
// Compute MD5 hash from bytes
data = x.ComputeHash(data);
// Decode MD5 resulting bytes as ASCII
return System.Text.Encoding.ASCII.GetString(data);
}
The last line confuses me, it is decoding bytes received from MD5 function as if they were ASCII, but that is incorrect assumption. And the resulting encoded string as you gave in comment contains lots of "?'s".
Next node.js code will do similar except encode the string using hex rather than ascii:
var crypto = require('crypto')
function createHash(data) {
return crypto.createHash('md5').update(data, 'ascii').digest('hex')
}
To emulate "bytes to ascii" you could try .digest('binary') instead of hex. If it does not give what you expect, then you have to make a separate 'conversion' step from hex to ascii. (I am not experienced enough to give you elegant solution to the later one)
I have a working Form Integration but need to move to v 3.00 of Form Integration which requires AES coding rather than Xor. The sample code is a J2EE app which hides away the method of encrypting. SagePay support have been unable to provide an API class/method for encrypting/decrypting; I suspect they are there, if anyone has used them and can advise on this?
Alternatively I am trying to do the encryption in my own code. Again SagePay have not been forthcoming, in that they don't provide enough information about how the encryption is done. I believe I have worked out that the password they provide is used both for the encryption key and iv, though they don't state this in the integration guide. Here is code I have tried for encoding inString
byte[] byteDataToEncrypt = inString.getBytes();
final byte[] keyBytes = webSite.encryptionPassword().getBytes("ASCII");
final byte[] ivBytes = webSite.encryptionPassword().getBytes("ASCII");
final SecretKey key = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec iv = new IvParameterSpec(ivBytes);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] byteCipherText = cipher.doFinal(byteDataToEncrypt);
byte[] aesEncryptedBase64encoded = Base64.encodeBase64(byteCipherText);
String crypt = "#" + new String(aesEncryptedBase64encoded);
I do not know why # would need to be on the front, but I have seen it in other code that has been posted online for SagePay integration. Without it SagePay test server tells me the encryption method is not recognised. With it, it tells me the Currency field is missing, though I know the currency field is set ok to "GBP" in the source and I am using the provided test server password.
Any help very much appreciated whether from SagePay or from the developer community.
John
This works even if moving to ver4.00. The only thing that needs to be different for ver 4.00 is that it needs the encrypted string to be in upper case. Hence instead of
String crypt = "#" + Hex.encodeHexString(byteCipherText);
it will need to be
String crypt = "#" + Hex.encodeHexString(byteCipherText).toUpperCase();
Latest Sagepay/Opayo documentation at least verbally lists what all is needed and even if it is still not very comprehensive, together with this code, I could work it out.
Thanks.
The solution that worked for me is to replace this:
byte[] aesEncryptedBase64encoded = Base64.encodeBase64(byteCipherText);
String crypt = "#" + new String(aesEncryptedBase64encoded);
with this
String crypt = "#" + Hex.encodeHexString(byteCipherText);
or if using the SagePay API directly, replace all the code with just these two lines:
byte[] aesEncrypted = CryptographyHelper.AESEncrypt(detail, "ISO-8859-1", password);
String crypt = "#" + Hex.encodeHexString(aesEncrypted);
My Company recently signed up for Google Maps API for business.
To use the API, I need to generte a HMacSHA1 signature, and add it to my HTTP request. Unfortunately, somehow, I am not able to generate the right signature.
For testing, I am using the values provided by google to ensure that the algorithm works fine and I get the right result. Here is the code:
string url = 'maps/api/geocode/json?address=New+York&sensor=false&client=clientID';
string privateKey = 'vNIXE0xscrmjlyV-12Nj_BvUPaw=';
privateKey = privateKey.replace('-', '+');
privateKey = privateKey.replace('_', '/');
//Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob privateKeyBlob = Blob.valueOf(privateKey);
Blob urlBlob = Blob.valueOf(url);
Blob signatureBlob = Crypto.generateMac('HMacSHA1', urlBlob, privateKeyBlob);
String signature =EncodingUtil.urlEncode(EncodingUtil.base64Encode(signatureBlob), 'UTF-8');
signature = signature.replace('+', '-');
signature = signature.replace('/', '_');
system.debug('signature is ' +signature);
The generated signature should be : KrU1TzVQM7Ur0i8i7K3huiw3MsA=
Here is the link to Google Documentation where you can also find the same example: https://developers.google.com/maps/documentation/business/webservices
Few points to note:
1. I used the sample Python script provided in API Documentation and it gives the right result.
2. I think the problem is, API Documentation says that we should decode the privateKey and then provide it to the function. Although the documentation for Crypto Class says the "The value of privateKey does not need to be in decoded form.". I tried both, with and without decoding, still no result.
3. For Google API, everything has to be UTF-8 Encoded; I don't know if thats the way Encoding.Util decode's it.
I have tried a lot fo combinations, but could not find a solution. Any help would be highly appreciated.
Thanks,
Ankit
You need to sign the full path and query. Your string url is missing the leading slash (/).
Also, don't be afraid to open a support case with Google for this type of query.
We have used the code, but there are some defects in it, for whomever intend to use this code, the actual code will be:
string url = '/maps/api/geocode/json?address=New+York&sensor=false&client=clientID';
string privateKey = 'vNIXE0xscrmjlyV-12Nj_BvUPaw=';
privateKey = privateKey.replace('-', '+');
privateKey = privateKey.replace('_', '/');
Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob urlBlob = Blob.valueOf(url);
Blob signatureBlob = Crypto.generateMac('hmacSHA1', urlBlob, privateKeyBlob);
String signature = EncodingUtil.base64Encode(signatureBlob);
signature = signature.replace('+', '-');
signature = signature.replace('/', '_');
system.debug('*** Signature: ' + signature);