I'm using RSACryptoServiceProvider from System.Security.Cryptography package. I need to decrypt some input coming from Android application using my private key. It's encrypting it in Java code with RSA/ECB/OAEPWithSHA256AndMGF1Padding algorithm. The problem is I cannot find any way to set nither padding MGFT1 nor ECB.
Could you help me somehow? Are these settings default or should I use another library? It's hard to believe it's impossible.
Crucial part of my code goes here:
RSAParameters RSAParamPrivateKey = DotNetUtilities.ToRSAParameters(privateKey)
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.ImportParameters(RSAParamPrivateKey);
return csp.Decrypt(encryptedAESKey, RSAEncryptionPadding.OaepSHA256);
The "ECB" is (hopefully) meaningless. RSA should only ever be used for a one block of data.
MGF1 is the only standard defined MGF, so it's not an option that .NET lets you currently specify.
OAEP with an algorithm other than SHA-1 is beyond RSACryptoServiceProvider's capabilities. But RSACng can do it:
RSAParameters RSAParamPrivateKey = DotNetUtilities.ToRSAParameters(privateKey);
RSA rsa = new RSACng();
rsa.ImportParameters(RSAParamPrivateKey);
return rsa.Decrypt(encryptedAESKey, RSAEncryptionPadding.OaepSHA256);
Related
I'm attempting to apply an rsa-sha512 signature to a message, using a certificate on the local HDD.
The final SignData raises a cryptographic exception "Invalid algorithm specified".
However, if I use SignData on a new instance of RSACryptoServiceProvider (created by importing an export of the original RSACryptoServiceProvider), I don't get that exception.
Is there some reason that the original version raises the exception? Since the "copy" is evidently different, I'd prefer to use the original.
The c# code I'm using is as follows:
X509Certificate2 cert = new X509Certificate2("C:\\Certs\\" + certName + ".p12", certPassword, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey;
UTF8Encoding ByteConverter = new UTF8Encoding();
byte[] unsignedBytes = ByteConverter.GetBytes(unsignedText);
byte[] signature;
//This raises an exception, "Invalid algorithm specified."
signature = csp.SignData(unsignedBytes, new SHA512CryptoServiceProvider());
//But if I make a copy of the RSACryptoServiceProvider, no exception is raised
RSACryptoServiceProvider cspCopy = new RSACryptoServiceProvider();
RSAParameters Key = csp.ExportParameters(true);
cspCopy.ImportParameters(Key);
signature = cspCopy.SignData(unsignedBytes, new SHA512CryptoServiceProvider());
To quote myself from a previous answer:
Software-backed RSACryptoServiceProvider is only capable of doing RSA signature using a SHA-2 digest algorithm when the CSP ProviderType value is 24 (PROV_RSA_AES) and the ProviderName is "Microsoft Enhanced RSA and AES Cryptographic Provider" (MS_ENH_RSA_AES_PROV). Hardware may or may not require PROV_RSA_AES, just depends on the hardware.
In this case, the PFX identifies the private key to belong to an older CSP (or, maybe, it has no CSP identifier and the PFX import is picking bad defaults). For software keys it's possible to extract the CspParameterInfo data from the key and re-open it using ProviderType 24, which is one way to work around the problem. Exporting the raw RSA parameters and importing it into a new object (which defaults to ProviderType 24) is a more aggressive workaround.
The better way to work around the problem is to abandon RSACryptoServiceProvider. Instead of using cert.PrivateKey, use cert.GetRSAPrivateKey(), which will almost always return an RSACng instance, which doesn't have this problem (but don't cast it if you can avoid it (if nothing else, see "almost" always)).
byte[] signature;
using (RSA rsa = cert.GetRSAPrivateKey())
{
signature = rsa.SignData(
unsignedBytes,
HashAlgorithmName.SHA512,
RSASignaturePadding.Pkcs1);
}
The using statement is correct for GetRSAPrivateKey, because it returns a distinct object per call.
RSACng and GetRSAPrivateKey both require .NET 4.6, but that's more than two years old at this point (and 4 (and a half) newer releases have happened in that time), so shouldn't cause you difficulty as a dependency.
I have the following c# Bouncy Castle code to sign some data in C# using RSA/SHA512. Can any tell me how I can also apply Pkcs1 padding in this process?
using (var txtreader = new StringReader(File.ReadAllText(_certificatePath)))
{
var keyPair = (AsymmetricCipherKeyPair)new PemReader(txtreader).ReadObject();
var key = keyPair.Private as RsaPrivateCrtKeyParameters;
ISigner sig = SignerUtilities.GetSigner("SHA512withRSA");
sig.Init(true, key);
sig.BlockUpdate(requestToSign, 0, requestToSign.Length);
byte[] signature = sig.GenerateSignature();
}
PKCS#1 is actually the only padding algorithm used for RSA-SHA512 signing.
Check RFC 4051:
2.3.4. RSA-SHA512
This implies the PKCS#1 v1.5 padding algorithm [RFC3447] as described in section 2.3.1, but with the ASN.1 BER SHA-512 algorithm designator prefix.
You could also check BouncyCastle source code. RsaDigestSigner creates the instance of Pkcs1Encoding without any conditions or configuration:
private readonly IAsymmetricBlockCipher rsaEngine = new Pkcs1Encoding(new RsaBlindedEngine());
You could also check used implementation under debug:
So answering the question:
Can any tell me how I can also apply Pkcs1 padding in this process?
No additional actions should be taken. Pkcs1 is the only possible padding algorithm for RSA-SHA512 signing.
I have DER encoded RSA keypair created in Crypto++, as well as cipher. They are Base64Encoded string. I first decode the data from Base64 to byte array, but I am not sure how to load them into RSACryptoServiceProvider.
static void Main()
{
string pbkeystr = "mypublickey";
string pvkeystr = "myprivatekey";
string cipherstr = "mycipher";
byte[] pbkey = Convert.FromBase64String(pbkeystr);
byte[] pvkey = Convert.FromBase64String(pvkeystr);
byte[] cipher = Convert.FromBase64String(cipherstr);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
//Set keys here..
//Decrypt the cipher using private key
rsa.Decrypt(pvkey, false);
}
There are no functions to set keys. The only thing I found was ImportParameters method, which takes RSAParameters class which consists of p, q, n, modulus, exponent etc. I don't have access to these.
Is there any way I can load the keys as string? How can I load the key into RSACryptoServiceProvider?
Is there any way I can load the keys as string? How can I load the key into RSACryptoServiceProvider?
From your other Crypto++ question, How to load Base64 RSA keys in Crypto++, it looks like you have only the public and private keys because you used DEREncode and BERDecode. That is, you have the RSA parameters, and not the subject public key info and the private key info. Your keys lack the OID identifiers and version numbers. Things are fine that way.
From Cryptographic Interoperability: Keys on the Code Project, you will need a C# class that parses the ASN.1/DER after you Base64 decode it. The CodeProject article provides a C# class called AsnKeyParser to read the ASN.1/DER and returns a RSAParameters to load into a CSP.
The code for the AsnKeyParser class is about 800 lines, and there are five other supporting files to make it all happen, so its not really appropriate to place it here. You should download it yourself. The file of interest is called CSInteropKeys.zip.
Once you wire-in the AsnKeyParser class, it will be as simple as the following for a RSA Public key. The private key will be similar, and the code is given on the CodeProject site.
// Your ASN.1/DER parser class
AsnKeyParser keyParser = new AsnKeyParser("rsa-public.der");
RSAParameters publicKey = keyParser.ParseRSAPublicKey();
// .Net class
CspParameters csp = new CspParameters;
csp.KeyContainerName = "RSA Test (OK to Delete)";
csp.ProviderType = PROV_RSA_FULL; // 1
csp.KeyNumber = AT_KEYEXCHANGE; // 1
// .Net class
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
rsa.PersistKeyInCsp = false;
rsa.ImportParameters(publicKey);
Linking to files on another site is frowned upon, but I don't know how to provide the information otherwise. There's too much source code involved to place in an answer.
For completeness, .Net does not make interop easy. They do not accept ASN.1/DER or PEM. Rather, .Net accepts some XML representation of the keys. I believe you can find it in RFC 3275, XML-Signature Syntax and Processing. Microsoft does not state that for you. I kind of pieced it together when I wrote the Code Project article.
Maybe we should add a class to Crypto++ to regurgitate XML in addition to ASN.1/DER and PEM.
I am trying to use the RSACryptoServiceProvider to encrypt/decrypt. Encrypting works fine, but the Decrypt method throws an exception with the message:
Unknown Error '80007005'.
This is the code:
Byte[] plainData = encoding.GetBytes(plainText);
Byte[] encryptedData;
RSAParameters rsap1;
Byte[] decryptedData;
using (RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider())
{
encryptedData = rsa1.Encrypt(plainData, false);
rsap1 = rsa1.ExportParameters(false);
}
using (RSACryptoServiceProvider rsa2 = new RSACryptoServiceProvider())
{
rsa2.ImportParameters(rsap1);
decryptedData = rsa2.Decrypt(encryptedData, false);
}
decryptedText = encoding.GetString(decryptedData, 0, decryptedData.Length);
Is anyone aware of a workaround?
Thanks!
Fixed the code! I guess I do not need to specify a container after all...
Byte[] plainData = encoding.GetBytes(plainText);
Byte[] encryptedData;
Byte[] decryptedData;
using (RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider())
{
RSAParameters rsap1 = rsa1.ExportParameters(false);
using (RSACryptoServiceProvider rsa2 = new RSACryptoServiceProvider())
{
rsa2.ImportParameters(rsap1);
encryptedData = rsa2.Encrypt(plainData, false);
}
decryptedData = rsa1.Decrypt(encryptedData, false);
}
decryptedText = encoding.GetString(decryptedData, 0, decryptedData.Length);
rsap1 = rsa1.ExportParameters(false);
By passing false to this method, you're choosing to not export the private key. Without the private key it will be difficult to decrypt the data. Try passing true to the export method.
When using RSA you need to understand the basics of key management. You did not specify what key container to use during encryption. What key do you expect to be used? The default user key? The machine key? Do you understand what the default user key and the machine keys are ? Not to mention the obvious question of why do you encrypt anything with RSA? RSA encryption is used solely for encrypting session keys, and there are dedicated key exchange protocols that take care of this out-of-the-box (stream oriented like TLS or document oriented like S/MIME). You should use one of these out-of-the-box protocols and not roll your own encryption scheme. You will screw up key management, that is guaranteed.
When you attempt to decrypt, does the decryptor has possession of the private key corresponding to the public key used during encryption?
See:
How to: Store Asymmetric Keys in a Key Container
Encrypting Data
Decrypting Data
Note that these are just simple code samples in MSDN and should never be used by anyone without a very deep understanding of cryptography, and specially key management.
I recommend you look into using a high level class like SslStream for encrypting data exchanges. For a document storage encryption scheme you better use the OS facilities or rely on ProtectedData class. Again, do not roll your own encryption unless you really know what you're doing (in which case you wouldn't be asking questions here).
Currently I am receiving the following error when using Java to decrypt a Base64 encoded RSA encrypted string that was made in C#:
javax.crypto.BadPaddingException: Not PKCS#1 block type 2 or Zero padding
The setup process between the exchange from .NET and Java is done by creating a private key in the .NET key store then from the PEM file extracted, created use keytool to create a JKS version with the private key. Java loads the already created JKS and decodes the Base64 string into a byte array and then uses the private key to decrypt.
Here is the code that I have in C# that creates the encrypted string:
public string Encrypt(string value) {
byte[] baIn = null;
byte[] baRet = null;
string keyContainerName = "test";
CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = keyContainerName;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp);
// Convert the input string to a byte array
baIn = UnicodeEncoding.Unicode.GetBytes(value);
// Encrypt
baRet = rsa.Encrypt(baIn, false);
// Convert the encrypted byte array to a base64 string
return Convert.ToBase64String(baRet);
}
Here is the code that I have in Java that decrypts the inputted string:
public void decrypt(String base64String) {
String keyStorePath = "C:\Key.keystore";
String storepass = "1234";
String keypass = "abcd";
byte[] data = Base64.decode(base64String);
byte[] cipherData = null;
keystore = KeyStore.getInstance("JKS");
keystore.load(new FileInputStream(keyStorePath), storepass.toCharArray());
RSAPrivateKey privateRSAKey = (RSAPrivateKey) keystore.getKey(alias, keypass.toCharArray());
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateRSAKey);
cipherData = cipher.doFinal(data);
System.out.println(new String(cipherData));
}
Does anyone see a step missing or where the padding or item needs to be changed? I have done hours of reading on this site and others but haven't really found a concrete solution.
You're help is vastly appreciated.
Thanks. -Matt
I had exactely the same problem and I finally find the solution!
I was stubborn using PKCS1Padding but I didn't manage to make it work.
The best result I got using "rsa.Encrypt(baIn, false)" on the C# side and "RSA/NONE/NoPadding" on the Java side was this kind of string : "☻?o+_>??5?l0Q*???*?R▲???♀7..." followed by my decrypted string. So in a way it got decrypted but since there is no padding specified, the data is shifted. So I tried all the paddings available in bouncycastle but I would alway get errors such as "block incorrect size" or "data hash wrong".
So I decided to start trying OAEP paddings and I finally managed to get it working by using "rsa.Encrypt(baIn, true)" on the C# side and "RSA/NONE/OAEPWithSHA1AndMGF1Padding" on the java side!
It worked for me, I hope it will work for you too! If it doesn't work make sure you're using the right key, very often the problem comes from the key.
Check that you have correctly exchanged the key.
Trying to decrypt with an incorrect key is indistinguishable from decrypting badly padded data.
I'm working through a similar problem operating between .Net and iPhone stuff in Objective - C, and I think the answer lies in this little gem from the RSACryptoServiceProvider documentation:
Unlike the RSA implementation in unmanaged CAPI, the RSACryptoServiceProvider class reverses the order of an encrypted array of bytes after encryption and before decryption. By default, data encrypted by the RSACryptoServiceProvider class cannot be decrypted by the CAPI CryptDecrypt function and data encrypted by the CAPI CryptEncrypt method cannot be decrypted by the RSACryptoServiceProvider class.
See here for more details: http://msdn.microsoft.com/en-us/library/s575f7e2(v=VS.90).aspx
I had the same problem when using Bouncy Castle 1.48 but it wasn't key-related. Instead, I found that I had to set the following system property:
-Dorg.bouncycastle.pkcs1.strict=false