To implement TLS encryption via SslStream i am using a self signed certificate.
I am experiencing strange "no common algorithm" errors for clients connecting from an older Win2003 machine.
After reading this thread i discovered the following:
Those errors disappear if i change my certificate generation procedure (more specifically: the private key generation part):
Old:
var privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0";
privateKey.MachineContext = true;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
New:
var privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0";
privateKey.MachineContext = true;
privateKey.Length = 1024;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
My questions (may sound stupid, sorry for that; i'm fairly new to TLS & co):
Which algorithms are relaying on a private key with this keySpec value? Can I see somewhere which algo has been taken by SslStream?
Why do I have to reduce the key length to 1024? Any value above will cause an exception to occur when calling Create().
Am I taking security risks with these changes?
Any suggestions refering to fixing the Win2K03 machine are also welcome...
Microsoft Base Cryptographic Provider v1.0 is the most limited of the cryptographic providers. For AT_EXCHANGE it is limited to 1024-bit RSA, per https://msdn.microsoft.com/en-us/library/windows/desktop/bb931357(v=vs.85).aspx.
Your TLS error probably comes from the SChannel library wanting to use the RSA key in AT_EXCHANGE mode even on ciphersuites where RSA signature is used, but not RSA encryption, since your two files differ in both keyspec and value.
Microsoft Enhanced RSA and AES Cryptographic Provider is the newest (added in XP SP3) CSP, if you change to that you should be able to make RSA AT_EXCHANGE keys up to length 16384 (though it'll take hours to do so, so you might want to stick to your 2048).
Related
BouncyCastle includes many symmetric encryption engines, as well as RSA and ElGamal encryption engines (asymmetric engines). It also has a lot of online resources about how to use these engines to perform encryption/decryption processes. However, a bouncy castle provides no Elliptic Curve engine (check github/bc). After reviewing the code, all asymmetric engines implement the AsymmetricBlockCipher interface and none of them is an EC engine.
This is somehow confusing since BouncyCastle provides the ability to generate EC key pairs with different key strength based on predefined and well-known curves, like the example below:
public static AsymmetricCipherKeyPair GenerateKeys(int keySize)
{
DerObjectIdentifier oid;
switch (keySize)
{
case 192:
oid = X9ObjectIdentifiers.Prime192v1;
break;
case 224:
oid = SecObjectIdentifiers.SecP224r1;
break;
case 128:
oid = SecObjectIdentifiers.SecP128r1;
break;
case 239:
oid = X9ObjectIdentifiers.Prime239v1;
break;
case 256:
oid = X9ObjectIdentifiers.Prime256v1;
break;
case 384:
oid = SecObjectIdentifiers.SecP384r1;
break;
case 521:
oid = SecObjectIdentifiers.SecP521r1;
break;
default:
throw new InvalidParameterException("unknown key size.");
}
ECKeyPairGenerator gen = new ECKeyPairGenerator();
SecureRandom secureRandom = new SecureRandom();
X9ECParameters ecps = CustomNamedCurves.GetByOid(oid);
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecps.Curve, ecps.G, ecps.N, ecps.H, ecps.GetSeed());
ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecDomainParameters, secureRandom);
gen.Init(ecKeyGenerationParameters);
return gen.GenerateKeyPair();
}
There are some engines, like IESEngine, that provides a public/private EC agreement on top of the encryption/decryption process (e.g. ECDHBasicAgreement), however, it doesn't use the public/private keys directly, instead, it calculates a new symmetric key from both keys that are then used to encrypt the plaintext message using a predefined symmetric cipher.
My question:
Is BC really not providing an easy to use EC Engine like
ElGamalEngine and RSAEngine?
If yes, how to implement a safe EC encryption/decryption process using directly the ECKeyParameters generated using the above function (if possible)?
Thanks in advance.
Is BC really not providing an easy to use EC Engine like ElGamalEngine and RSAEngine?
Correct, because there aren't any. In principle you could use ElGamal encryption with ECC, but that has such serious input limitations (requiring a point rather than normal plaintext) that it is hardly useful to do so. Furthermore, using it directly will lead to an insecure scheme. That's not specific to Bouncy Castle, by the way.
If yes, how to implement a safe EC encryption/decryption process using directly the ECKeyParameters generated using the above function (if possible)?
Unless you are a cryptographer / mathematician, you don't. You use ECIES.
After some research, I found that BouncyCastle has its own SM2Engine that implements the SM2 Digital Signature Algorithm and uses the ECKeyParameters (Elliptic curve key parameters) to provide encryption/decryption abilities.
Edit: please note that SM2 is not yet verified to be totally secure, where there has been relatively little analysis of the SM2 signature scheme that I can find in the anglophone cryptography literature beyond some side channel attacks. So use it upon your own responsibility.
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(true, new ParametersWithRandom((ECKeyParameters) publicKey, new SecureRandom()));
byte[] enc1 = sm2Engine.processBlock(plainText, 0, plainText.length);
System.out.println("Cipher Text (SM2Engine): " + Hex.toHexString(enc1));
sm2Engine = new SM2Engine();
sm2Engine.init(false, (ECKeyParameters) privateKey);
byte[] dec1 = sm2Engine.processBlock(enc1, 0, enc1.length);
System.out.println("Plain Text (SM2Engine): " + Hex.toHexString(dec1));
Starting from .NET 4.7.2 it is possible to generate RSA and EC certificates using .NET CertificateRequest. However I can't find anything that would allow me to generate DSA certs. Here is how I'd do it for RSA and EC:
private static X509Certificate2 GenerateRsaCertificate()
{
var hashAlgorithm = HashAlgorithmName.SHA256;
var rsaKey = RSA.Create(2048);
var subject = new X500DistinguishedName("CN=mycert");
var request = new CertificateRequest(subject, rsaKey, hashAlgorithm, RSASignaturePadding.Pkcs1);
var certificate = request.CreateSelfSigned(DateTime.Now - TimeSpan.FromDays(5), DateTime.Now + TimeSpan.FromDays(365));
return certificate;
}
private static X509Certificate2 GenerateEcDsaCertificate()
{
var hashAlgorithm = HashAlgorithmName.SHA256;
var curve = ECCurve.NamedCurves.nistP256;
var ecDsaKey = ECDsa.Create(curve);
var subject = new X500DistinguishedName("CN=mycert");
var request = new CertificateRequest(subject, ecDsaKey, hashAlgorithm);
var certificate = request.CreateSelfSigned(DateTime.Now - TimeSpan.FromDays(5), DateTime.Now + TimeSpan.FromDays(365));
return certificate;
}
Previously I used Bouncy Castle to generate all three types of certs, but with migration to .NET I'm able to use only RSA and ECDsa in CertificateRequest calls. Are there any reasons why DSA is not included? I can still generate a key with DSA.Create(keySize) however. Also .NET Framework includes other classes that work with DSA: DSA, DSACng, DSACryptoServiceProvider, DSACertificateExtensions, but I don't see anything for certificate generation. Are there any problems with the algorithm itself (maybe I shouldn't use it at all)? Or am I missing something in the API?
Are there any problems with the algorithm itself (maybe I shouldn't use it at all)?
Non-EC DSA is dying.
I'll speculate that the thing that really did it in is the original specification (FIPS 186-1) limited the keys to 1024-bit and the algorithm to SHA-1. In 2009 the algorithm got updated in FIPS 186-3 to support slightly larger keys and the SHA-2 hashes. FIPS 186-1 (and FIPS 186-2) DSA signatures only required data and a private key (verification only required data, signature, and a public key), FIPS 186-3 signatures also require the hash algorithm as an input... so the API isn't exactly compatible.
Windows CAPI (the older of the two Windows cryptography platforms) ignored the FIPS 186-3 update, as did Apple's Security.framework. Windows CNG and OpenSSL both support "new DSA". Apple can't process certificates signed with "new DSA" (and maybe not even with "DSA classic", I forget), and Windows doesn't support "new DSA" in cert chains, only "DSA classic".
So DSA certificates are generally limited to FIPS 186-1/186-2 restrictions, which means SHA-1 (not on anyone's good side these days) and 1024-bit keys (which are too small by today's reckoning). If you know you're being validated by OpenSSL you can use better DSA keys.
DSA is also generally much slower at signature verification than RSA.
At the 80 bits of security level, using OpenSSL's speed tool on a random VM of mine (output slightly modified for presentation purposes, sorted by verify/s descending):
sign verify sign/s verify/s
rsa 1024 bits 0.000301s 0.000018s 3326.3 56419.7
dsa 1024 bits 0.000309s 0.000236s 3236.2 4240.5
ecdsa 160 bits (secp160r1) 0.0005s 0.0004s 1984.6 2385.7
112 bits of security
sign verify sign/s verify/s
rsa 2048 bits 0.002030s 0.000062s 492.6 16062.4
ecdsa 224 bits (nistp224) 0.0001s 0.0002s 9020.6 4252.2
dsa 2048 bits 0.000885s 0.000802s 1129.4 1247.3
128 bits of security
sign verify sign/s verify/s
rsa 3072 bits 0.006935s 0.000135s 144.2 7401.6
ecdsa 256 bits (nistp256) 0.0001s 0.0002s 16901.5 5344.7
ecdsa 256 bits (brainpoolP256t1) 0.0010s 0.0008s 980.1 1262.5
ecdsa 256 bits (brainpoolP256r1) 0.0010s 0.0008s 1012.9 1209.5
dsa 3072 bits (not in test suite)
192 bits of security
sign verify sign/s verify/s
rsa 7680 bits 0.122805s 0.000820s 8.1 1220.2
ecdsa 384 bits (nistp384) 0.0024s 0.0018s 416.1 571.2
ecdsa 384 bits (brainpoolP384t1) 0.0024s 0.0018s 410.0 545.1
ecdsa 384 bits (brainpoolP384r1) 0.0025s 0.0019s 407.4 540.1
dsa 7680 bits (beyond FIPS 186-3 DSA maximum of 3072 bits)
256 bits of security
sign verify sign/s verify/s
ecdsa 521 bits (nistp521) 0.0006s 0.0012s 1563.1 841.3
ecdsa 512 bits (brainpoolP512t1) 0.0038s 0.0027s 265.2 369.1
ecdsa 512 bits (brainpoolP512r1) 0.0038s 0.0028s 262.4 360.5
rsa 15360 bits 0.783846s 0.003190s 1.3 313.5
dsa 15360 bits (beyond FIPS 186-3 DSA maximum of 3072 bits)
I'm able to use only RSA and ECDsa in CertificateRequest calls. Are there any reasons why DSA is not included?
From the thread with the original feature proposal:
Based on new data from Windows (and their lack of support for FIPS 186-3 DSA certificates) I'm going to pull the DSA typed constructor and leave DSA as a "power user" scenario (custom X509SignatureGenerator class, etc)
So, it was removed mainly because DSA is dying.
Or am I missing something in the API?
The API allows for custom signature generators to be provided. In the tests for CertificateRequest it proves this out with a DSAX509SignatureGenerator
X509SignatureGenerator dsaGen = new DSAX509SignatureGenerator(dsaCsp);
// Use SHA-1 because that's all DSACryptoServiceProvider understands.
HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
CertificateRequest request = new CertificateRequest(
new X500DistinguishedName($"CN={KeyName}-{provType}"),
dsaGen.PublicKey,
hashAlgorithm);
DateTimeOffset now = DateTimeOffset.UtcNow;
using (X509Certificate2 cert = request.Create(request.SubjectName, dsaGen, now, now.AddDays(1), new byte[1]))
using (X509Certificate2 certWithPrivateKey = cert.CopyWithPrivateKey(dsaCsp))
using (DSA dsa = certWithPrivateKey.GetDSAPrivateKey())
{
byte[] signature = dsa.SignData(Array.Empty<byte>(), hashAlgorithm);
Assert.True(dsaCsp.VerifyData(Array.Empty<byte>(), signature, hashAlgorithm));
}
(snippet from https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/PrivateKeyAssociationTests.cs#L276-L295)
I am using:
Windows 10 (Version 1709, OS Build 17025.1000)
.net framework 4.7
VS 2017 (version: 15.3.5)
Here is what I did:
Got a self signed ECC certificate using OpenSSL and steps outlined in the script at https://gist.github.com/sidshetye/4759690 with modifications:
a) Used NIST/P-256 curve over a 256 bit prime field
b) Used SHA-256
Load the certificate from file (generated in previous step) into X509Certificate2 object
Imported the PFX file into windows trust store (for testing). This is successful.
Inspection of the imported certificate shows Public Key field as 'ECC (256 Bits)' and Public key parameters as 'ECDSA_P256'.
Next tried to figure out how to encrypt with this certificate.
I am stuck at the last step because all the examples that use X509Certificate2 object predominantly use only RSA and I am using ECC certificate. For RSA certificate, there is a GetRSAPublicKey extention method on X509Certificate2 and RSA class has Encrypt method. However there is no such method for ECC certificates.
Next, I stumbled on this post (Load a Certificate Using X509Certificate2 with ECC Public Key) and tried following (even though it appeared bizarre as to why ECC cert public key is being coerced into RSA type):
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key
I got following exception: The certificate key algorithm is not supported.
Next I stumbled on this post (Importing ECC-based certificate from the Windows Certificate Store into CngKey) which basically tried to create CNGKey type and instantiate ECDsaCng with it. However even if I can do it with ECDiffieHellmanCng, there is no Encrypt method on it.
So I am not really sure how can I proceed further to use ECC X509 certificate's public key to encrypt data.
###Background
Asymmetric algorithms have three different purposes (that I know of)
Encryption
RSA is the only "standard" algorithm that can do this directly.
Signature
RSA
DSA
ECDSA
ElGamal Signature
Key Agreement
Diffie-Hellman (DH)
ECDH
ElGamal encryption (the asymmetric startup phase)
MQV
ECMQV
Because RSA encryption is space limited, and was hard for computers in the '90s, RSA encryption's primary use was in "Key Transfer", which is to say that the "encrypted message" was just the symmetric encryption key for DES/3DES (AES not yet having been invented) - https://www.rfc-editor.org/rfc/rfc2313#section-8.
Key agreement (or transfer) schemes always have to be combined with a protocol/scheme to result in an encryption operation. Such schemes include
TLS (nee SSL)
CMS or S/MIME encrypted-data
IES (Integrated Encryption Scheme)
ECIES (Elliptic Curve Integrated Encryption Scheme)
ElGamal encryption (holistically)
PGP encryption
So what you probably want is ECIES.
ECIES.Net
Currently (.NET Framework 4.7.1, .NET Core 2.0) there's no support to get an ECDiffieHellman object from a certificate in .NET.
Game over, right? Well, probably not. Unless a certificate carrying an ECDH key explicitly uses the id-ecDH algorithm identifier (vs the more standard id-ecc one) it can be opened as ECDSA. Then, you can coerce that object into being ECDH:
using (ECDsa ecdsa = cert.GetECDsaPublicKey())
{
return ECDiffieHellman.Create(ecdsa.ExportParameters(false));
}
(a similar thing can be done for a private key, if the key is exportable, otherwise complex things are required, but you shouldn't need it)
Let's go ahead and carve off the recipient public object:
ECDiffieHellmanPublicKey recipientPublic = GetECDHFromCertificate(cert).PublicKey;
ECCurve curve = recipientPublic.ExportParameters().Curve;
So now we turn to http://www.secg.org/sec1-v2.pdf section 5.1 (Elliptic Curve Integrated Encryption Scheme)
###Setup
Choose ANSI-X9.63-KDF with SHA-2-256 as the hash function.
Choose HMAC–SHA-256–256.
Choose AES–256 in CBC mode.
Choose Elliptic Curve Diffie-Hellman Primitive.
You already chose secp256r1.
Hard-coded. Done.
Point compression's annoying, choose not to use it.
I'm omitting SharedInfo. That probably makes me a bad person.
Not using XOR, N/A.
###Encrypt
Make an ephemeral key on the right curve.
ECDiffieHellman ephem = ECDiffieHellman.Create(curve);
We decided no.
ECParameters ephemPublicParams = ephem.ExportParameters(false);
int pointLen = ephemPublicParams.Q.X.Length;
byte[] rBar = new byte[pointLen * 2 + 1];
rBar[0] = 0x04;
Buffer.BlockCopy(ephemPublicParams.Q.X, 0, rBar, 1, pointLen);
Buffer.BlockCopy(ephemPublicParams.Q.Y, 0, rBar, 1 + pointLen, pointLen);
Can't directly do this, moving on.
Can't directly do this, moving on.
Since we're in control here, we'll just do 3, 4, 5, and 6 as one thing.
KDF time.
// This is why we picked AES 256, HMAC-SHA-2-256(-256) and SHA-2-256,
// the KDF is dead simple.
byte[] ek = ephem.DeriveKeyFromHash(
recipientPublic,
HashAlgorithmName.SHA256,
null,
new byte[] { 0, 0, 0, 1 });
byte[] mk = ephem.DeriveKeyFromHash(
recipientPublic,
HashAlgorithmName.SHA256,
null,
new byte[] { 0, 0, 0, 2 });
Encrypt stuff.
byte[] em;
// ECIES uses AES with the all zero IV. Since the key is never reused,
// there's not risk in that.
using (Aes aes = Aes.Create())
using (ICryptoTransform encryptor = aes.CreateEncryptor(ek, new byte[16]))
{
if (!encryptor.CanTransformMultipleBlocks)
{
throw new InvalidOperationException();
}
em = encryptor.TransformFinalBlock(message, 0, message.Length);
}
MAC it
byte[] d;
using (HMAC hmac = new HMACSHA256(mk))
{
d = hmac.ComputeHash(em);
}
Finish
// Either
return Tuple.Create(rBar, em, d);
// Or
return rBar.Concat(em).Concat(d).ToArray();
###Decrypt
Left as an exercise to the reader.
For getting ECDiffieHellman private key from certificate, use the following method:
Install NuGet package Security.Cryptography (CLR Security). (The package is under MIT license.)
Use the following extension method to get the CngKey instance:
CngKey cngKey = certificate.GetCngPrivateKey();
(Note: The extension method certificate.GetECDsaPrivateKey(), natively supported in .NET, returns an ECDsaCng instance; there is no extension method to return ECDiffieHellmanCng.)
The cngKey instance can be used to create either an ECDsaCng or an ECDiffieHellmanCng instance:
var sa = new ECDsaCng(cngKey);
var sa = new ECDiffieHellmanCng(cngKey);
I'm trying to generate a self-signed certificate on the fly (programmatically) in a C# assembly (targeting .NET 4.0), to serve as a root CA to generate other certificates. The certificate doesn't need to be persisted in the Windows certificate store, I'll export it as a file.
Reading through this question (and in particular, #dthorpe's answer), I decided to give a try to CLR Security.
The CLR Security library put an extension method on CngKey class to generate a self-signed certificate, but I couldn't succeed in creating an instance of CngKey with:
var key = CngKey.Create(CngAlgorithm.Sha1); //same with Sha256, Sha512 and MD5
//or
var key = CngKey.Create(CngAlgorithm.Sha1, null, new CngKeyCreationParameters()
{
ExportPolicy = CngExportPolicies.AllowExport,
KeyUsage = CngKeyUsages.AllUsages,
KeyCreationOptions = CngKeyCreationOptions.MachineKey,
});
Any of these lines raises the exception:
System.Security.Cryptography.CryptographicException was unhandled
HResult=-2146893783
Message=The requested operation is not supported.
Source=System.Core
StackTrace:
at System.Security.Cryptography.NCryptNative.CreatePersistedKey(SafeNCryptProviderHandle provider, String algorithm, String name, CngKeyCreationOptions options)
at System.Security.Cryptography.CngKey.Create(CngAlgorithm algorithm, String keyName, CngKeyCreationParameters creationParameters)
at System.Security.Cryptography.CngKey.Create(CngAlgorithm algorithm)
at Tests.Program.Main(String[] args) at Program.cs:line 51
Searching through SO and the internet, I've checked the following:
I'm running a Windows 7 box (so it supports RPC as per MSDN)
Tried on a Windows Server 2012 box, same error
The process is running as admin (so it have access to all cert storages, anyway)
The services CNG Key Isolation and Remote Procedure Call (RPC) are running
Any help would be appreciated.
Small off-topic: during google search for this question found a site with HRESULT descriptions and handy search tool on SO and MSDN (I simply googled for your HRESULT code -2146893783)
I found a topic on MSDN which contains code failing with similar HRESULT, and the author provides a link to MSDN article about CNG:
NCRYPT_ALGORITHM_GROUP_PROPERTY
L"Algorithm Group"
A null-terminated Unicode string that contains the name of the object's algorithm group. This property only applies to keys. The following identifiers are returned by the Microsoft key storage provider:
NCRYPT_RSA_ALGORITHM_GROUP
"RSA", The RSA algorithm group.
NCRYPT_DH_ALGORITHM_GROUP
"DH", The Diffie-Hellman algorithm group.
NCRYPT_DSA_ALGORITHM_GROUP
"DSA", The DSA algorithm group.
NCRYPT_ECDSA_ALGORITHM_GROUP
"ECDSA", The elliptic curve DSA algorithm group.
NCRYPT_ECDH_ALGORITHM_GROUP
"ECDH", The elliptic curve Diffie-Hellman algorithm group.
Also I found an article on MSDN about CNG Key Storage Providers, which contains similar list of the algorithms:
Diffie-Hellman (DH)
Secret agreement and key exchange, 512 to 4096 in 64-bit increments
Digital Signature Algorithm (DSA)
Signatures, 512 to 1024 in 64-bit increments
Elliptic Curve Diffie-Hellman (ECDH)
Secret agreement and key exchange, P256, P384, P521
Elliptic Curve Digital Signature Algorithm (ECDSA)
Signatures, P256, P384, P521
RSA
Asymmetric encryption and signing, 512 to 16384 in 64-bit increments
So, as you've said that you've tried only Sha1, Sha256, Sha512 and MD5, maybe you simply use another algorithm from list available? You can find there ones mentioned above:
RSA
ECDsa
P256
P384
P521
ECDiffieHellman
P256
P384
P521
Here other developers successfully created one of them and was able to export it:
var cngKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256, null,
new CngKeyCreationParameters { ExportPolicy = CngExportPolicies.AllowPlaintextExport });
How do you set the key size on the RSA class?
There is no key size option with RSA.Create() and setting KeySize on RSA after it has been created does not have any effect.
If you are using .NET Framework:
There's no provider-unaware solution. You have to use the RSACryptoServiceProvider(int) constructor or intentionally create an RSACng object.
If you are using .NET Core:
RSA rsa = RSA.Create();
rsa.KeySize = someValue
is the right approach, it works on all possible answers for RSA.Create().
If you are using Mono:
I don't know which behavior it matches.
If you are from the future:
https://github.com/dotnet/corefx/issues/8688 is tracking the future addition of RSA.Create(int) (and RSA.Create(RSAParameters)) to help solve this problem.
A scoped method which requires cross-compiling:
(Defining NETFX correctly for your build and lining that up in a nuget package is an exercise left to the reader)
internal static RSA RsaCreate(int keySize)
{
#if NETFX
// If your baseline is .NET 4.6.2 or higher prefer RSACng
// or 4.6+ if you are never giving the object back to the framework
// (4.6.2 improved the framework's handling of those objects)
// On older versions RSACryptoServiceProvider is the only way to go.
return new RSACng(keySize);
#else
RSA rsa = RSA.Create();
rsa.KeySize = keySize;
if (rsa.KeySize != keySize)
throw new Exception("Setting rsa.KeySize had no effect");
return rsa;
#endif
}
And, of course, if you're from the future you could call the new Create overload directly in a higher precedence #if.
RSA is just an abstract class for RSA implementations. You should use RSACryptoServiceProvider.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(bitSize);
Note that the key doesn't get generated until you attempt to use it, so don't put the constructor by itself in a background worker etc.