Get SHA1 sign of string with DSA private key from PEM file - c#

I have a PEM file which includes my DSA private Key and i need to sign a string with this private key to send it to my partner API. (code attached)
For some reason i'm always getting Signature Invalid form my partner API. which means the sign is not in a good format.
My partner offers me to use bouncycastle for c# but i couldn't find any examples of how to sign the string with DSA from external PEM file.
Can i get any examples here?
Thanks.
I've tried to sign the string with a method i wrote:
public string ComputeSignature(string method, string path,string client_id, string timestamp, string username)
{
var data = String.Concat(method, path, client_id, timestamp);
if (!string.IsNullOrEmpty(username))
{
data = data + username;
}
string sig;
var encoding = new ASCIIEncoding();
byte[] dataInBytes = encoding.GetBytes(data);
using (System.Security.Cryptography.HMAC Hmac = new HMACSHA1())
{
Hmac.Key = encoding.GetBytes(privateKeyClean);
byte[] hash = Hmac.ComputeHash(dataInBytes, 0, dataInBytes.Length);
sig = Convert.ToBase64String(hash, 0, hash.Length);
}
return sig;
}

I solved this issue using bouncycastle:
private static string ComputeSignature()
{
AsymmetricCipherKeyPair asymmetricCipherKeyPair;
using (TextReader textReader = new StreamReader("myprivatekey.pem"))
{
asymmetricCipherKeyPair = (AsymmetricCipherKeyPair)new PemReader(textReader).ReadObject();
}
DsaPrivateKeyParameters parameters = (DsaPrivateKeyParameters)asymmetricCipherKeyPair.Private;
string text = TEXTTOENC;
if (!string.IsNullOrEmpty(userName))
{
text += userName;
}
Console.Out.WriteLine("Data: {0}", text);
byte[] bytes = Encoding.ASCII.GetBytes(text);
DsaDigestSigner dsaDigestSigner = new DsaDigestSigner(new DsaSigner(), new Sha1Digest());
dsaDigestSigner.Init(true, parameters);
dsaDigestSigner.BlockUpdate(bytes, 0, bytes.Length);
byte[] inArray = dsaDigestSigner.GenerateSignature();
string text2 = Convert.ToBase64String(inArray);
Console.WriteLine("Signature: {0}", text2);
return text2;
}

The reason that the DSA signature fails here is you aren't doing DSA at all. You're doing HMAC-SHA-1 using the contents of a key file as the HMAC key.
Read the DSA key parameters from the file.
.NET has no intrinsic support for reading PEM key files. But your title mentions BouncyCastle, so you can probably adapt the accepted answer to How to read a PEM RSA private key from .NET to DSA (using the PemReader).
Alternatively, you could use OpenSSL to make a self-signed certificate based off of this key, and put the cert+key into a PFX. .NET can load PFXes just fine.
Populate a DSA object.
If you go the PFX route, cert.GetDSAPrivateKey() will do the right thing (.NET 4.6.2). On versions older than 4.6.2 you can use cert.PrivateKey as DSA, which will only work for FIPS 186-2-compatible DSA (DSA was upgraded in FIPS 186-3).
If you're using BouncyCastle to read the PEM key parameters you can then stick with BouncyCastle or do something like using (DSA dsa = DSA.Create()) { dsa.ImportParameters(parametersFromTheFile); /* other stuff here */ }. (DSA.Create() will give an object limited to FIPS-186-2 on .NET Framework, but it can do FIPS-186-3 on .NET Core).
Sign the data
FIPS-186-2 only allows SHA-1 as the hash algorithm, FIPS-186-3 allows the SHA-2 family (SHA256, SHA384, SHA512). We'll assume that you're using FIPS-186-2/SHA-1 (if not, the substitutions required are hopefully obvious).
BouncyCastle: However they compute signatures. (Sorry, I'm not familiar with their APIs)
.NET 4.6.1 or older:
using (SHA1 hash = SHA1.Create())
{
signature = dsa.CreateSignature(hash.ComputeHash(dataInBytes));
}
.NET 4.6.2 or newer:
signature = dsa.SignData(dataInBytes, HashAlgorithmName.SHA1);
Soapbox
Then, once that's all said and done (or, perhaps, before): Ask yourself "why am I using DSA?". In FIPS 186-2 (and prior) DSA is limited to a 1024-bit key size and the SHA-1 hash. NIST SP800-57 classifies SHA-1 and DSA-1024 both as having 80 bits of security (Tables 2 and 3). NIST classified 80 bits of security as "Deprecated" for 2011-2013 and "Disallowed" for 2014 and beyond (Table 4). Modern usage of DSA (for entities subject to NIST recommendations) requires FIPS-186-3 support.
ECDSA gets ~keysize/2 bits of security; so the lowest (commonly supported) ECDSA keysize (keys based on NIST P-256/secp256r1) gets 128 bits of security, which NIST rates as good for 2031+.
RSA is also a better choice than DSA, because it has much better breadth of support for signatures still considered secure by NIST.
Though, if you're conforming to a protocol I guess there aren't a lot of options.

Related

How can I create an X509 Store key programmatically for use in encryption?

I have a service that needs to encrypt and decrypt a connection text. I don't want to hard-code the password, so I've decided to use a certificate, but I want to be able to add it to the store the first time the settings UI (a separate configuration app) is opened.
I have code that checks for an existing certificate, and adds a new one if the certificate is not found.
private static void GenerateNewSecurityCertificate()
{
var ecdsa = ECDsa.Create(); // generate asymmetric key pair
var request = new CertificateRequest($"cn={CertificateName}", ecdsa, HashAlgorithmName.SHA512);
var cert = request.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(50));
File.WriteAllBytes("c:\\temp\\EncryptionCert.pfx", cert.Export(X509ContentType.Pfx, _certificatePassword));
}
private static void AddCertificateToStore()
{
using (var store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
using (var cert = new X509Certificate2("c:\\temp\\EncryptionCert.pfx", _certificatePassword,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet))
{
store.Add(cert);
}
}
}
I run into the issue when I try to retrieve the public key
using (var store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
using (var cert = store.Certificates.Find(X509FindType.FindBySubjectName, CertificateName, false)[0])
{
var publicKey = cert.GetRSAPublicKey();
encryptedKey = publicKey.Encrypt(aesKey.Key, RSAEncryptionPadding.OaepSHA512);
}
}
I get a null returned when I call cert.GetRSAPublicKey(). Does anyone see what I might be doing wrong?
Update:
I updated GenerateNewSecurityCertificate to read
var rsa = RSA.Create();
var request = new CertificateRequest($"cn={CertificateName}", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
I am able to get the public key now, but get an Exception reading
"The parameter is incorrect"
at System.Security.Cryptography.NCryptNative.EncryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptEncryptor`1 encryptor)
on my call to publicKey.Encrypt.
Since you apparently encrypt an AES key, it can be assumed that the (maximum) length of your data is 32 bytes (AES-256). The error message can be reproduced, e.g. if the message is too large for the key used.
For RSA, the length of a message must not exceed the key length. In fact, it is even smaller, because a part of the allowed length is reserved for padding. For OAEP this part depends on the digest used. For SHA512 the reserved part (130 bytes) is so large that a 1024 bits (128 bytes) key is not sufficient, with a 2048 bits (256 bytes) key the message may still be 126 bytes large, see here.
You create an RSA key with
var rsa = RSA.Create();
which uses a default value for the key length, s. RSA.Create(). This default value also depends on the .NET version. Under .NET Framework 4.8 a 1024 bits key is created on my machine (nowadays too short), under .NET Core 3.1 a 2048 bits key is created. You can check the key length with rsa.KeySize.
Probably a key that is too short for your purposes is generated in your environment. It is always better not to rely on defaults, but to specify the values explicitly. The Create method has an overload that makes this possible, see RSA.Create(Int32). You should use this overload and create a key of (at least 2048 bits).
Alternatively, the bug could theoretically be eliminated with another digest (e.g. SHA256). Independent of this, a key with 1024 bits should not be used nowadays (12/2020) for security reasons, the length should be at least 2048 bits (see here).

Encrypting Unity c# callout to node js server

I have a node server and and passing up usernames and passwords from unity. here is what I have so far. I am still trying to learn and understand encryption and there are so many types and Im just confused. The code below will successfully encrypt and decrypt the string. Is this code a good code to use for something like this or is there a better alternative? What type of encryption is this actually using? How would I decrypt this on node js? Any additional example, links, or comments would be much appreciated. Thanks!
public string encrypt(string toEncrypt) {
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "ThisIsAKey"; // This is the key used to encrypt and decrypt can be anything.
var provider = new RSACryptoServiceProvider(cspParams);
byte[] tempencryptedBytes = provider.Encrypt(System.Text.Encoding.UTF8.GetBytes(toEncrypt), true);
string encrypted = Convert.ToBase64String(tempencryptedBytes); // convert to base64string for storage
Debug.Log("encrypted: " + encrypted);
// Get the value stored in RegString and decrypt it using the key.
return encrypted;
}
public string decrypt(string toDecrypt) {
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "ThisIsAKey"; // This is the key used to encrypt and decrypt can be anything.
var provider = new RSACryptoServiceProvider(cspParams);
string decrypted = System.Text.Encoding.UTF7.GetString(provider.Decrypt(Convert.FromBase64String(toDecrypt), true));
Debug.Log("decrypted: " + decrypted);
return decrypted;
}
EDIT: SHA256 code that i used added here. It doesnt output the correct string value.
SHA256 sha256 = SHA256Managed.Create();
byte[] bytes = System.Text.Encoding.UTF8.GetBytes("randy");
byte[] hash = sha256.ComputeHash(bytes);
string result = "";
for (int i = 0; i < hash.Length; i++) {
result += String.Format("{0:x2}", i);
}
Debug.Log("hash: " + result);
string result2 = Convert.ToBase64String(hash);
Debug.Log("hash: " + result2);
If something is good to be used depends on the context.
If you need to pass a username / password combination then RSA encryption may indeed be used, preferably in addition to TLS transport security for the connection. If you just need to verify a username or password then you may need a password hash (or PBKDF) instead. The .NET version of PBKDF2 can be found in this badly named class.
Even if all the cryptographic algorithms are secure then your system may still not be secure. For instance, if you cannot trust the public key that you are encrypting with then you may be encrypting with a public key of an attacker.
So your code is using this specific encrypt call using a boolean to select the encryption algorithm. As the boolean is set to true that means that RSA with OAEP is being used, using the default SHA-1 hash function internally (which is secure for OAEP encryption, even if SHA-1 isn't). It's better to use the newer call where you can specify the padding without the boolean anti-pattern. In that case you can also specify the internal hash function to be used.
RSA with OAEP is specified in PKCS#1 v2.2, which is specified in turn in RFC 8017. This will even specify the byte order to be used (RSA operates on numbers in the end, which can be encoded to bytes in different ways). As long as you use a compliant library in any runtime and know how to encode / decode the plaintext and ciphertext (when using text) then you should be able to decrypt using any runtime that implements RSA with OAEP, if you have the matching private key of course.
As a general rule, passwords shouldn't be decryptable. You should hash the password (using something like SHA256), then compare that to a stored hash in your Node.js code. Never store or transfer passwords plaintext or in a method that can be converted back to the original password.
In C#, hashing will look something like:
string toHash = "PasswordToBehashed";
SHA256 sha = new SHA256();
byte[] tempencryptedBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(toHash));
For reference, see the SHA256 class and an example using MD5 instead of SHA256.

Using CngKey to Generate RSA key pair in PEM (DKIM compatible) using C#.... similar to "openssl rsa"

Is it possible to generate an RSA key pair, export that into ASN1 format compatible with DKIM's PEM-like format, using only C#?
I'd like to reduce my dependencies on 3rd parties, but here are some that I have found
Bouncy Castle
https://stackoverflow.com/a/251757
Cryptography Application Block
http://msdn.microsoft.com/en-us/library/dd203099.aspx
https://stackoverflow.com/a/243787
Win32 PFXImportCertStore
http://msdn.microsoft.com/en-us/library/aa387314(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/aa387313(v=vs.85)
Import PEM
This code only imports PEM data, but is different from OpenSSL in that it fixes an issue with .NET 4.0 and leading zeros in the key http://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back
Microsoft's CLR Security enhancements
http://clrsecurity.codeplex.com/
Microsoft CNG
http://msdn.microsoft.com/en-us/magazine/cc163389.aspx
Here is code for the Microsoft CNG provider with the .NET dll on codeplex (above)... however I don't know how to export and import both the public and private keys in DKIM compatible ASN1 format.
byte[] pkcs8PrivateKey = null;
byte[] signedData = null;
CngKey key = CngKey.Create(CngAlgorithm2.Rsa);
byte[] exportedPrivateBytes = key.Export(CngKeyBlobFormat.GenericPrivateBlob);
string exportedPrivateString= Encoding.UTF8.GetString(exportedPrivateBytes);
pkcs8PrivateKey = Encoding.UTF8.GetBytes(exportedPrivateString);
using (CngKey signingKey = CngKey.Import(pkcs8PrivateKey, CngKeyBlobFormat.Pkcs8PrivateBlob))
{
using (RSACng rsa = new RSACng(signingKey))
{
rsa.SignatureHashAlgorithm = CngAlgorithm.Sha1;
signedData = rsa.SignData(dataToSign);
}
}
Question
Are there any direct examples of using Microsoft's libraries (Win32, PFX, or CLR on Codeplex) that illustrate how to create a key pair and export / import those values in PEM format?
So you just need a pkcs8 of the key then.
CngKeyCreationParameters ckcParams = new CngKeyCreationParameters()
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
KeyCreationOptions = CngKeyCreationOptions.None,
KeyUsage = CngKeyUsages.AllUsages,
};
ckcParams.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.None));
myCngKey = CngKey.Create(CngAlgorithm.Rsa, null, ckcParams);
byte[] privatePlainTextBlob = myCngKey.Export(CngKeyBlobFormat.Pkcs8PrivateBlob);
Console.WriteLine(Convert.ToBase64String(privatePlainTextBlob));
}
Now your key pair is contained in the PKCS#8 ASN.1 encoded string.

How to use c# to decrypt an MD5 hash that is encrypted using RSA1024

I trying to verify the integrity of a file at work and an having a hard time of it. I'm not very well versed with encryption and hashing, so bear with me.
I have some files that have an MD5 hash located at the end of them. I have written code to grab the bytes that I think are the hash and they seen to be uniformly 128 bytes long. In the file, just before the hash, is the keyword "RSA1024", which I have taken to mean the hash is encrypted using RSA 1024.
I have what I know is the RSA key in a file, and have read out the bytes (always 258 bytes long). I have seen many tutorials which use FromXmlString() to pull in the key, but this RSA key was not generated using the .net framework, and is not in an XML format.
I have written the following method to decrypt the hash data using the key, and it throws this error when executing ImportCspBlob() - System.Security.Cryptography.CryptographicException: Bad Version of provider.
Any ideas?
public byte[] DecryptRSA(byte[] encryptedData, byte[] keyData)
{
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseExistingKey;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
rsaProvider.ImportCspBlob(keyData);
byte[] decryptedData = rsaProvider.Decrypt(encryptedData, false);
return decryptedData;
}
Basic Algorithm
It may sound strange to want to "decrypt an MD5 hash", and especially when one says that they want to "decrypt it with a public key". But that is how digital signatures work. With RSA you can:
encrypt with private key
decrypt with the public key
The message digest is encrypted with the private key, and can then only be decrypted with the public key. That way you know that only the person with the private key could have signed the message.
Your key is most likely not a CSP-type key (it is most likely DER encoded). You can decrypt it using Bouncy Castle with the DER key like this:
RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(key);
byte[] rv = null;
RsaEngine eng = new RsaEngine();
eng.Init(false, privateKey);
int size = eng.GetOutputBlockSize();
rv = eng.ProcessBlock(cipher, 0, cipher.Length);
EDIT: to addressing GregS scenario that it may be a signature verify operation
If you are trying to verify a signature, you would need a certificate used to verify a message, the original message text, and the existing message signature to compare against.
What you do is pass in the original message text (minus the signature), the bytes of the message signature, and the path to the certificate you will use to verify the passed in signature.
Then, you will hash the original message and compare the result against the passed in signature.
Here is some code to illustrate:
private bool VerifySignature(string messageText, byte[] messageSignature, string certificatePath)
{
// Load the certificate from a file
X509Certificate2 cert = new X509Certificate2(certificatePath);
// Get public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
// Next, hash the messageText
SHA1Managed sha1 = new SHA1Managed();
byte[] messageBytes = Encoding.Unicode.GetBytes(messageText);
byte[] hash = sha1.ComputeHash(messageBytes);
// Verify the signature with the hash
return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), messageSignature);
}
MD5 is one-way hash. But you might check out hashing algorithm. There are some ways to break this hash, just do some research ;)

Sign data with MD5WithRSA from .Pem/.Pkcs8 keyfile in C#

I've got the following code sample in Java, and I need to re-enact it in C#:
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pkcs8PrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privKey = keyFactory.generatePrivate(privKeySpec);
Signature sign = Signature.getInstance("MD5withRSA");
sign.initSign(privKey);
sign.update(data);
byte[] signature = sign.sign();
Is it possible with the standard .Net Crypto API, or should I use BouncyCastle?
Thanks,
b.
Another way is to use CNG (Cryptography Next Generation), along with the Security.Cryptography DLL from CodePlex
Then you can write:
byte[] dataToSign = Encoding.UTF8.GetBytes("Data to sign");
using (CngKey signingKey = CngKey.Import(pkcs8PrivateKey, CngKeyBlobFormat.Pkcs8PrivateBlob))
using (RSACng rsa = new RSACng(signingKey))
{
rsa.SignatureHashAlgorithm = CngAlgorithm.MD5;
return rsa.SignData(dataToSign);
}
Updated thanks to Simon Mourier: with .Net 4.6, you no longer need a separate library
I am running into a very similar problem trying to create a native C# tool for packing Chrome extensions (using SHA1, not MD5, but that's not a big difference). I believe I have tried literally every possible solution for .Net: System.Security.Cryptography, BouncyCastle, OpenSSL.Net and Chilkat RSA.
The best solution is probably Chilkat; their interface is the cleanest and most straightforward, it's well-supported and well-documented, and there are a million examples. For instance, here's some code using their library that does something very close to what you want: http://www.example-code.com/csharp/rsa_signPkcs8.asp. However, it's not free (though $150 is not unreasonable, seeing as I have burned 2 days trying to figure this out, and I make a bit more than $75 a day!).
As a free alternative, JavaScience offers up a number of crypto utilities in source form for multiple languages (including C#/.Net) at http://www.jensign.com/JavaScience/cryptoutils/index.html. The one that's most salient to what you are trying to do is opensslkey (http://www.jensign.com/opensslkey/index.html), which will let you generate a RSACryptoServiceProvider from a .pem file. You can then use that provider to sign your code:
string pemContents = new StreamReader("pkcs8privatekey.pem").ReadToEnd();
var der = opensslkey.DecodePkcs8PrivateKey(pemContents);
RSACryptoServiceProvider rsa = opensslkey.DecodePrivateKeyInfo(der);
signature = rsa.SignData(data, new MD5CryptoServiceProvider());
You can use this code . At the first you should download "BouncyCastle.Crypto.dll" from http://www.bouncycastle.org/csharp/ .
/// <summary>
/// MD5withRSA Signing
/// https://www.vrast.cn
/// keyle_xiao 2017.1.12
/// </summary>
public class MD5withRSASigning
{
public Encoding encoding = Encoding.UTF8;
public string SignerSymbol = "MD5withRSA";
public MD5withRSASigning() { }
public MD5withRSASigning(Encoding e, string s)
{
encoding = e;
SignerSymbol = s;
}
private AsymmetricKeyParameter CreateKEY(bool isPrivate, string key)
{
byte[] keyInfoByte = Convert.FromBase64String(key);
if (isPrivate)
return PrivateKeyFactory.CreateKey(keyInfoByte);
else
return PublicKeyFactory.CreateKey(keyInfoByte);
}
public string Sign(string content, string privatekey)
{
ISigner sig = SignerUtilities.GetSigner(SignerSymbol);
sig.Init(true, CreateKEY(true, privatekey));
var bytes = encoding.GetBytes(content);
sig.BlockUpdate(bytes, 0, bytes.Length);
byte[] signature = sig.GenerateSignature();
/* Base 64 encode the sig so its 8-bit clean */
var signedString = Convert.ToBase64String(signature);
return signedString;
}
public bool Verify(string content, string signData, string publickey)
{
ISigner signer = SignerUtilities.GetSigner(SignerSymbol);
signer.Init(false, CreateKEY(false, publickey));
var expectedSig = Convert.FromBase64String(signData);
/* Get the bytes to be signed from the string */
var msgBytes = encoding.GetBytes(content);
/* Calculate the signature and see if it matches */
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(expectedSig);
}
}
This SO question answers the PKCS#8 part of your code. The rest of the .NET RSA classes are a bizarre jumble of partially overlapping classes that are very difficult to fathom. It certainly appears that signature support is in either of the RSACryptoServiceProvider and/or RSAPKCS1SignatureFormatter classes.
Disclaimer: I know Java and cryptography, but my knowledge of C# and .NET is very limited. I am writing here only under the influence of my Google-fu skills.
Assuming that you could decode a PKCS#8-encoded RSA private key, then, from what I read on MSDN, the rest of the code should look like this:
byte[] hv = MD5.Create().ComputeHash(data);
RSACryptoServiceProvider rsp = new RSACryptoServiceProvider();
RSAParameters rsp = new RSAParameters();
// here fill rsp fields by decoding pkcs8PrivateKey
rsp.ImportParameters(key);
RSAPKCS1SignatureFormatter rf = new RSAPKCS1SignatureFormatter(rsp);
rf.SetHashAlgorithm("MD5");
byte[] signature = rf.CreateSignature(hv);
The relevant classes are in the System.Security.Cryptography namespace.
As for the PKCS#8 key blob decoding (i.e. filling in the rsp fields), I found this page which describes a command-line utility in C# which can perform that job. The source code is provided and is a single C# file. From what I read in it, that code decodes the PKCS#8 file "manually"; indirectly, this should mean that raw .NET (2.0) does not have facilities for PKCS#8 key file decoding (otherwise the author of that tool would not have went to the trouble of implementing such decoding). For your task at hand, you could scavenge from that source file the parts that you need, skipping anything about PEM and symmetric encryption; your entry point would be the DecodePrivateKeyInfo() function, which apparently expects a DER-encoded unencrypted PKCS#8 file, just like Java's PKCS8EncodedKeySpec.

Categories

Resources