I am the author of a .NET library that allows developers to process data provided by a 3rd party. Among the many features my library provides is the ability to validate that received data was indeed signed by the 3rd party in question. The 3rd party provides the following information:
a string containing base64 encoded DER signature
a string containing base64 encoded secp256r1/NIST P-256 public key
a array of bytes containing the data that was encoded by the 3rd party using the private key
The developer expects my library to return a Boolean value indicating whether the data is legitimate or not. I was able to figure out how to convert the signature to Microsoft CNG supported format thanks to this StackOverflow question and, similarly, I figured out how to convert the public key into Microsoft CNG supported format thanks to this other StackOverflow question. I put it all together in the following C# code snippet:
// Convert the signature and public key provided by the 3rd party into formats usable by the .net crypto classes
var signatureBytes = Convert.FromBase64String(signature);
var sig = ConvertECDSASignature.LightweightConvertSignatureFromX9_62ToISO7816_8(256, signatureBytes);
var cngBlob = Utils.ConvertSecp256R1PublicKeyToEccPublicBlob(publicKey);
// Verify the signature
var cngKey = CngKey.Import(cngBlob, CngKeyBlobFormat.EccPublicBlob);
var eCDsaCng = new ECDsaCng(cngKey);
var verified = eCDsaCng.VerifyData(data, sig);
This has been working perfectly until a developer recently complained about System.PlatformNotSupportedException on Linux/Ubuntu machines. After a quick research, I found out that ECDsaCng is Windows-specific and I should be using ECDsa which is cross-platform.
So I came up with the following code which is no only cross-platform but also is much simpler that my original code because I no longer need to convert the signature and public key to different formats:
var signatureBytes = Convert.FromBase64String(signature);
var publicKeyBytes = Convert.FromBase64String(publicKey);
// Verify the signature
var eCDsa = ECDsa.Create();
eCDsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
var verified = eCDsa.VerifyData(data, signatureBytes, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
The only caveat is that Microsoft introduced the ImportSubjectPublicKeyInfo method in the ECDsa class in more recent versions of the .NET framework (I could be wrong but I believe it was introduced in .NET core 3.1) and my library targets netstandard2.0 so it can be used by developers who are not necessarily using the latest .NET.
So, all this to say: is there a way to validate the data using the provided signature and public key in a way that is cross-platform AND usable in netstandard2.0?
ECDsa.ImportSubjectPublicKey() is supported in .NET Core 3.0 and later, but not in .NET Framework. An alternative would be ECDsa.ImportParameters(), which according to the documentation is supported as of .NET Framework 4.7 and .NET Core 1.0. DSASignatureFormat is supported as of .NET 5.0.
So a possible alternative would be the current code, with the key import modified as follows:
// Test data
byte[] data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
string signature = "MEUCIFBfoMubs82ExlbPQHR2LKKcJpvODxaoo4NO4VoKmRfxAiEAg6tug3ctzSAZrkF175B71D7Uynn9Bc1O40XIpxD93MY=";
string publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==";
// Convert the signature and public key provided by the 3rd party into formats usable by the .net crypto classes
var signatureBytes = Convert.FromBase64String(signature);
var sig = lightweightConvertSignatureFromX9_62ToISO7816_8(256, signatureBytes);
ECParameters ecParameters = ConvertSecp256r1PublicKeyToECParameters(publicKey); // Replaced!
// Verify the signature
var eCDsa = ECDsa.Create();
eCDsa.ImportParameters(ecParameters);
var verified = eCDsa.VerifyData(data, sig, HashAlgorithmName.SHA256);
where ConvertSecp256r1PublicKeyToECParameters() is a slightly modified version of ConvertSecp256R1PublicKeyToEccPublicBlob():
private static readonly byte[] s_secp256r1Prefix = Convert.FromBase64String("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");
private static ECParameters ConvertSecp256r1PublicKeyToECParameters(string base64)
{
byte[] subjectPublicKeyInfo = Convert.FromBase64String(base64);
if (subjectPublicKeyInfo.Length != 91)
throw new InvalidOperationException();
byte[] prefix = s_secp256r1Prefix;
if (!subjectPublicKeyInfo.Take(prefix.Length).SequenceEqual(prefix))
throw new InvalidOperationException();
byte[] x = new byte[32];
byte[] y = new byte[32];
Buffer.BlockCopy(subjectPublicKeyInfo, prefix.Length, x, 0, x.Length);
Buffer.BlockCopy(subjectPublicKeyInfo, prefix.Length + x.Length, y, 0, y.Length);
ECParameters ecParameters = new ECParameters();
ecParameters.Curve = ECCurve.NamedCurves.nistP256; // aka secp256r1 aka prime256v1
ecParameters.Q.X = x;
ecParameters.Q.Y = y;
return ecParameters;
}
The code is supported on my machine (Windows 10) as of .NET Framework 4.7 and .NET Core 2.0 (it doesn't run under 1.0, but that may be a local issue). I see no reason why there should be problems on Linux/Ubuntu. However, I have not tested this.
Related
I have a requirement to generate SHA1 hashed certificates for an old legacy system which does not support SHA256. I'm well aware that MD5 and SHA1 hashing in crypto scenarios is not recommended, but this is an external requirement.
I'm using .NET Framework 4.7.2 and its new CertificateRequest class.
Here is the relevant code snippet:
using (var rsa = RSA.Create(2048))
{
var subjectName = "CN=sampleName";
var certificateRequest = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
var selfSignedCertificate = certificateRequest.CreateSelfSigned(DateTime.UtcNow, DateTime.UtcNow.AddYears(10));
var certAsBytes = selfSignedCertificate.Export(X509ContentType.Pfx, "fakePassword");
File.WriteAllBytes("cert-sha1.pfx", certAsBytes);
}
When certificateRequest.CreateSelfSigned is called, it throws the following exception:
'SHA1' is not a known hash algorithm.
Parameter name: hashAlgorithm
Actual value was SHA1.
The relevant point in the stack trace is:
...
at System.Security.Cryptography.X509Certificates.RSAPkcs1X509SignatureGenerator.GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm)
at System.Security.Cryptography.X509Certificates.TbsCertificate.Encode(X509SignatureGenerator signatureGenerator, HashAlgorithmName hashAlgorithm)
at System.Security.Cryptography.X509Certificates.TbsCertificate.Sign(X509SignatureGenerator signatureGenerator, HashAlgorithmName hashAlgorithm)
at System.Security.Cryptography.X509Certificates.CertificateRequest.Create(X500DistinguishedName issuerName, X509SignatureGenerator generator, DateTimeOffset notBefore, DateTimeOffset notAfter, Byte[] serialNumber)
at System.Security.Cryptography.X509Certificates.CertificateRequest.CreateSelfSigned(DateTimeOffset notBefore, DateTimeOffset notAfter)
at Tests.Sha1Test()
From the .NET API browser for the constructor I'm using, I don't see anything why this shouldn't work, even if not recommended. Using MD5 throws a similar exception. SHA256 and above work fine.
The CertificateRequest class also does not show in the reference source (maybe because it's too new?) so I'm out of ideas.
Not supporting MD5 and SHA-1 was intentional, based on feedback from the feature pull request (after opening the link you have to wait a few seconds while the page loads enough code to find the anchor then re-jump).
The documentation has now been updated to say that:
Remarks
This method does not support using MD5 or SHA-1 as the hash algorithm for the certificate signature. If you need an MD5 or SHA-1 based certificate signature, you need to implement a custom X509SignatureGenerator and call Create(X500DistinguishedName, X509SignatureGenerator, DateTimeOffset, DateTimeOffset, Byte[]).
Surely creating an X509SignatureGenerator is hard, right? Well, from the same feedback link:
private sealed class RSASha1Pkcs1SignatureGenerator : X509SignatureGenerator
{
private readonly X509SignatureGenerator _realRsaGenerator;
internal RSASha1Pkcs1SignatureGenerator(RSA rsa)
{
_realRsaGenerator = X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1);
}
protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey;
public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm)
{
if (hashAlgorithm == HashAlgorithmName.SHA1)
return "300D06092A864886F70D0101050500".HexToByteArray();
throw new InvalidOperationException();
}
public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) =>
_realRsaGenerator.SignData(data, hashAlgorithm);
}
That code is also used as a test for the ability to provide a custom generator.
I'm working with a 3rd party who sends me a certificate via a byte array. They send me 3 strings in an XML document that include, x509Data, SignatureValue, and DigestValue(for debugging).
They want me to validate that the SignatureValue is valid using the certificate public key contained in the x509Data cert. I'm populating the cert fine but when I try to Verify, it always returns false.
Here is my code:
byte[] SignatureValueBytes = Convert.FromBase64String(Signature.SignatureValue);
byte[] x509DataBytes = Convert.FromBase64String(Signature.x509Data);
byte[] DigestValueBytes = Convert.FromBase64String(Signature.DigestValue);
X509Certificate2 cert = new X509Certificate2(x509DataBytes);
using (RSA RSA = (RSA)cert.PublicKey.Key)
{
bool a = RSA.VerifyData(x509DataBytes, SignatureValueBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Signature.* is the string coming from the XML file. Can some kind soul point out where I'm going wrong here?
Your code, as written, is trying to verify that SignatureValueBytes is a signature that signed x509DataBytes using RSASSA-PKCS1-SHA256.
Assuming you got the RSASSA-PKCS1-SHA256 part right, you probably want to use VerifyHash and DigestValueBytes instead of VerifyData and x509DataBytes. (You also want to use cert.GetRSAPublicKey() instead of cert.PublicKey.Key)
byte[] SignatureValueBytes = Convert.FromBase64String(Signature.SignatureValue);
byte[] x509DataBytes = Convert.FromBase64String(Signature.x509Data);
byte[] DigestValueBytes = Convert.FromBase64String(Signature.DigestValue);
X509Certificate2 cert = new X509Certificate2(x509DataBytes);
using (RSA RSA = cert.GetRSAPublicKey())
{
bool a = RSA.VerifyHash(DigestValueBytes, SignatureValueBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
I am trying to read an RSA private key into .Net using BouncyCastle to test data I have previously encrypted. The encrypted data is working fine using the public key and Bouncy Castle and I have also used the same private key as below (which is DER format) to successfully decrypt my data in a PHP application but I don't know why I can't create the private key in .Net to do the same thing:
byte[] privatekey = File.ReadAllBytes(#"C:\Users\Luke\privkey.der");
var rsaKeyParameters = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privatekey);
The second line throws an exception:
"unknown object in factory: DerInteger\r\nParameter name: obj"
I also tried using a stream instead of a byte array and the same error occurs. The key pair was created using OpenSSL and as mentioned, decryption works in PHP using openssl_private_decrypt() and the same key as in the .Net code. I also tried a PEM format of the same key and that also didn't work (but I don't think BC supports PEM directly anyway)
Has anyone done this before? Thanks
The problem was that I had assumed PublicKeyFactory and PrivateKeyFactory were complimentary since they are in the same namespace. They are not!
To decode the private key, I needed the following alternative code:
var privKeyObj = Asn1Object.FromStream(privatekey);
var privStruct = new RsaPrivateKeyStructure((Asn1Sequence)privKeyObj);
// Conversion from BouncyCastle to .Net framework types
var rsaParameters = new RSAParameters();
rsaParameters.Modulus = privStruct.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = privStruct.PublicExponent.ToByteArrayUnsigned();
rsaParameters.D = privStruct.PrivateExponent.ToByteArrayUnsigned();
rsaParameters.P = privStruct.Prime1.ToByteArrayUnsigned();
rsaParameters.Q = privStruct.Prime2.ToByteArrayUnsigned();
rsaParameters.DP = privStruct.Exponent1.ToByteArrayUnsigned();
rsaParameters.DQ = privStruct.Exponent2.ToByteArrayUnsigned();
rsaParameters.InverseQ = privStruct.Coefficient.ToByteArrayUnsigned();
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(ciphertext), true));
A BIG thankyou to owlstead for their help.
I'm trying to build a portable class library that generates OAuth urls for other classes/applications to use. This class library using OAuth has to be a portable class library so it can work with different versions of a DropBox API I'm building.
Part of this class needs to generate an SHA1 hash to generate the oauth_signature with.
I'm aware that portable class library doesn't support System.Security.Cryptography, so is there anyway that this class can generate an SHA1 hash without that class?
I think the easiest way is to use the PCLCrypto nuget package. Then you can do:
private static string CalculateSha1Hash(string input)
{
// step 1, calculate MD5 hash from input
var hasher = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha1);
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hash = hasher.HashData(inputBytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
Well I needed this too recently and I found much easier to take SHA1 implementation from HashLib : http://hashlib.codeplex.com/
Mono implementation have some far-going dependencies (localization of exceptions, etc.), while from HashLib you need only to copy few files without any changes in them:
Converters.cs
Hash.cs
HashBuffer.cs
HashCryptoNotBuildIn.cs
HashResult.cs
IHash.cs
SHA0.cs
SHA1.cs
55 KB of code total, so nothing too heavy.
I have used this BouncyCastle Nuget package: https://www.nuget.org/packages/BouncyCastle-PCL/ and it works just fine for me (cross platforms Windows Store App, .Net Framework 4.5, Silverlight 5, Windows Phone 8, Xamarin.Android, Xamarin.iOS)
Use HMACSHA1 to generate signature like this:
public string GenerateSignature(string key, string signatureBase)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
HMACSHA1 hashAlgorithm = new HMACSHA1(keyBytes);
byte[] dataBuffer = Encoding.UTF8.GetBytes(signatureBase);
byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer);
return Convert.ToBase64String(hashBytes);
}
Mono provides a managed implementation of SHA1 for it's own mscorlib.dll (but it's not located in Mono.Security.dll like #CodeInChaos suggested).
It's open source, very well tested and meant to behave exactly like Microsoft implementation (e.g. it derives from SHA1, HashAlgorith... implements ICryptoTransform...) so it should be an easy drop-in replacement.
The SHA-1 Wikipedia article contains pseudocode that you could use as a guideline for your own implementation. But, as always with cryptographic functions, I strongly advise to use a tried and tested implementation.
Assuming you want a SHA-256 implementation, you can find one in BouncyCastle, which is available in source code form. The relevant class there is called Org.BouncyCastle.Crypto.Digests.Sha256Digest (here's its source).
You might want to check out the new .NET Standard library:
https://learn.microsoft.com/en-us/dotnet/articles/standard/library
It is portable, and System.Security.Cryptography is included.
/// <summary>
/// Compute hash for string encoded as UTF8
/// </summary>
/// <param name="input">String to be hashed.</param>
/// <returns>40-character hex string.</returns>
public static string GetSha1(string input)
{
using (var sha1 = System.Security.Cryptography.SHA1.Create())
{
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hash = sha1.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
}
You might also get some help (for creating a PCL project with .NET Standard Library) here:
https://xamarinhelp.com/dot-net-standard-pcl-xamarin-forms/
Here is an example using BouncyCastle
public static string ComputeSha1(string data)
{
var sha1Digest = new Org.BouncyCastle.Crypto.Digests.Sha1Digest();
var hash = new byte[sha1Digest.GetDigestSize()];
var dataBytes = Encoding.UTF8.GetBytes(data);
foreach (var b in dataBytes)
{
sha1Digest.Update(b);
}
sha1Digest.DoFinal(hash, 0);
return string.Join("", hash.Select(b => b.ToString("x2")).ToArray());
}
I wanted sign OAuth also, and am looking at PCL Crypto - this test shows creation of a HmacSha1 hash, and compares the result to the standard .NET Framework way.
[Test]
public void CreateHash_VersusComputeHash_ReturnsEquivalent()
{
// USING TRADITIONAL .NET:
var key = new byte[32];
var contentBytes = Encoding.UTF8.GetBytes("some kind of content to hash");
new RNGCryptoServiceProvider().GetBytes(key);
var alg = new HMACSHA1(key); // Bouncy castle usage does not differ from this
var result = alg.ComputeHash(contentBytes);
// USING PCL CRYPTO:
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha1);
byte[] mac;
using (var hasher = algorithm.CreateHash(key))
{
hasher.Append(contentBytes);
mac = hasher.GetValueAndReset();
}
// Assert results:
Assert.AreEqual(result.Length, mac.Length);
for (var i = 0; i < result.Length; i++)
{
Assert.AreEqual(result[i], mac[i]);
}
}
This worked for me when I had to achieve the same outcome. You can do this with SHA512 and others too.
using System.Security.Cryptography;
public static string HashSHA1(this string value)
{
using (var sha = SHA1.Create())
{
return Convert.ToBase64String(sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(value)));
}
}
Code cited from: https://xamarinhelp.com/cryptography-in-xamarin-forms/
I am trying to encrypt data with a password and store it inside a ASN.1 encoded CMS message (using C# and BouncyCastle 1.4)
The code I have seems to have two problems:
the data does not seem to be signed with a HMAC, so when I tamper with the encodedData (by enabling the commented out line), the decryption still succeeds.
when I decrypt the data I have tampered with, I get beck corrupted plain text. However only a two blocks of plaintext data are corrupted. This seems to suggest that the encryption does not actually use CBC mode.
(edit: disregard the second point, this is exactly how CBC is supposed to work)
This is what I am testing with:
public void TestMethod1()
{
byte[] data = new byte[1024]; // plaintext: a list of zeroes
CmsEnvelopedDataGenerator generator = new CmsEnvelopedDataGenerator();
CmsPbeKey encryptionKey = new Pkcs5Scheme2PbeKey("foo", new byte[] { 1, 2, 3 }, 2048);
generator.AddPasswordRecipient(encryptionKey, CmsEnvelopedDataGenerator.Aes256Cbc);
CmsProcessableByteArray cmsByteArray = new CmsProcessableByteArray(data);
CmsEnvelopedData envelopeData = generator.Generate(cmsByteArray, CmsEnvelopedDataGenerator.Aes256Cbc);
byte[] encodedData = envelopeData.GetEncoded();
// encodedData[500] = 10; // tamper with the data
RecipientID recipientID = new RecipientID();
CmsEnvelopedData decodedEnvelopeData = new CmsEnvelopedData(encodedData);
RecipientInformation recipient = decodedEnvelopeData.GetRecipientInfos().GetFirstRecipient(recipientID);
byte[] data2 = recipient.GetContent(encryptionKey);
CollectionAssert.AreEqual(data, data2);
}
What am I doing wrong? What would be the correct way to write this?
To add an HMAC to a CMS message, you would have to use a AuthenticatedData-structure.
I am not especially familiar with Bouncy Castle, but from a cursory look at the API, I would say that it does not support AuthenticatedData. In fact, it looks like it only supports SignedData for authentication.
So your options seems to be:
Use another library (or write your own code) to handle the AuthenticatedData-structure.
Calculate the HMAC and provide it in a non-standard way (in a proprietary Attribute or out-of-band).
Use SignedData with an RSA key pair instead.