Sign data with MD5WithRSA from .Pem/.Pkcs8 keyfile in C# - 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.

Related

C# (.NET) RSACryptoServiceProvider import/export x509 public key blob and PKCS8 private key blob

first of all let me state that I'm not a crypto expert but i know the basics.
I want to be able to:
Get a RSACryptoServiceProvider instance from a X509 Public Key blob
Get a RSACryptoServiceProvider instance from a PKCS8 Private Key blob
Export the public key from a RSACryptoServiceProvider instance as a x509 Public Key blob
Export the private key from a RSACryptoServiceProvider instance as a PKCS8 blob
After looking around all day, i've found this repository (thanks a lot jrnker) and i selected the code i needed to able to met the goals 1, 2 and 3.
Since Jrnker's only provides methods to get a RSACryptoServiceProvider from a PKCS1 blob (and what i needed was a RSACryptoServiceProvider from a PKCS8 blob) i kept looking to met goal number 4. Then i've found Michel Gallant's "opensslkey.cs" and i selected the needed code to met goal number 4.
Then i've proceeded to compile a class with needed methods and classes.
Here's my demo class:
using System;
namespace RSAKeyTests
{
class Demo
{
static void Main(string[] args)
{
//EXPORTED KEYS
string importedPublicKeyBase64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhbVC4aUR+XRCepBcPlod69wruXqwW9yL/YJYvuaQ33QxUoAehQ0z4SuphHwEPxQp/qLqucmE6XKlEeTksFAmaGM88uuGessqMZmdu9WFhc07MWLTCifR43IRtGEeWeFSWjUI6mNRrShP3QQ3+Z6e7w+HRA2RpmgNgEhJRvECHAKpcpHvP9o5Sq6q/dIAyR6NEjRFhfud27rFtnWrLj+ZmIsScemvks4vh8V3n8EzxxRE8nzVuZYr4v4NNH+q95XgIadHZ1Y6ICXJgX2NfacNRQl9+SEv0Wo8lbmFSIO3jHqyiWuSugv7R3/rQPRXHT6HJAtw0tBiPOBitMkTzqOvIwIDAQAB";
string importedPrivateKeyBase64 = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCFtULhpRH5dEJ6kFw+Wh3r3Cu5erBb3Iv9gli+5pDfdDFSgB6FDTPhK6mEfAQ/FCn+ouq5yYTpcqUR5OSwUCZoYzzy64Z6yyoxmZ271YWFzTsxYtMKJ9HjchG0YR5Z4VJaNQjqY1GtKE/dBDf5np7vD4dEDZGmaA2ASElG8QIcAqlyke8/2jlKrqr90gDJHo0SNEWF+53busW2dasuP5mYixJx6a+Szi+HxXefwTPHFETyfNW5livi/g00f6r3leAhp0dnVjogJcmBfY19pw1FCX35IS/RajyVuYVIg7eMerKJa5K6C/tHf+tA9FcdPockC3DS0GI84GK0yRPOo68jAgMBAAECggEAJ/Dalr8RnHvPM/+Vnoaa847kfNaaggZixwq96eDEHAwAg82D0Gj+O2AolkvZlOI4HTmbdn4tNvMpPiwq6EQ5BOvIFCSpGltAMmraBHcnGK4S5ZDIy/rTJuc3RLPSNjUpvYqkLCgZCOnG2ZXeBrIMdgskc/69qIDir5RoV0m9QJJYU7pfrfErWYr/eqb1t7eZtTBAg+LAjKUMUq68WoJiBSBRPbAvlyFoc6tyk0ftngsF4OPVbwZQyYC2vLmxVrr1/YQbEgjpuJwQ0bONL6G9PAH6O+h10ILk9nyJY2c9gOXU0tz+foJ47naM12wCJETEy9JGeAiN4NLz5wRKTZzZwQKBgQDCOEJGDgtmSM0bDv4vPuxbacFgGTgRAKTs6sG9E1Cf3LNBLDP9OhfRkXFc192PmQRAktaZAN89zXeGxK1tLbJ3003qKXw05K3KOksVjJ7AH4Yhurv3VWmFZB8pryUsxIp+rm/5GLf4LfptUmBO6R4+jTfJVRBtK4A9KmkbY7BjgwKBgQCwPWayTgd0fmDqJxptWfPThcUw3/cG6EWTpnx1dSOdaBHzewRwq/8/i4vs314/onLggXgZTIkPU7y8ylTmz5KcaPIQkmRSSSL0Y2yzMGcHnylj7ysgBLw23k/PVzGSsMZ6ly7lE03SNQ3tyg6u0lc3pbT8ZLHf/x913stxSSiT4QKBgQChdgnKmZRqhS1WSGGSP3pZCJNFY9HTeLijaQqFOFB3hg/Tp37VDv2MMKCQsbi0z13UnP4glrQAehbbCBixQiMzMIx+ldx3UIEWNN4E3TGAwPROiCIJnY0q4rBxg/SgwgftBvF5oU4X2YluZuQ/1ddZ4ya0jq4oQ9jJgL9+kKKsJwKBgQCndbBfPEVZK7xqwT0bKp3EHxd/mU/gAFQcN9WKxgNRTdHAyOMvLD8c4jvSl2u2i2UcbejwIQkaxzZPLPH/XrywYgegN3mbtmLAVLi0iwla9KEfk+ImSlmMyTCMkw1HlTECyySEBhOr6T2S9Kt+8d5twcZ3DDb34DLEjS5CNoGYAQKBgDCEyhrg2lwyYwrL26ohNNuzgiabC5IKCgHlMpsUQjoCid9awCSb2iROf7iZIBoDyzXqgEQWTAf2clpJxgHz0necVw2sXP8wGcJXJ+e/lXNfPaC4z2QRnQ6i2iV88jRlWLK+S403hGnK0L/SDu9LtBhHwy6r/qRGT14ourqS6x7O";
byte[] importedPublicKeyBytes = Convert.FromBase64String(importedPublicKeyBase64);
byte[] importedPrivateKeyBytes = Convert.FromBase64String(importedPrivateKeyBase64);
//PRINT INFO
Console.WriteLine("------ IMPORTED KEY PAIR: ------\n");
Console.WriteLine("PUBLIC KEY:\n"+importedPublicKeyBase64+"\n\n");
Console.WriteLine("PRIVATE KEY:\n" + importedPrivateKeyBase64 + "\n\n");
//GENERATING RSACRYPTOSERVICEPROVIDER FROM X509 PUBLIC KEY BLOB
using (var providerFromX509pubKey = RSAKeyUtils.DecodePublicKey(importedPublicKeyBytes))
{
providerFromX509pubKey.PersistKeyInCsp = false; //DO NOT STORE IN KEYSTORE
//EXPORT TO X509 PUBLIC KEY BLOB
byte[] x509pubKeyBytes = RSAKeyUtils.PublicKeyToX509(providerFromX509pubKey.ExportParameters(false));
//CONVERT TO BASE64
string x509pubKeyBase64 = Convert.ToBase64String(x509pubKeyBytes);
//PRINT INFO
Console.WriteLine("------ PUBLIC KEY TO EXPORT ------");
Console.WriteLine("Public key to export matches imported? "+importedPublicKeyBase64.Equals(x509pubKeyBase64));
Console.WriteLine(x509pubKeyBase64+"\n\n");
}
//GENERATING RSACRYPTOSERVICEPROVIDER FROM PKCS8 PRIVATE KEY BLOB
using (var providerFromPKCS8privKey = RSAKeyUtils.DecodePrivateKeyInfo(importedPrivateKeyBytes))
{
providerFromPKCS8privKey.PersistKeyInCsp = false; //DO NOT STORE IN KEYSTORE
//EXPORT TO PKCS8 PRIVATE KEY BLOB
byte[] pkcs8privKeyBytes = RSAKeyUtils.PrivateKeyToPKCS8(providerFromPKCS1privKey.ExportParameters(true));
//CONVERT TO BASE64
string pkcs8privKeyBase64 = Convert.ToBase64String(pkcs8privKeyBytes);
//PRINT INFO
Console.WriteLine("------ PRIVATE KEY TO EXPORT ------");
Console.WriteLine("Private key to export matches imported? " + importedPrivateKeyBase64.Equals(pkcs8privKeyBase64));
Console.WriteLine(pkcs8privKeyBase64);
}
//PREVENTS THE PROGRAM FROM EXITING
Console.ReadKey();
}
}
}
Here's the "RSAKeyUtils" class i've compiled.
I hope this can be useful to someone else.
First, byte in Java isn't the same as byte in .Net. Java only has signed integers, so a Java byte ranges from -128 to 127, while a .Net byte ranges from 0 to 255. But I'm not sure, if this is the problem, as a Base64 string relies on the bit pattern. Try to use a larger type in its positive range and use the lower 8 bits only.
Second, the message about the bad provider version might indicate either the above cause or might just be a misleading text, that won't really help. I remember I once encountered the same error message and had a hard time to find the actual cause. A quick search some minutes ago hasn't been successful either. In my case I had a completely other situation, so I won't be able to point my finger on your problem directly. Instead I would recommend to check padding, encrypt mode, base64 translation and related stuff around the RSA algorithm and try to find an alternative way to achieve the same as you wanted before. On the way to there you might stumble across the actual problem.

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

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.

Does ECDiffieHellmanCng in .NET have a key derivation function that implements NIST SP 800-56A, section 5.8.1

I have a task at hand that requires deriving key material using the key derivation function described in NIST SP 800-56A, section 5.8.1. I'm not an expert in Cryptography so please excuse me if the question is naive. Here's what I've done so far:
I have the other party's public key and my private key
Now I try to generate the shared secret using ECDH 1.3.132.1.12 using C# (.NET 4) ECDiffieHellmanCng class like so:
// The GetCngKey method reads the private key from a certificate in my Personal certificate store
CngKey cngPrivateKey = GetCngKey();
ECDiffieHellmanCng ecDiffieHellmanCng = new ECDiffieHellmanCng(cngPrivateKey);
ecDiffieHellmanCng.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP256;
ecDiffieHellmanCng.KeyDerivationFunction = ?? // What do I set here
Finally do this:
ecDiffieHellmanCng.DeriveKeyMaterial(otherPartyPublicKey:);
Where/how do I set the other parameters Algorithm ID, Party U Info, Party V Info?
EDIT
I am open to using other libraries like Bouncy Castle (provided they can be called from .NET)
TL;DR; I haven't found a way to derive the symmetric key using KDF described in NIST SP 800-56A, section 5.8.1 using built-in classes in .NET 4.0 alone
The good news (for me :-)) is that it IS possible in .NET 4.0 using the lovely BouncyCastle library (NuGet: Install-Package BouncyCastle-Ext -Version "1.7.0"). Here's how:
STEP 1: Get other party's public key
Depending on your scenario, this may be read from a certificate or come to you as part of the message containing the encrypted data. Once you have the Base64 encoded public-key, read it into a Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters object like so:
var publicKeyBytes = Convert.FromBase64String(base64PubKeyStr);
ECPublicKeyParameters otherPartyPublicKey = (ECPublicKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
STEP 2: Read your private-key
This would most-commonly involve reading the private key from a PFX/P12 certificate. The windows account running the code should have access to the PFX/P12 and additionally, if the certificate is imported into a certificate store, you'll need to grant permissions via the All Tasks -> manage private key menu in certmgr.msc
using (StreamReader reader = new StreamReader(path))
{
var fs = reader.BaseStream;
string password = "<password for the PFX>";
Pkcs12Store store = new Pkcs12Store(fs, passWord.ToCharArray());
foreach (string n in store.Aliases)
{
if (store.IsKeyEntry(n))
{
AsymmetricKeyEntry asymmetricKey = store.GetKey(n);
if (asymmetricKey.Key.IsPrivate)
{
ECPrivateKeyParameters privateKey = asymmetricKey.Key as ECPrivateKeyParameters;
}
}
}
}
STEP 3: Compute the shared secret
IBasicAgreement aKeyAgree = AgreementUtilities.GetBasicAgreement("ECDH");
aKeyAgree.Init(privateKey);
BigInteger sharedSecret = aKeyAgree.CalculateAgreement(otherPartyPublicKey);
byte[] sharedSecretBytes = sharedSecret.ToByteArray();
STEP 4: Prepare information required to compute symmetric key:
byte[] algorithmId = Encoding.ASCII.GetBytes(("<prependString/Hex>" + "id-aes256-GCM"));
byte[] partyUInfo = Encoding.ASCII.GetBytes("<as-per-agreement>");
byte[] partyVInfo = <as-per-agreement>;
MemoryStream stream = new MemoryStream(algorithmId.Length + partyUInfo.Length + partyVInfo.Length);
var sr = new BinaryWriter(stream);
sr.Write(algorithmId);
sr.Flush();
sr.Write(partyUInfo);
sr.Flush();
sr.Write(partyVInfo);
sr.Flush();
stream.Position = 0;
byte[] keyCalculationInfo = stream.GetBuffer();
STEP 5: Derive the symmetric key
// NOTE: Use the digest/Hash function as per your agreement with the other party
IDigest digest = new Sha256Digest();
byte[] symmetricKey = new byte[digest.GetDigestSize()];
digest.Update((byte)(1 >> 24));
digest.Update((byte)(1 >> 16));
digest.Update((byte)(1 >> 8));
digest.Update((byte)1);
digest.BlockUpdate(sharedSecret, 0, sharedSecret.Length);
digest.BlockUpdate(keyCalculationInfo, 0, keyCalculationInfo.Length);
digest.DoFinal(symmetricKey, 0);
Now you have the symmetric key ready to do the decryption. To perform decryption using AES, BouncyCastle IWrapper can be used. Obtain an IWrapper using Org.BouncyCastle.Security.WrapperUtilities by calling WrapperUtilities.GetWrapper("AES//") e.g. "AES/CBC/PKCS7". This will also depend on the agreement between the two communicating parties.
Initialize the cipher (IWrapper) with symmetric key and initialization vector (IV) and call the Unwrap method to get plain-text bytes. Finally, convert to string literal using the character encoding used (e.g. UTF8/ASCII/Unicode)

CryptographicException "Key not valid for use in specified state." while trying to export RSAParameters of a X509 private key

I am staring at this for quite a while and thanks to the MSDN documentation I cannot really figure out what's going. Basically I am loading a PFX file from the disc into a X509Certificate2 and trying to encrypt a string using the public key and decrypt using the private key.
Why am I puzzled: the encryption/decryption works when I pass the reference to the RSACryptoServiceProvider itself:
byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);
But if the export and pass around the RSAParameter:
byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
...it throws a "Key not valid for use in specified state." exception while trying to export the private key to RSAParameter. Please note that the cert the PFX is generated from is marked exportable (i.e. I used the pe flag while creating the cert). Any idea what is causing the exception?
static void Main(string[] args)
{
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test");
x.FriendlyName = "My test Cert";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
try
{
store.Add(x);
}
finally
{
store.Close();
}
byte[] ed1 = EncryptRSA("foo1", x.PublicKey.Key as RSACryptoServiceProvider);
string foo1 = DecryptRSA(ed1, x.PrivateKey as RSACryptoServiceProvider);
byte[] ed = EncryptRSA("foo", (x.PublicKey.Key as RSACryptoServiceProvider).ExportParameters(false));
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider).ExportParameters(true));
}
private static byte[] EncryptRSA(string data, RSAParameters rsaParameters)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] plainData = bytConvertor.GetBytes(data);
RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
publicKey.ImportParameters(rsaParameters);
return publicKey.Encrypt(plainData, true);
}
private static string DecryptRSA(byte[] data, RSAParameters rsaParameters)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
RSACryptoServiceProvider privateKey = new RSACryptoServiceProvider();
privateKey.ImportParameters(rsaParameters);
byte[] deData = privateKey.Decrypt(data, true);
return bytConvertor.GetString(deData);
}
private static byte[] EncryptRSA(string data, RSACryptoServiceProvider publicKey)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] plainData = bytConvertor.GetBytes(data);
return publicKey.Encrypt(plainData, true);
}
private static string DecryptRSA(byte[] data, RSACryptoServiceProvider privateKey)
{
UnicodeEncoding bytConvertor = new UnicodeEncoding();
byte[] deData = privateKey.Decrypt(data, true);
return bytConvertor.GetString(deData);
}
Just to clarify in the code above the bold part is throwing:
string foo = DecryptRSA(ed, (x.PrivateKey as RSACryptoServiceProvider)**.ExportParameters(true)**);
I believe that the issue may be that the key is not marked as exportable. There is another constructor for X509Certificate2 that takes an X509KeyStorageFlags enum. Try replacing the line:
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test");
With this:
X509Certificate2 x = new X509Certificate2(#"C:\temp\certs\1\test.pfx", "test", X509KeyStorageFlags.Exportable);
For the issue I encountered a code change was not an option as the same library was installed and working elsewhere.
Iridium's answer lead me to look making the key exportable and I was able to this as part of the MMC Certificate Import Wizard.
Hope this helps someone else. Thanks heaps
I've met some similar issue, and X509KeyStorageFlags.Exportable solved my problem.
I'm not exactly an expert in these things, but I did a quick google, and found this:
http://social.msdn.microsoft.com/Forums/en/clr/thread/4e3ada0a-bcaf-4c67-bdef-a6b15f5bfdce
"if you have more than 245 bytes in your byte array that you pass to your RSACryptoServiceProvider.Encrypt(byte[] rgb, bool fOAEP) method then it will throw an exception."
For others that end up here through Google, but don't use any X509Certificate2, if you call ToXmlString on RSACryptoServiceProvider but you've only loaded a public key, you will get this message as well. The fix is this (note the last line):
var rsaAlg = new RSACryptoServiceProvider();
rsaAlg.ImportParameters(rsaParameters);
var xml = rsaAlg.ToXmlString(!rsaAlg.PublicOnly);
AFAIK this should work and you're likely hitting a bug/some limitations. Here's some questions that may help you figure out where's the issue.
How did you create the PKCS#12 (PFX) file ? I've seen some keys that CryptoAPI does not like (uncommon RSA parameters). Can you use another tool (just to be sure) ?
Can you export the PrivateKey instance to XML, e.g. ToXmlString(true), then load (import) it back this way ?
Old versions of the framework had some issues when importing a key that was a different size than the current instance (default to 1024 bits). What's the size of your RSA public key in your certificate ?
Also note that this is not how you should encrypt data using RSA. The size of the raw encryption is limited wrt the public key being used. Looping over this limit would only give you really bad performance.
The trick is to use a symmetric algorithm (like AES) with a totally random key and then encrypt this key (wrap) using the RSA public key. You can find C# code to do so in my old blog entry on the subject.
Old post, but maybe can help someone.
If you are using a self signed certificate and make the login with a different user, you have to delete the old certificate from storage and then recreate it. I've had the same issue with opc ua software

Encrypting a BouncyCastle RSA Key Pair and storing in a SQL2008 database

I have a function that generates a BouncyCastle RSA key pair. I need to encrypt the private key and then store the encrypted private and public keys into separate SQL2008 database fields.
I am using the following to get the keypair:
private static AsymmetricCipherKeyPair createASymRandomCipher()
{
RsaKeyPairGenerator r = new RsaKeyPairGenerator();
r.Init(new KeyGenerationParameters(new SecureRandom(), 1024));
AsymmetricCipherKeyPair keys = r.GenerateKeyPair();
return keys;
}
This is returning the keys fine, but I am not sure how I can then encrypt the private key and subsequently store it in the database.
This is what I am currently using the encrypt the data (incorrectly?):
public static byte[] encBytes2(AsymmetricKeyParameter keyParam, byte[] Key, byte[] IV)
{
MemoryStream ms = new MemoryStream();
Rijndael rjdAlg = Rijndael.Create();
rjdAlg.Key = Key;
rjdAlg.IV = IV;
CryptoStream cs = new CryptoStream(ms, rjdAlg.CreateEncryptor(), CryptoStreamMode.Write);
byte[] keyBytes = System.Text.Encoding.Unicode.GetBytes(keyParam.ToString());
cs.Write(keyBytes, 0, keyBytes.Length);
cs.Close();
byte[] encryptedData = ms.ToArray();
return encryptedData;
}
Obviously the keyBytes setting where I am converting keyParam.ToString() is not correct as it only converts the KeyParameter name, not the actual value. I am submitting to this function the previous key pair return of keys.Private.
The other question is as I am not encrypting the Public Key what format should I be storing this in the SQL2008 database, nvarchar(256) or other?
Any help would be greatly appreciated.
For reasons that should be clear, default (and perhaps inadvertent) serialization does not play well with private keys which should only be written out in very limited situations.
BouncyCastle has support for PKCS#8, which is the relevant standard for "serializing" private keys. There are ASN.1 structures called PrivateKeyInfo and EncryptedPrivateKeyInfo. Since they are in ASN.1 there are standard ways to serialize/deserialize them. As the name suggests, one stores the key in plaintext, the other encrypts the key based on a password.
For the public keys - these would not ordinarily be encrypted. BC supports the X.509 standard format of SubjectPublicKeyInfo for serializing them.
In the C# build, the high-level classes to look at would be:
Org.BouncyCastle.Security.PrivateKeyFactory
Org.BouncyCastle.Security.PublicKeyFactory
Org.BouncyCastle.Pkcs.EncryptedPrivateKeyInfoFactory
Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory
Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory
As long as the object is marked as serializable, one way to convert an object to a byte array is to use the BinaryFormatter class in .Net.
You will need to add this using statement to your code file:
using System.Runtime.Serialization.Formatters.Binary;
A binary formatter can output your class to a stream. As you intend to convert your object to a byte array, you can use a System.IO.MemoryStream as temporary storage.
MemoryStream memStream = new MemoryStream();
You can then create a new binary formatter.
BinaryFormatter formatter = new BinarryFomatter();
and use this to serialize your object.
formatter.Serialize(memStream, someObject);
To get the bytes you can use:
return memStream.ToArray();
To deserialize the byte array you need to write the bytes to a memory stream.
memStream.Write(arrBytes, 0, arrBytes.Length);
Return to the beginning of the stream.
memStream.Seek(0, SeekOrigin.Begin);
Then use the formatter to recreate the object.
Object obj = (Object)formatter.Deserialize(memStream);
If you are already using encryption functions you should be able to encrypt the created byte array quite easily before storing it in the database.
Hopefully that will help you in the right direction. If you are lucky, the BouncyCastle objects will be marked as serializable, if not you will need some extra code. Later, I will get a chance to look at the BouncyCastle librarys to be able to test this and will post more code if necessary.
... I have never used BouncyCastle before. After some testing, it appears that the public and private key objects are not serializable, so you will need to convert these objects into something that is!
It appears that the public and private keys expose properties as various BouncyCastle.Math.BigInteger values. (The keys can also be constructed from these BigIntegers). Further, BigIntegers have a ToByteArray() function and can also be constructed from a byte array. Very useful..
Knowing that you can break each key into BigIntegers and these in turn to a byte array and that the reverse is also possible, you a way to store all these in a serializable object. A simple struct or class would do e.g.
[Serializable]
private struct CipherPrivateKey
{
public byte[] modulus;
public byte[] publicExponent;
public byte[] privateExponent;
public byte[] p;
public byte[] q;
public byte[] dP;
public byte[] dQ;
public byte[] qInv;
}
[Serializable]
private struct CipherPublicKey
{
public bool isPrivate;
public byte[] modulus;
public byte[] exponent;
}
This gives us a pair of easy to use serializable objects.
The AsymmetricCipherKeyPair exposes the Public and Private keys as AsymmetricKeyParameter objects. To get at the more detailed properties you will need to cast these to the following:
keyPair.Public to BouncyCastle.Crypto.Parameters.RsaKeyParameters
keyPair.Private to BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters
The following functions will convert these to the structs to declared earlier:
private static CipherPublicKey getCipherPublicKey(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters cPublic)
{
CipherPublicKey cpub = new CipherPublicKey();
cpub.modulus = cPublic.Modulus.ToByteArray();
cpub.exponent = cPublic.Exponent.ToByteArray();
return cpub;
}
private static CipherPrivateKey getCipherPrivateKey(Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters cPrivate)
{
CipherPrivateKey cpri = new CipherPrivateKey();
cpri.dP = cPrivate.DP.ToByteArray();
cpri.dQ = cPrivate.DQ.ToByteArray();
cpri.modulus = cPrivate.Modulus.ToByteArray();
cpri.p = cPrivate.P.ToByteArray();
cpri.privateExponent = cPrivate.Exponent.ToByteArray();
cpri.publicExponent = cPrivate.PublicExponent.ToByteArray();
cpri.q = cPrivate.Q.ToByteArray();
cpri.qInv = cPrivate.QInv.ToByteArray();
return cpri;
}
Using the binary formatter mentioned earlier, we can convert the serializable objects we have just created to a byte array.
CipherPublicKey cpub = getCipherPublicKey((Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)keypair.Public);
MemoryStream memStream = new MemoryStream();
BinaryFormatter formatter = new BinarryFomatter();
formatter.Serialize(memStream, cpub);
return memStream.ToArray();
Desierializing is then just the inverse as described earlier. Once you have either the public or private structs deserialized you can use the BouncyCastle contructors to recreate the keys. These functions demonstrate this.
private static Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters recreateASymCipherPublicKey(CipherPublicKey cPublicKey)
{
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters key;
key = new Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters(
cPublicKey.isPrivate,
createBigInteger(cPublicKey.modulus),
createBigInteger(cPublicKey.exponent));
return key;
}
private static Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters recreateASymCipherPrivateKey(CipherPrivateKey cPrivateKey)
{
Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters key;
key = new Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters(
createBigInteger(cPrivateKey.modulus),
createBigInteger(cPrivateKey.publicExponent),
createBigInteger(cPrivateKey.privateExponent),
createBigInteger(cPrivateKey.p),
createBigInteger(cPrivateKey.q),
createBigInteger(cPrivateKey.dP),
createBigInteger(cPrivateKey.dQ),
createBigInteger(cPrivateKey.qInv));
return key;
}
If you need to recreate the original key pair for any reason:
AsymmetricKeyParameter publ = (AsymmetricKeyParameter)recreateASymCipherPublicKey(cKeyPair.publicKey);
AsymmetricKeyParameter priv = (AsymmetricKeyParameter)recreateASymCipherPrivateKey(cKeyPair.privateKey);
AsymmetricCipherKeyPair keyPair = new AsymmetricCipherKeyPair(publ, priv);
Hopefully that all makes sense! The code samples should help you on your way.
The correct approach is to use Peters' suggestion.
I have included a small C# code sample below :
var keyPair = GetKeypair();
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
byte[] serializedKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();
AsymmetricKeyParameter deserializedKey1 = PrivateKeyFactory.CreateKey(serializedKey);
Assert.AreEqual(keyPair.Private, deserializedKey1);
AsymmetricKeyParameter deserializedKey2 = PrivateKeyFactory.CreateKey(privateKeyInfo);
Assert.AreEqual(keyPair.Private, deserializedKey2);
The sample uses the Bouncy Castle API. Note that the sample does NOT encrypt the key. The CreatePrivateKeyInfo method is overloaded to allow the use of a password as protection of the key.
Regarding the second part of your question, the data type that should be used for storing the key would be VARBINARY(256).
Back to the first part of your question, you actually have the option of having SQL Server handle the encryption for you. Granted, whether or not you would want to do this would be a matter of what your application requirements are, but I'll go over it in case it's an option.
We'll be pretty basic here and just use symmetric keys and Triple-DES.
First, the database has a master key which is used to protect certificates and asymmetric keys. The master key is encrypted with Triple-DES.
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'supersecretpassword'
SQL Server 2005/2008 can generate their own X.509 certificates used to protect the keys used to encrypt the actual data.
CREATE CERTIFICATE ExampleCertificate
WITH SUBJECT = 'thisisjustsomemetadata'
There are a lot of options for encrypting symmetric keys (certificates, passwords, other keys), as well as many supported algorithms. But for this example, we'll use our certificate.
CREATE SYMMETRIC KEY ExampleKey
WITH ALGORITHM = TRIPLE_DES
ENCRYPTION BY CERTIFICATE EncryptTestCert
The key needs to be decrypted using the same method with which it was encrypted. In our case, this would be the certificate we created.
DECLARE #Value VARCHAR(50)
SET #Value = 'supersecretdata!'
OPEN SYMMETRIC KEY ExampleKey DECRYPTION BY CERTIFICATE ExampleCertificate
UPDATE SomeTable
SET SomeColumn = ENCRYPTBYKEY(KEY_GUID('ExampleKey'), #Value)
Decryption is just as straightforward.
OPEN SYMMETRIC KEY ExampleKey DECRYPTION BY CERTIFICATE ExampleCertificate
SELECT CONVERT(VARCHAR(50),DECRYPTBYKEY(SomeColumn)) AS DecryptedData
FROM SomeTable
Hopefully this solved your problem, or at least opened you up to alternative solutions (though someone who's had experience doing encryption in C# apps could probably find the fault in your above code). If you have requirements that necessitate that the data can't even go over the wire to the SQL Server in plain-text, obviously this is a no-go (well, you can actually create SSL connections to SQL Server...).

Categories

Resources