4

I have an old code block that uses RSACryptoServiceProvider that I wanted to port over to use the newer RSACng. I have run into a problem that I am unable to solve: RSACng's VerifyHash method doesn't appear to verify the hash as expected. The same code and data, when used with RSACryptoServiceProvider, works fine and the verification succeeds.

I have done some investigation and .NET source stepping, eventually going all the way down to NCryptVerifySignature. I made sure the hash, signature, etc are all the same that RSACryptoServiceProvider works with. In the end, for unknown reasons, NCryptVerifySignature is not successful in verifying the hash in this case.

My code is below:

var rsaKeyPub = "BgIAAACkAABSU0ExAAgAAAMAAAC7N02Zb0lf3UO4pl3ymFvkrNSkPP0Q076vYzvbeTqS5vkBcoXloM044q1LCFtaXw6DUzSFM0IqoGOONb+PW+UeNcTcA/+MKNi7nzbBGg3kAj8QhuxO3u2QJXg62Zb9H+SEvZYfi9PhOCSo0LpWKl72k+uaoTyPAuV738TamRvXWb8XOswSmsHQa38q1Id4TW7CzvVOjc0vnhL+rZ8Po1qg5FJc8m8gdGWC0a4NJTzBsOqLzeVE12B8zgIMehu1gGw/SjY5PVEkDABWgY2DzxLT3rbs6oZ5ZLSHu041q3s1ihOQ8+GMRx3qqvPyB4JVlyd7jqN0j0dT+Yqr+8t3/Liu";
var hash = "Ow1kg47GAgf9cyZbisDuTRNy5NQ=";
var signature = "bBnFouYvuZSYZIPihDB4J/CVC7o5ej3MrbkZV9cn6vgL23rDW1jevWEHx4wGBXLc443DKrZ0XQlSpp3FE/+isyDMcGh7c0buMufiYuOQ0rbo8e4tvuZuZpt+06xnBQcYyFMqe4lkFcI0f/NeAIvy1vME+Kq4v3ikwR4+CsjObgEJIBdWB0B4cqp8355pxtYJv2BQ7UHy/Tv0+OtslgbxikrwU2CQ+tR3XHywIdzm0BEOBfdnOlky96ED18BAqwLlxjef0snCl3DvKz93gtIIQVwEoDRlKC/v/Xb4Eke/fyvt66orLEIyL8Emaer9J6P38ZB1pWRuOsLCv4ly8fnOMw==";
RSAParameters rsaParams;
using (var rsa = new RSACryptoServiceProvider())
{
    rsa.ImportCspBlob(Convert.FromBase64String(rsaKeyPub));
    //Export now for easy importing in the next function. This is not the reason the next block returns false.
    rsaParams = rsa.ExportParameters(false);
    Console.WriteLine(rsa.VerifyHash(Convert.FromBase64String(hash), Convert.FromBase64String(signature), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1));
}
using (var rsa = new RSACng())
{
    rsa.ImportParameters(rsaParams);
    Console.WriteLine(rsa.VerifyHash(Convert.FromBase64String(hash), Convert.FromBase64String(signature), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1));
}

Does anyone have any advice on what I am doing wrong? This is using the latest .NET Framework (4.7.2).

Eaton
  • 1,310
  • 2
  • 15
  • 31
  • Could you please include the code into the question? You can still link to the gystlyn thingy - that is certainly helpful - but questions on SO should be *self contained*, and your question doesn't make sense without the code. – Maarten Bodewes Mar 24 '19 at 11:31
  • Hi @MaartenBodewes, code added. Please let me know if you have any suggestions on what the problem may be. Thanks! – Eaton Mar 24 '19 at 16:15
  • I can only try by testing, and I'm stricktly out of time for today. Actually, I'm not even typing this :) Voted up though, vexing question :| I'd check for bug reports and make sure you're working on the latest versions. Maybe include version about framework in question. – Maarten Bodewes Mar 24 '19 at 17:28
  • 1
    @MaartenBodewes Updated to include framework version. It's all the latest. Maybe I'll test it on .NET Core to see if it fails there, and file a bug report. I wanted to make sure I wasn't making a dumb mistake somewhere first, though. – Eaton Mar 24 '19 at 18:04
  • Can you print out the public exponent? There may be differences in which public exponents are accepted by the different implementations. – Maarten Bodewes Mar 24 '19 at 20:53
  • @MaartenBodewes Is that so? The public exponent in this case is 4 bytes: 00 00 00 03 – Eaton Mar 24 '19 at 21:09
  • Ah ,that's why I could not find it in the base 64 after decoding. [Yes there are differences](https://stackoverflow.com/questions/27474550/is-there-a-limit-for-rsa-key-exponents-in-net) but CNG should be more permissive, not less, it seems. – Maarten Bodewes Mar 24 '19 at 21:42
  • Note that exponent 3 is sometimes considered insecure as there are some attacks for very small primes like (or specifically) 3. It is pretty safe for large keys and correct padding, so it is not *generally* considered a problem anymore, but I would not be surprised if some implementations block public exponent 3. You could try your code with a key pair of 2048 bit and a public key set to hex `010001` / 65537, the fifth prime of Fermat, also called F4 (zero indexed). – Maarten Bodewes Mar 24 '19 at 22:29
  • 1
    I've replicated the issue in C# using 4.6.1 (no newer platform on my computer at this time). I'm betting it's the minimal public exponent, checking on other frameworks / runtimes if the key is correct... Can't find any info on acceptance of exponent 3, and given the state of Microsoft documentation, I'm not sure if I'm going to find it. – Maarten Bodewes Mar 24 '19 at 23:31
  • @MaartenBodewes This is not a key I generated myself. It was obtained elsewhere from a third party and I'm using it to verify this data. I'm assuming I can't just change the exponent to something more acceptable and expect it to still validate data successfully. It seems odd to me that there is no error message thrown at all either. – Eaton Mar 24 '19 at 23:51
  • I've just verified the signature on Java as well. I'm pretty sure it is the public exponent, but I've dug deep and found no references to using exp. 3 yet. I'll call in some help. – Maarten Bodewes Mar 24 '19 at 23:58
  • Error messages must be thrown by the C# wrapper, not by the CNG provider itself, which is native. So if they "forgot" to generate exceptions for specific error conditions then you just get "false". Don't know if you can enable any logging for the native provider. – Maarten Bodewes Mar 25 '19 at 00:07

1 Answers1

2

I dug at this a bit and the issue is because the signature, when run through OpenSSL via:

openssl rsautl -verify -in sig.bin -inkey pub.key -pubin -hexdump -raw presents:

0000 - 00 01 ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0010 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0020 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0030 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0040 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0050 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0060 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0070 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0080 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
0090 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
00a0 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
00b0 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
00c0 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
00d0 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff   ................
00e0 - ff ff ff ff ff ff ff ff-ff ff ff 00 3b 0d 64 83   ............;.d.
00f0 - 8e c6 02 07 fd 73 26 5b-8a c0 ee 4d 13 72 e4 d4   .....s&[...M.r..

Essentially you have a signature with PKCSv1.5 padding with the hash at the end. However, the signature is missing the SEQUENCE(OID(SHA-1), OCTETSTRING) - the digest is going straight in.

CNG expects the OID to be there as defined in RSASSA-PKCS1-v1_5. You can ask CNG to relax this requirement by passing in NULL to the BCRYPT_PKCS1_PADDING_INFO's pszAlgId, however this is not exposed anywhere in .NET Framework or Core as far as I know.

As far as how to fix it, the easiest thing right now if possible is to use CAPI / RsaCryptoServiceProvider. GitHub issue 34202 may track any additional progress on CNG's behavior.

vcsjones
  • 138,677
  • 31
  • 291
  • 286
  • I guess modernizing old code isn't always a good thing to do. Thanks for finding the issue, very interesting. – Eaton Mar 25 '19 at 20:41
  • So is there any actual solution to this? – NickG Jun 10 '20 at 11:03
  • @NickG you can either make sure the signing process uses PKCS1 signatures that include the correct data structure, or use `RSACryptoServiceProvider`. The .NET folks indicated they do not intend to fix this for RSACng or the opaque RSA type. – vcsjones Jun 10 '20 at 19:17