I'm running this code successfully on my Windows machine (Win 10 x64, running dotnet 4.7.2). It generates an EC keypair ("P-256"), hashes the plaintext with SHA-256, signs the hash with the ec private key and verifies the signature against the hashed plaintext with the ec public key.
I'm getting this output so everything works fine:
EC signature curve secp256r1 / P-256 string
dataToSign: The quick brown fox jumps over the lazy dog
* * * sign the plaintext with the EC private key * * *
EC keysize: 256
signature (Base64): cwLBRSt1vtO33tHWcTdx1OTu9lBFXHEJgvdRyDUynLLE5eMakUZUAKLwaJvYoS7NBylx2Zz0+G6dvgJ6xv5qNA==
* * *verify the signature against hash of plaintext with the EC public key * * *
signature verified: True
Now I'm trying to find any online compiler that is been able to run the code. My favorite compiler
(https://repl.it/, Mono C# compiler version 6.8.0.123, full code: https://repl.it/#javacrypto/EcSignatureFull#main.cs) is running into this error:
Unhandled Exception:
System.NotImplementedException: The method or operation is not implemented.
at EcSignatureString.Main () [0x00036] in <13e2ad358a924efc874a89efad35ffe7>:0
[ERROR] FATAL UNHANDLED EXCEPTION: System.NotImplementedException: The method or operation is not implemented.
at EcSignatureString.Main () [0x00036] in <13e2ad358a924efc874a89efad35ffe7>:0
exit status 1
Using another platform (https://dotnetfiddle.net/, Compiler .net 5, full code: https://dotnetfiddle.net/lSPpjz) is giving this similar error:
Unhandled exception. System.PlatformNotSupportedException: Windows Cryptography Next Generation (CNG) is not supported on this platform.
at System.Security.Cryptography.ECDsaCng..ctor(Int32 keySize)
at EcSignatureString.Main()
Command terminated by signal 6
So my question: is there any online compiler available that is been able to run the code?
I assume my question might be a slice off-topic for SO - in this case - is there any other stackexchange-site that would be a better place for my question?
Warning: the following code has no exception handling and is for educational purpose only:
using System;
using System.Security.Cryptography;
class EcSignatureString {
static void Main() {
Console.WriteLine("EC signature curve secp256r1 / P-256 string");
string dataToSignString = "The quick brown fox jumps over the lazy dog";
byte[] dataToSign = System.Text.Encoding.UTF8.GetBytes(dataToSignString);
Console.WriteLine("dataToSign: " + dataToSignString);
try {
Console.WriteLine("\n* * * sign the plaintext with the EC private key * * *");
ECDsaCng ecDsaKeypair = new ECDsaCng(256);
Console.WriteLine("EC keysize: " + ecDsaKeypair.KeySize);
byte[] hashedData = null;
byte[] signature = null;
// create new instance of SHA256 hash algorithm to compute hash
HashAlgorithm hashAlgo = new SHA256Managed();
hashedData = hashAlgo.ComputeHash(dataToSign);
// sign Data using private key
signature = ecDsaKeypair.SignHash(hashedData);
string signatureBase64 = Convert.ToBase64String(signature);
Console.WriteLine("signature (Base64): " + signatureBase64);
// get public key from private key
string ecDsaPublicKeyParametersXml = ecDsaKeypair.ToXmlString(ECKeyXmlFormat.Rfc4050);
// verify
Console.WriteLine("\n* * *verify the signature against hash of plaintext with the EC public key * * *");
ECDsaCng ecDsaVerify = new ECDsaCng();
bool signatureVerified = false;
ecDsaVerify.FromXmlString(ecDsaPublicKeyParametersXml, ECKeyXmlFormat.Rfc4050);
signatureVerified = ecDsaVerify.VerifyHash(hashedData, signature);
Console.WriteLine("signature verified: " + signatureVerified);
}
catch(ArgumentNullException) {
Console.WriteLine("The data was not signed or verified");
}
}
}
Microsoft has decided that encryption and hashing must be fully delegated to the OS (in .NET Framework it was half and half), so now .NET 5 (and .NET Core) has multiple backends for encryption (for example for ECDsa it has ECDsaCng that uses Windows services and ECDsaOpenSsl for Linux/MacOs that uses OpenSsl (see MSDN)
Now... the solution for your problem is to use the ECDsa class and let it select its backend. There are some problems with it. You can't easily export the keys to xml format, nor you can easily export them to PEM format. You can easily export them to a byte[], and you can easily import them from PEM format. This isn't really a big problem, because rarely you'll need to generate keys, and normally your program receives its keys from an external source, or if it generates them itself, it can save them to binary format to reuse them later.
var dataToSignString = "Hello world!";
var dataToSign = Encoding.UTF8.GetBytes(dataToSignString);
Console.WriteLine("dataToSign: " + dataToSignString);
try
{
Console.WriteLine("\n* * * sign the plaintext with the EC private key * * *");
var ecDsaKeypair = ECDsa.Create(ECCurve.NamedCurves.nistP256);
// Normally here:
//ecDsaKeypair.ImportFromPem()
Console.WriteLine("EC keysize: " + ecDsaKeypair.KeySize);
byte[] hashedData = null;
byte[] signature = null;
// create new instance of SHA256 hash algorithm to compute hash
HashAlgorithm hashAlgo = new SHA256Managed();
hashedData = hashAlgo.ComputeHash(dataToSign);
// sign Data using private key
signature = ecDsaKeypair.SignHash(hashedData);
string signatureBase64 = Convert.ToBase64String(signature);
Console.WriteLine("signature (Base64): " + signatureBase64);
// get public key from private key
string ecDsaPublicKeyParameters = Convert.ToBase64String(ecDsaKeypair.ExportSubjectPublicKeyInfo());
// verify
Console.WriteLine("\n* * *verify the signature against hash of plaintext with the EC public key * * *");
var ecDsaVerify = ECDsa.Create(ECCurve.NamedCurves.nistP256);
bool signatureVerified = false;
// Normally here:
//ecDsaKeypair.ImportFromPem()
var publicKey = Convert.FromBase64String(ecDsaPublicKeyParameters);
ecDsaVerify.ImportSubjectPublicKeyInfo(publicKey, out _);
signatureVerified = ecDsaVerify.VerifyHash(hashedData, signature);
Console.WriteLine("signature verified: " + signatureVerified);
}
catch (ArgumentNullException)
{
Console.WriteLine("The data was not signed or verified");
}
About the From/ToXmlFormat, the current comment on them on the github of .NET Core is:
// There is currently not a standard XML format for ECC keys, so we will not implement the default
// To/FromXmlString so that we're not tied to one format when a standard one does exist. Instead we'll
// use an overload which allows the user to specify the format they'd like to serialize into.
Mmmh from some tests done, exporting in PEM format seems to be quite easy:
public static IEnumerable<string> Split(string str, int chunkSize)
{
for (int i = 0; i < str.Length; i += chunkSize)
{
yield return str.Substring(i, Math.Min(chunkSize, str.Length - i));
}
}
and then
string b64privateKey = Convert.ToBase64String(ecDsaKeypair.ExportPkcs8PrivateKey());
b64privateKey = string.Join("\r\n", Split(b64privateKey, 64));
string pemPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" + b64privateKey + "\r\n-----END PRIVATE KEY-----";
or
string b64publicKey = Convert.ToBase64String(ecDsaKeypair.ExportSubjectPublicKeyInfo());
b64publicKey = string.Join("\r\n", Split(b64publicKey, 64));
string pemPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" + b64publicKey + "\r\n-----END PUBLIC KEY-----";
(note that I had to split the string manually in blocks of 64 characters, that is the exact number given in the rfc7468, because Convert.ToBase64String() supports only the 76 line length)
Related
I am learning about Cryptography in .NET and I wrote the following function as a test:
byte[] foo(byte[] input, string keyContainerName)
{
CngKey key = CngKey.Open(keyContainerName);
RSACng rsa = new RSACng(key);
rsa.KeySize = 2048;
byte[] v = rsa.Encrypt(input, RSAEncryptionPadding.OaepSHA512);
CngKey keyb = CngKey.Open(keyContainerName);
RSACng rsab = new RSACng(keyb);
rsab.KeySize = 2048;
return rsab.Decrypt(v, RSAEncryptionPadding.OaepSHA512);
}
When I try executing it, rsab.Decrypt() throws a Cryptographic exception with the message: "The parameter is incorrect.".
Why is this happening? Where did I go wrong?
P.S. I previously created a key pair in the KSP with CngKey.Create(). foo is called with keyContainerName beeing the keyName passed to CngKey.Create().
If you want to create an app that does symmetric and asymmetric encryption and decryption, You can try integrating ExpressSecurity library via NuGet
More info: https://github.com/sangeethnandakumar/Express-Security-Library
AES - Symetric Encryption (For files)
var password = "sangeeth123";
var inputPath = "C:\sample.txt";
var outputPath = "C:\sample.txt.aes";
//AES Encription
AESEncription.AES_Encrypt(inputPath, password);
//AES Description
AESEncription.AES_Decrypt(outputPath, password);
RSA - Asymmetric Encryption (For strings and text)
//Generate Keys
var publicKeyPath = "C:\public_key.rsa";
var privateKeyPath = "C:\private_key.rsa";
RSAEncription.MakeKey(publicKeyPath, privateKeyPath);
var input = "sangeeth"
//RSA Encription
var ciphertext = RSAEncription.EncryptString(input, publicKeyPath);
//RSA Description
input = RSAEncription.DecryptString(ciphertext, privateKeyPath);
I have a X509Certificate2 variable and I'm trying to cast the private key of the variable to a RSACryptoServiceProvider
RSACryptoServiceProvider pkey = (RSACryptoServiceProvider)cert.PrivateKey;
However I get this exception.
System.InvalidCastException: 'Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.'
It's weird that this happens because other answers in SO suggested the same procedure as mine but I get an exception. Any solutions to this?
So after a few tries and discussions in the comments I came up with the following solution.
RSA rsa = (RSA)cert.PrivateKey;
(cert.PrivateKey as RSACng).Key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
RSAParameters RSAParameters = rsa.ExportParameters(true);
AsymmetricCipherKeyPair keypair = DotNetUtilities.GetRsaKeyPair(RSAParameters);
The problem was that the variable rsa wasn't exportable. To change this I set a new CngProperty for the export policy. Works perfectly now
Just wanted to note that there's also an extension method that can be used:
using System.Security.Cryptography.X509Certificates;
...
//certificate is a X509Certificate2
using (var rsa = certificate.GetRSAPrivateKey())
{
//the var rsa is an RSA object
//...
}
In my case the same problem was happening when trying to convert local store certificate to RSACryptoServiceProvider as below:
RSACryptoServiceProvider encryptProvider =
certificate.PrivateKey as RSACryptoServiceProvider;
Issue fixed by using RSA instead of RSACryptoServiceProvider.
Putting instructions here in case if someone will be curious how to do that )).
To store some certificate into your machine open Visual Studio Developer Command and type following:
makecert -n "CN=JohnDoe" -sr currentuser -ss someCertStore
...where you can specify and values instead of "JohnDoe" and "demoCertStore".
Now you can use the below code to access certificates from the local certificate store:
public class Program
{
static void DumpBytes(string title, byte[] bytes)
{
Console.Write(title);
foreach (byte b in bytes)
{
Console.Write("{0:X} ", b);
}
Console.WriteLine();
}
static void Main(string[] args)
{
// This will convert our input string into bytes and back
var converter = new ASCIIEncoding();
// Get a crypto provider out of the certificate store
// should be wrapped in using for production code
var store = new X509Store("someCertStore", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
// should be wrapped in using for production code
X509Certificate2 certificate = store.Certificates[0];
RSA rsa = (RSA)certificate.PrivateKey;
(certificate.PrivateKey as RSACng)?.Key.SetProperty(
new CngProperty(
"Export Policy",
BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),
CngPropertyOptions.Persist));
string messageToSign = "This is the message I want to sign";
Console.WriteLine("Message: {0}", messageToSign);
byte[] messageToSignBytes = converter.GetBytes(messageToSign);
// need to calculate a hash for this message - this will go into the
// signature and be used to verify the message
// Create an implementation of the hashing algorithm we are going to us
// should be wrapped in using for production code
DumpBytes("Message to sign in bytes: ", messageToSignBytes);
HashAlgorithm hasher = new SHA1Managed();
// Use the hasher to hash the message
byte[] hash = hasher.ComputeHash(messageToSignBytes);
DumpBytes("Hash for message: ", hash);
// Now sign the hash to create a signature
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
DumpBytes("Signature: ", messageToSignBytes);
// Now use the signature to perform a successful validation of the mess
bool validSignature = rsa.VerifyHash(hash: hash,
signature: signature,
hashAlgorithm: HashAlgorithmName.SHA1,
padding: RSASignaturePadding.Pss);
Console.WriteLine("Correct signature validated OK: {0}", validSignature);
// Change one byte of the signature
signature[0] = 99;
// Now try the using the incorrect signature to validate the message
bool invalidSignature = rsa.VerifyHash(hash: hash,
signature: signature,
hashAlgorithm: HashAlgorithmName.SHA1,
padding: RSASignaturePadding.Pss);
Console.WriteLine("Incorrect signature validated OK: {0}", invalidSignature);
Console.ReadKey();
}
You can avoid the code that is setting the export policy altogether by simply creating the certificate with the export policy already being correct. I used the New-SelfSignedCertificate PowerShell utility to create a certificate that was exportable from inception.
PS C:>New-SelfSignedCertificate -CertStoreLocation "Cert:\CurrentUser\" -Subject "CN=JUSTIN" -KeyExportPolicy Exportable
This negates the need for:
(certificate.PrivateKey as RSACng)?.Key.SetProperty(new CngProperty("Export Policy", BitConverter.GetBytes((int)CngExportPolicies.AllowPlaintextExport),CngPropertyOptions.Persist));
I'm creating a .net core application and I want it to work on both Windows and Linux.
In my app I'm using VerifyHash to verify file hashes, everything works great on Windows but it fails on linux.
public static bool ValidateSignature(byte[] sha256, string signature)
{
if (_rsaCryptoServiceProvider == null || string.IsNullOrEmpty(signature))
{
return false;
}
var test = _rsaCryptoServiceProvider.VerifyHash(sha256, CryptoConfig.MapNameToOID("SHA256"), Base64.Decode(signature));
Console.WriteLine("SHA256: " + Sha256ToString(sha256));
Console.WriteLine("SIGNATURE: " + signature);
Console.WriteLine("Match: " + test);
return test;
}
I added these debug WriteLines to check if function get the same data, and it does.
This is my output from Windows:
SHA256: 4146024cf95ed34573d2dabde7569f1ca3611091760060c9fee718522890519e
SIGNATURE: H+FOxGDT3vE8fA6oyKIH56fVCipRkf6oidxU0KnvCvADGu083h196dPv4lWwi5i0XATygABqEbS0iwLBXFtrhoX5wxLRkpnpnZjTEgcONWrKe78wYKKwAqUfVWyT6VuQGX3bpcSHlvTUsbtZAins2BBp7kS2CtivVwU1G87+cNs=
Match: True
And this is output from Linux:
SHA256: 4146024cf95ed34573d2dabde7569f1ca3611091760060c9fee718522890519e
SIGNATURE: H+FOxGDT3vE8fA6oyKIH56fVCipRkf6oidxU0KnvCvADGu083h196dPv4lWwi5i0XATygABqEbS0iwLBXFtrhoX5wxLRkpnpnZjTEgcONWrKe78wYKKwAqUfVWyT6VuQGX3bpcSHlvTUsbtZAins2BBp7kS2CtivVwU1G87+cNs=
Match: False
Hash and signature are exactly the same but linux fails to verify it.
I checked if the signature match on the linux machine using openssl, here's my output:
$ cat mod.dll | openssl dgst -sha256
(stdin)= 4146024cf95ed34573d2dabde7569f1ca3611091760060c9fee718522890519e
$ cat mod.dll | openssl dgst -sha256 -binary | openssl rsautl -inkey key -sign | base64
H+FOxGDT3vE8fA6oyKIH56fVCipRkf6oidxU0KnvCvADGu083h196dPv4lWwi5i0XATygABqEbS0
iwLBXFtrhoX5wxLRkpnpnZjTEgcONWrKe78wYKKwAqUfVWyT6VuQGX3bpcSHlvTUsbtZAins2BBp
7kS2CtivVwU1G87+cNs=
As you can see signature and hash from openssl matches so the signature is 100% okay, there's something wrong in the VerifyHash function.
There's my code for creating and disposing service provider:
private static void _UpdateCrypto()
{
var key = PublicKeyFactory.CreateKey(_key);
var rsaKey = (RsaKeyParameters) key;
var parameters = new RSAParameters
{
Modulus = rsaKey.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKey.Exponent.ToByteArrayUnsigned()
};
_rsaCryptoServiceProvider = new RSACryptoServiceProvider();
_rsaCryptoServiceProvider.ImportParameters(parameters);
}
public static void Dispose()
{
_rsaCryptoServiceProvider?.Dispose();
}
_key is byte[] field, it's a .der file loaded into a byte array, _UpdateCrypto() is called at the start of the program.
Okay, I just used BouncyCastle library instead and it works great.
That's my code for verifying signatures if someone needs:
public static bool ValidateSignature(byte[] sha256, string signature)
{
if (_asymmetricBlockCipher == null || string.IsNullOrEmpty(signature))
{
return false;
}
var decoded = Base64.Decode(signature);
return _asymmetricBlockCipher.ProcessBlock(decoded, 0, decoded.Length).SequenceEqual(sha256);
}
In my case the issue was that the text file (JSON) file I used as input to:
new HMACSHA256(key).ComputeHash(text)
had CRLF line ending, so it worked in Visual Studio on Windows, but failed on the Ubuntu agent.
Changing the line endings for the text file to LF fixed it.
I am using ECDSA with SHA1 encryption because I am trying to make a licencing activation for a desktop application. For that I use a PHP server to which I give PC information and the server gives me the public the key and then I want to validate the data in C#.
I generated this public key in PHP:
"-----BEGIN PUBLIC KEY-----
MDIwEAYHKoZIzj0CAQYFK4EEAAYDHgAEKzL3PFVVo3IWftdEYmwiSO/4zULGM/wB
8BrLjQ==
-----END PUBLIC KEY-----";
I used the code from here http://securitydriven.net/inferno/ To get to this
byte[] thePublicKeyToBytes = GetBytesFromPEM(thePublicKey2, "PUBLIC KEY");
CngKey dsaKeyPublic2 = thePublicKeyToBytes.ToPublicKeyFromBlob();
byte[] theRestToBytes = GetBytes(theRestInBinary);
byte[] meinData = GetBytes("Blabla");
using (var ecdsa = new ECDsaCng(dsaKeyPublic2) { HashAlgorithm = CngAlgorithm.Sha1 }) // verify DSA signature with public key
{
if (ecdsa.VerifyData(meinData, theRestToBytes)) MessageBox.Show("Signature verified.");
else MessageBox.Show("Signature verification failed.");
}
where the procedure is:
byte[] GetBytesFromPEM(string pemString, string section)
{
var header = String.Format("-----BEGIN {0}-----", section);
var footer = String.Format("-----END {0}-----", section);
var start = pemString.IndexOf(header, StringComparison.Ordinal) + header.Length;
var end = pemString.IndexOf(footer, start, StringComparison.Ordinal) - start;
if (start < 0 || end < 0)
{
return null;
}
return Convert.FromBase64String(pemString.Substring(start, end));
}
The problem is that I get this exception "cryptographicexception the parameter is incorrect" at this line:
CngKey dsaKeyPublic2 = thePublicKeyToBytes.ToPublicKeyFromBlob();
I can't show the inferno's public key, but I saw that the length of their key is 384. Is this where I am doing it wrong? The length of the generated public key?
Your public key is 52 bytes long - it is too short. How are you generating it?
The ToPublicKeyFromBlob() method is a shortcut for return CngKey.Import(byteArray, CngKeyBlobFormat.EccPublicBlob) - it works only on Ecc-based keys, and those generated by .NET. Inferno uses ECC keys over P384 curve, which means that each public key will have 48*2=96 bytes, plus 8 header bytes (as described here), for a total of 104 bytes.
Andrei, Inferno uses the NIST P-384 curve only. More importantly, the only curves supported by .NET framework (out-of-the-box) are P-256, P-384, and P-521.
I'm trying to encrypt some data in Mono C#, send it to a NodeJS server and decrypt it there. I'm trying to figure out what algorithms to use to match the two.
I send the encrypted string encoded with base64. So I do something like this in Javascript, where I know the key which was used to encrypt the data in my C# application:
var decipher = crypto.createDecipher('aes192',binkey, biniv);
var dec = decipher.update(crypted,'base64','utf8');
dec += decipher.final('utf8');
console.log("dec", dec);
In Mono I create my Cypher with:
using System.Security.Cryptography;
using (Aes aesAlg = Aes.Create("aes192"))
I need to pass the correct string to Aes.Create() in order to have it use the same algorithm, but I can't find what it should be. "aes192" is not correct it seems.
I don't need aes192 this was just a tryout. Suggest a different encryption flavor if it makes sense. Security is not much of an issue.
Here are links to .NET and Nodejs docs:
http://msdn.microsoft.com/en-us/library/system.security.cryptography.aes.aspx
http://nodejs.org/api/crypto.html
This code works for my Node.js side, but please replace the static iv, otherwhise aes encryption would be useless.
var crypto = require('crypto');
function encrypt(data, key) {
key = key || new Buffer(Core.config.crypto.cryptokey, 'binary'),
cipher = crypto.createCipheriv('aes-256-cbc', key.toString('binary'), str_repeat('\0', 16));
cipher.update(data.toString(), 'utf8', 'base64');
return cipher.final('base64');
}
function decipher(data, key) {
key = key || new Buffer(Core.config.crypto.cryptokey, 'binary'),
decipher = crypto.createDecipheriv('aes-256-cbc', key.toString('binary'), str_repeat('\0', 16));
decipher.update(data, 'base64', 'utf8');
return decipher.final('utf8');
}
function str_repeat(input, multiplier) {
var y = '';
while (true) {
if (multiplier & 1) {
y += input;
}
multiplier >>= 1;
if (multiplier) {
input += input;
} else {
break;
}
}
return y;
}
I hope this helps You.
NOTE: You need to deliver an 265bit aka 32 character key for this algorithm to work.
POSSIBLE .NET SOLUTION: This may help you Example
You should simply write new AesManaged().
You don't need to call Create().
You then need to set Key and IV, then call CreateDecryptor() and put it in a CryptoStream.
It turned out to be a stupid mistake. I thought the create function in Node.js could take a variable argument count. Turns out you need to call the createDecipheriv() instead.
Just for the record, you can easily check the padding and mode by looking at those properties in the Aes object. The defaults are CBC and PKCS7. That padding is also used in nodejs crypto. So a for a 128 key size my code to decrypt a base64 encoded string would be:
var crypto = require('crypto');
var binkey = new Buffer(key, 'base64');
var biniv = new Buffer(iv, 'base64');
var decipher = crypto.createDecipheriv('aes-128-cbc', binkey, biniv);
var decrypted = decipher.update(crypted,'base64','utf8');
decrypted += decipher.final('utf8');
console.log("decrypted", decrypted);