Bad Key Exception using RSACryptoServiceProvider - c#

The key exchange method is: RSA-PKCS1-KeyEx
So I don't think the answers in other similar questions apply and I have no control over the certificate - it's valid and is supposed to work.
.NET Framework 3.5
The code:
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PublicKey.Key;
byte[] bytestodecrypt = Convert.FromBase64String(SignatureValue);
byte[] plainbytes = rsa.Decrypt(bytestodecrypt, false);
Screenshot of the attributes of the rsa object:

While RSA can do both signatures and encryption, they can't be used interchangeably (except for at the raw/"no padding" version of the algorithm, which .NET does not support; and even then the keys are "backwards").
The signature blob is computed by "encrypting" with the private key. It gets "decrypted" by the public key, and then the underlying system compares it with the provided hash and hash algorithm. If they both match, it returns true; otherwise it returns false (or might throw an exception if it gets really confused).
So rather than rsa.Decrypt(signature, false) you want rsa.VerifyHash(hash, signature, hashAlgorithm, RSASignaturePadding.Pkcs1), which returns a bool.
The "bad key" exception is because you only have a public key (which is sufficient for verify) but called decrypt (which requires the private key).

Related

Key is not a valid public or private key

I am trying to import key pair created by RSACryptoServiceProvider into ECDiffieHellmanOpenSsl:
RSACryptoServiceProvider keyPair = EncryptionTools.GenerateRSAKeyPair(4096);
ECDiffieHellmanOpenSsl alice = new ECDiffieHellmanOpenSsl();
int publicBytesReadAlice = 0;
alice.ImportSubjectPublicKeyInfo(new ReadOnlySpan<byte>(keyPair.ExportSubjectPublicKeyInfo()), out publicBytesReadAlice);formát
int privateBytesRead = 0;
alice.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(keyPair.ExportPkcs8PrivateKey()), out privateBytesRead);
It is throwing this error on fifth line:
System.Security.Cryptography.CryptographicException: Key is not a valid public or private key.
I don't know where the issue is because docs say:
ECDiffieHellman.ImportSubjectPublicKeyInfo: Imports the public key from an X.509 SubjectPublicKeyInfo structure after decryption, replacing the keys for this object. Source is: The bytes of an X.509 SubjectPublicKeyInfo structure in the ASN.1-DER encoding.
AsymmetricAlgorithm.ExportSubjectPublicKeyInfo: Exports the public-key portion of the current key in the X.509 SubjectPublicKeyInfo format.
The same thing happens when trying to import RSA private key, which is supposed to be in Pkcs8 format.
Any idea how to correctly export/serialize keys and pass it to ECDiffieHellmanOpenSsl? And any idea how to serialize it to string?
RSA keys and ECDH-compatible keys are extremely different beasts. It's not even quite "square peg, round hole", but more like "color green, round hole". They're both asymmetric keys, which means they're both compatible with the SPKI and PKCS8 formats, though. (Both of those formats are basically "here's the kind of key I am" and "here's some data of the format appropriate to that key")
ECDH can load in any key created by an ECDsa or ECDH instance.
ECDsa can load in any key created by an ECDsa instance, and most keys created by an ECDH instance.
RSA can only load keys created by an RSA instance.
DSA can only load keys created by a DSA instance.
(for all of those statements the keys can also be created by a compatible notion on some other platform or some other library set).
So, if you want to serialize an ECDH, just start by creating an ECDH (e.g. ECDiffieHellman.Create(ECCurve.NamedCurves.nistP521)).
For text versions, .NET 7 is adding straight-to-PEM versions (e.g. key.ExportSubjectPublicKeyInfoPem()). For earlier versions you can use PemEncoding, e.g. PemEncoding.Write("PUBLIC KEY", key.ExportSubjectPublicKeyInfo()).

RSA Crypto: Is it require to store keypair after geneating first time

Once public/private key generated then, is this has to store or persist on storage So, when decryption call then same private key (related to public key) is used ?
Basically, there will be two separate endpoints for RSA encrypt and decryption.
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
var pu = rsa.ToXmlString(false);
var pr = rsa.ToXmlString(true);
}
Yes, in almost all cases you need to store the private key permanently. You could use RSA for one-time key establishment. However, creating a key pair is relatively slow for RSA, so RSA is not commonly used for this.
Note that you may also create a key pair where the private key remains in a key container such as a key store. For instance, most smart cards have been designed in such a way that the private key never leaves the device; it is simply used when required. The key is automatically persisted after creation. So you would not be able to retrieve the values of them and convert them to XML.
Storing keys in the Microsoft proprietary XML format without any protection - as performed in the code within the question - is not a good idea.

C# Verify Json string via signature and RSA public key

I'm creating an IAP plugin for the local Android market that I think has similar API as google play.
I build an Android side and it will return all response from the market to the unity c# side.
All part working fine but I can't figure out how to verify the signature of the response.
I'm new in cryptography and searching day to day about this.
Please correct me if I'm wrong.
They use a hash algorithm to sign data and encrypt that hash with private key.
I must decrypt signature with public key and compare hashes.
This is my public key (Base64):
MIHNMA0GCSqGSIb3DQEBAQUAA4G7ADCBtwKBrwDltnT/AaF3oMt+F3lza5JEvce0GLS1L9v9Z53lQ3LNluGk0eI2ukgWm7tAiIOLQgn11Sb9mW2VWkYTWGnZ1AZtY0GwdUQJUr7u3CWNznE6XH4UCVOVhGDCLnFrG8BcfDelhcfReGZQ3izOefhc4Oq6vZf5PfLwximK+FH27fR6XL8vg3yyK4LSwT764Dfd6H3IGes6EdTx/C3C690jdyMvhi2Q3qBiqfepHzW/jV8CAwEAAQ==
This key is in ASN.1 DER format.
I break it out and find this data:
SEQUENCE
SEQUENCE
OBJECT IDENTIFIER 1.2.840.113549.1.1.1 rsaEncryption(PKCS #1)
NULL
BIT STRING
SEQUENCE
INTEGER 969837669069837851043825067021609343597253227631794160042862620526559…
INTEGER 65537
As I read on net first INTEGER is Modules and second INTEGER is Exponent
So in c# write a code like this =>
var parameter = new RSAParameters
{
Modulus = HexToByteArray(/* "first_INTEGET" */),
Exponent = BitConverter.GetBytes(/* "second_INTEGER" */)
};
Market send me a Json like this:
{"orderId": "0j8oJgE0Bett-neB", "purchaseToken": "0j8oJgE0Bett-neB", "developerPayload": "payload", "packageName": "com.some.market", "purchaseState": 0, "purchaseTime": 1520676644872, "productId": "card-1"}
The signature is like this:
hTFeQd25PZJ2DhGmXd0eO+C+oBeWsg983I4e5ztXtKAUrOIaNBaqAxHU3vW8acBs1I9fE5cxx/DI/sQGY4QSvpDnSm9aYz3do3joHPOXIVvXjSJfejxwzp9DKMUPd6LrgtxkaGevG+94NuKHFxpCdZlovEPXRJZyEznbASuYLqeW0KjP3jnvvw2O5iNlQRdh98h4Q18bSsaxq9zaRKExFLHkhNf/yO5m84kRB1G8
I'm searched for a method to do this but I don't know which method is true for me.
My verify code is this:
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(parameter);
var hash = new SHA1Managed();
bool dataOK =
rsa.VerifyData(hash.ComputeHash(Encoding.UTF8.GetBytes(json)), CryptoConfig.MapNameToOID("SHA1"), Encoding.UTF8.GetBytes(signature));
}
How truly convert signature to byte[] to verify? (with encoding or what???)
I'm searching a lot but more search more confuse.
Am I going the wrong way or using wrong method or ...?
Why should workflow be complicated?
Can anyone help me, please?
Thanks.
OK, I'll answer in order:
They use a hash algorithm to sign data and encrypt that hash with private key. I must decrypt signature with public key and compare hashes.
No, that's not correct. You should use a signature verification method, as you're currently doing. Seeing signature as encryption of a hash is incorrect; even the latest RSA standards go out of their way to explain this. For RSA the internal padding method is different. For ECDSA there is no direct encryption/decryption possible using the same scheme.
As I read on net first INTEGER is Modules and second INTEGER is Exponent
Yes, although it is spelled modulus, not modules. It is the public exponent, there is also a private exponent for the private key. Also without the caps.
How truly convert signature to byte[] to verify? (with encoding or what???)
Standard Base 64 is already mentioned in the comment section. Note that the key and signature size are not common (but that's OK in itself).
Why should workflow be complicated?
Well, somebody has to code it in the end and crypto is hard. But to make it easier for you: the entire ASN.1 structure is called a SubjectPublicKeyInfo structure; if you look on the internet you will find pre-made code to import from such a structure.
To finally verify the structure: make sure that you use the correct signature format (RSA-PKCS#1 v1.5 or RSA-PSS) and that you know exactly which binary data is fed to the signature generation function. For instance, the signature over the JSON could be in ASCII or UTF-8 or it could be in UTF-16 LE or BE.
Better ask the creator of the signature.

AES: Use nonce to create new key for HMAC

I use AES to creature HMAC signatures for API calls and while I understand what nonces are and why they're important, I'm a little confused on where exactly they're meant to be implemented.
I had the idea of using the nonce to alter a client's secret key to generate a new single-use key to sign calls that could otherwise be easily exploited with replay attacks.
The basic premise is the User, who gets a secret key when they log in, calls a setup API procedure which creates and returns a transactionID and a new nonce. The user then combines their own secret key and the nonce to sign the TransactionID and a few other things. The server then tries to match this signature using the currently active nonce for that user.
The server automatically clears old nonces and overwrites them when new setup calls are made so the user must always follow this paired sequence (you can't just reuse the second call, since the old nonce will have been deleted and the server will no longer accept this signature).
If this is the valid way to use nonces, how do I combine them with the client's keys to get a valid Secret key?
I generate AES providers this way:
private static AesCryptoServiceProvider GetProvider(byte[] key, byte[] IV)
{
AesCryptoServiceProvider result = new AesCryptoServiceProvider();
result.BlockSize = 128;
result.KeySize = 256;
result.Mode = CipherMode.CBC;
result.Padding = PaddingMode.PKCS7;
result.IV = IV;
result.Key = key;
return result;
}
I use AES-256 so my keys are always 32 Bytes long. But if I want to create a new key using a nonce, I can't simply concatenate them since the resulting string would no longer be 32 bytes.
Should I concatenate and then use a fixed-length hash function to get the result back into a 32-byte length? Or is there an AESCryptoServiceProvider constructor/method that automatically handles nonces I'm missing?
Or am I just supposed to append nonces in plaintext into the signature and let the server read them separately and check them directly?
There are many ways to solve this. The easiest would be to use hashing. You can use HMAC or even HKDF.
SessionKey = HMAC_SHA256(SecretKey, Nonce)
Be sure to use the user's SecretKey only for deriving other keys. If you want to use this derivation for different things, then you need to bind the uses into the key:
SessionKey = HMAC_SHA256(SecretKey, "Encryption".Concat(Nonce))
SessionToken = HMAC_SHA256(SecretKey, "Token".Concat(Nonce))
This is just pseudo-code. Here are some examples of actual HMAC in C#.

Digital Signatures: Encrypting the Hash vs Signing the Hash?

I'm trying to implement SHA256-RSA digital signatures and I'm confused with the terminology and implementation in C#.
AFAIK, "signing a file" is to generate the hash of a file, and then encrypt that hash. I've also heard the phrase "signing the hash". Is this the same thing? Or is this hashing a hash and then encrypting hash'?
Here's the code in question:
public void SignatureTest(byte[] data, X509Certificate2 cert)
{
var sha256 = new SHA256CryptoServiceProvider();
var rsa = (RSACryptoServiceProvider)cert.PrivateKey;
var hashOfData = sha256.ComputeHash(data);
var encryptedHash = rsa.Encrypt(hashOfData, false);
var encryptedHashOAEP = rsa.Encrypt(hashOfData, true);
var signedHash = rsa.SignHash(hashOfData, "SHA256");
//Shouldn't one of these be true?
var false1 = CompareAsBase64Str(encryptedHash, signedHash);
var false2 = CompareAsBase64Str(encryptedHashOAEP, signedHash);
//This is the one that actually matches
var true1 = CompareAsBase64Str(signedHash, rsa.SignData(data, sha256));
}
public bool CompareAsBase64Str(byte[] b1, byte[] b2)
{
return (Convert.ToBase64String(b1) == Convert.ToBase64String(b2));
}
Here's what MSDN says on RSACryptoServiceProvider:
SignHash() Computes the signature for the specified hash value by encrypting it with the private key.
Encrypt() Encrypts data with the RSA algorithm.
Shouldnt SignHash(hash) and Encrypt(hash) be the same?
You need to separate concerns, this will help you understand the terminology.
Any arbitrary blob of data can be hashed and/or encrypted in any combination.
Hash means: use a cryptographic algorithm to generate a value that is irreversible (that is, simply by knowing algorithm and hash you are unable to reconstitute original data) and consistent (that is, given the same data and algorithm, the value of the hash produced is always the same).
Encrypt means: use a cryptographic algorithm to encipher data (altogether or in blocks) with a given key (a key can be symmetric or asymmetric).
Sign means: Hash the data and Encrypt the hash with a given key. Then, given the pair (for asymmetric) or same (for symmetric) key, a consumer can validate that:
hash is matching, that means the data has not been altered in transit
hash did come from the source that at least has the pair key (for asymmetric) or same key (for symmetric)
The answer given by zaitsman is a good explanation of the topics related to your questions and I think should be the accepted answer, but just to help tie it back to your specific question of why encrypting the hash doesn't give you the same result as signing the hash (the rsa.SignHash(hashOfData, "SHA256") in your code):
Signing a hash doesn't just encrypt the hash data -- it also encrypts the name (or some identifier) of the hashing algorithm used to generate the hash along with it. Without that, the receiver wouldn't know what algorithm to use when computing their own hash (of the message being sent) to compare with the one they just decrypted in order to verify the authenticity of the message (which, of course, is the whole point).
When you encrypted the hash yourself (with rsa.Encrypt(hashOfData, false) and rsa.Encrypt(hashOfData, true)), you only encrypted the hash data and not the combination of hash data and algorithm identifier ("SHA256" in your code). In other words, you encrypted different data, so you got different (encrypted) results.
The reason the return value of that SignHash call does match the value returned by rsa.SignData(data, sha256) is that the latter method does the same thing, except it does the hashing and hash signing as one operation, so you don't have to compute the hash as a separate step if you don't need it for any purpose other than signing it.
From RSACryptoServiceProvider.SignData Method on MSDN:
Computes the hash value of the specified data and signs it.
Also see: Why does SignHash need to know what hash algorithm was used?

Categories