It seems like your initial confusion lies in this statement:
Now I need to decrypt the signature with only this public key (it was encrypted with the > private key matching this public key) and the result should be a signing string, looking > like this:
So a message digest (SHA256 is a message digest or hashing algorithm) or even a signature is not reversibly encrypted data. It is indeed a one-way function meaning the "hash" or message digest produced both by the SHA256 algorithm as well as the RSA signing algorithm is not reversible back into the data from which it was generated.
See here: https://www.cs.cornell.edu/courses/cs5430/2015sp/notes/rsa_sign_vs_dec.php
So you really are not "decrypting" that signature. I believe what you would want to do is take the known public key and create an instance of the RSACryptoServiceProvider
and then pass the "signature" and the "sha256 hash" into the VerifyHash()
function to get back a boolean of whether the signature matches the hash. You should probably re-compute the SHA256 hash on your end as well to ensure what was sent in the header actually matches the "payload" you received. Just running the check on the HASH passed in the header against the signature in the header doesn't guarantee that they payload was not altered/tampered.
So I used the values you provided:
Digest (SHA256 Hash):
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
Signature (RSA):
UiBgtTc4hIYeZehhP8RWKvPIvWFXeh7ERFEvJr43v87YE7I4dqAHbD8l5DwZW3jezVfIcBflBS7ezjFDOH0/43T21ZCxwa/0qKhQRTjXoWQSETap5fXI9MCtWcGcP5iNmnBang7zfIsr+DBqQU5N3vlCBLORGqGVM0eMEv8nwBanAM2J7ZjbIVg7gou22eHau9751M4OoQM5FCo3nBTRepf2XA0K4W00TJg55chjMW/s91rw2ryJSPLUlrhvl5kWUYozGO56SzRjNhW+/HawcNZRa+OioWvlLp4bYi8mFyjlWWNAXqKPbqROQILhJnlHjGeMMq2qZTbd3t8aGHpeDg==
Public Key (PEM format):
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpb
EMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66RE
hi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK
99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C
9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1
MBV2XwIDAQAB
And following the RFC draft for the Authorization header:
https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-07#section-3.1
The following C# code should attempt to Verify the signature:
using System;
using System.Text;
using System.Security.Cryptography;
public static class SHA256WithRSATest
{
/*
Public Key:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpb
EMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66RE
hi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK
99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C
9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1
MBV2XwIDAQAB
Converted to an RSAParameters struct using Openssl:
echo MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkH+X94XHM5RDyTNETNmt8vuu1Q5leSpbEMIru1WE/vte5Co8k9lluffhvGWeyZRCKH4kzIBHf5n/kKjftTvvT/mPwArnJFZXcRbT4ebZ66REhi/+uJkJbIQ2md43MJBjM6fSXZAUiNw8VP29yVhbVPV5UUIA2ddJfygk/4ZNOmxdgnEJdc3aJTsK99dEJ6BYqbcxA0Bk19Fv3/azV0jGZgrC4Y2hx3A+NvIYC05QYoqNkcfDExRVrKwduVWzqz6XDt7C9ERl5Ss2bsgm4gbDouJC+k+WoCcxkUO2tnxrsKxQtZetZZSvrkst/5ELBaJAvKpcq12CvnB09dY1MBV2XwIDAQAB | base64 -d | openssl rsa -pubin -inform DER -noout -text
RSA Public-Key: (2048 bit)
Modulus:
00:90:7f:97:f7:85:c7:33:94:43:c9:33:44:4c:d9:
ad:f2:fb:ae:d5:0e:65:79:2a:5b:10:c2:2b:bb:55:
84:fe:fb:5e:e4:2a:3c:93:d9:65:b9:f7:e1:bc:65:
9e:c9:94:42:28:7e:24:cc:80:47:7f:99:ff:90:a8:
df:b5:3b:ef:4f:f9:8f:c0:0a:e7:24:56:57:71:16:
d3:e1:e6:d9:eb:a4:44:86:2f:fe:b8:99:09:6c:84:
36:99:de:37:30:90:63:33:a7:d2:5d:90:14:88:dc:
3c:54:fd:bd:c9:58:5b:54:f5:79:51:42:00:d9:d7:
49:7f:28:24:ff:86:4d:3a:6c:5d:82:71:09:75:cd:
da:25:3b:0a:f7:d7:44:27:a0:58:a9:b7:31:03:40:
64:d7:d1:6f:df:f6:b3:57:48:c6:66:0a:c2:e1:8d:
a1:c7:70:3e:36:f2:18:0b:4e:50:62:8a:8d:91:c7:
c3:13:14:55:ac:ac:1d:b9:55:b3:ab:3e:97:0e:de:
c2:f4:44:65:e5:2b:36:6e:c8:26:e2:06:c3:a2:e2:
42:fa:4f:96:a0:27:31:91:43:b6:b6:7c:6b:b0:ac:
50:b5:97:ad:65:94:af:ae:4b:2d:ff:91:0b:05:a2:
40:bc:aa:5c:ab:5d:82:be:70:74:f5:d6:35:30:15:
76:5f
Exponent: 65537 (0x10001)
*/
private static readonly RSAParameters PUBLIC_KEY = new RSAParameters
{
Modulus = new byte[]
{
0x90, 0x7f, 0x97, 0xf7, 0x85, 0xc7, 0x33, 0x94, 0x43, 0xc9,
0x33, 0x44, 0x4c, 0xd9, 0xad, 0xf2, 0xfb, 0xae, 0xd5, 0x0e,
0x65, 0x79, 0x2a, 0x5b, 0x10, 0xc2, 0x2b, 0xbb, 0x55, 0x84,
0xfe, 0xfb, 0x5e, 0xe4, 0x2a, 0x3c, 0x93, 0xd9, 0x65, 0xb9,
0xf7, 0xe1, 0xbc, 0x65, 0x9e, 0xc9, 0x94, 0x42, 0x28, 0x7e,
0x24, 0xcc, 0x80, 0x47, 0x7f, 0x99, 0xff, 0x90, 0xa8, 0xdf,
0xb5, 0x3b, 0xef, 0x4f, 0xf9, 0x8f, 0xc0, 0x0a, 0xe7, 0x24,
0x56, 0x57, 0x71, 0x16, 0xd3, 0xe1, 0xe6, 0xd9, 0xeb, 0xa4,
0x44, 0x86, 0x2f, 0xfe, 0xb8, 0x99, 0x09, 0x6c, 0x84, 0x36,
0x99, 0xde, 0x37, 0x30, 0x90, 0x63, 0x33, 0xa7, 0xd2, 0x5d,
0x90, 0x14, 0x88, 0xdc, 0x3c, 0x54, 0xfd, 0xbd, 0xc9, 0x58,
0x5b, 0x54, 0xf5, 0x79, 0x51, 0x42, 0x00, 0xd9, 0xd7, 0x49,
0x7f, 0x28, 0x24, 0xff, 0x86, 0x4d, 0x3a, 0x6c, 0x5d, 0x82,
0x71, 0x09, 0x75, 0xcd, 0xda, 0x25, 0x3b, 0x0a, 0xf7, 0xd7,
0x44, 0x27, 0xa0, 0x58, 0xa9, 0xb7, 0x31, 0x03, 0x40, 0x64,
0xd7, 0xd1, 0x6f, 0xdf, 0xf6, 0xb3, 0x57, 0x48, 0xc6, 0x66,
0x0a, 0xc2, 0xe1, 0x8d, 0xa1, 0xc7, 0x70, 0x3e, 0x36, 0xf2,
0x18, 0x0b, 0x4e, 0x50, 0x62, 0x8a, 0x8d, 0x91, 0xc7, 0xc3,
0x13, 0x14, 0x55, 0xac, 0xac, 0x1d, 0xb9, 0x55, 0xb3, 0xab,
0x3e, 0x97, 0x0e, 0xde, 0xc2, 0xf4, 0x44, 0x65, 0xe5, 0x2b,
0x36, 0x6e, 0xc8, 0x26, 0xe2, 0x06, 0xc3, 0xa2, 0xe2, 0x42,
0xfa, 0x4f, 0x96, 0xa0, 0x27, 0x31, 0x91, 0x43, 0xb6, 0xb6,
0x7c, 0x6b, 0xb0, 0xac, 0x50, 0xb5, 0x97, 0xad, 0x65, 0x94,
0xaf, 0xae, 0x4b, 0x2d, 0xff, 0x91, 0x0b, 0x05, 0xa2, 0x40,
0xbc, 0xaa, 0x5c, 0xab, 0x5d, 0x82, 0xbe, 0x70, 0x74, 0xf5,
0xd6, 0x35, 0x30, 0x15, 0x76, 0x5f
},
Exponent = new byte[] { 0x01,0x00,0x01 }
};
public static readonly byte[] theSignature =
Convert.FromBase64String("UiBgtTc4hIYeZehhP8RWKvPIvWFXeh7ERFEvJr43v87YE7I4dqAHbD8l5DwZW3jezVfIcBflBS7ezjFDOH0/43T21ZCxwa/0qKhQRTjXoWQSETap5fXI9MCtWcGcP5iNmnBang7zfIsr+DBqQU5N3vlCBLORGqGVM0eMEv8nwBanAM2J7ZjbIVg7gou22eHau9751M4OoQM5FCo3nBTRepf2XA0K4W00TJg55chjMW/s91rw2ryJSPLUlrhvl5kWUYozGO56SzRjNhW+/HawcNZRa+OioWvlLp4bYi8mFyjlWWNAXqKPbqROQILhJnlHjGeMMq2qZTbd3t8aGHpeDg==");
public static readonly byte[] SHA256HASH =
Convert.FromBase64String(
"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=");
public static bool VerifyDigestSignature(
byte[] digest, byte[] digestSignature)
{
using(RSACryptoServiceProvider rsaProvider =
new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(PUBLIC_KEY);
return rsaProvider.VerifyHash(
digest, digestSignature, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
public static void Main(string[] args)
{
Console.Error.WriteLine(
VerifyDigestSignature(SHA256HASH,theSignature));
}
}
However, the output of that code returns "False." So it would appear either something was lost in your post or wherever that example came from may have a misprint?
As a pure proof of concept here is similar code that computes a hash and signature and then verifies the signature successfully using the SHA256WithRSA method:
A new public/private key pair was generated using openssl genrsa 2048
...
using System;
using System.Text;
using System.Security.Cryptography;
public static class SHA256WithRSATest
{
private static readonly RSAParameters PUBLIC_KEY = new RSAParameters
{
Modulus = new byte[]
{
0xc2, 0xdb, 0x06, 0xc3, 0xe0, 0x43, 0x3b, 0x38, 0x72,
0x43, 0xc1, 0x87, 0xa4, 0x8b, 0x8c, 0x31, 0x64, 0x8a, 0xa4,
0xf3, 0x0d, 0x96, 0xc9, 0x3c, 0x46, 0x5e, 0x16, 0x8a, 0x57,
0xb6, 0x8c, 0x62, 0x0d, 0x3b, 0x78, 0x2f, 0xd8, 0x23, 0x6d,
0x1a, 0x74, 0x0b, 0x1e, 0x7e, 0xd9, 0x44, 0xec, 0x74, 0x99,
0xaf, 0x83, 0xe1, 0x84, 0x5c, 0x8b, 0x31, 0xac, 0x83, 0xe3,
0x09, 0xc9, 0xff, 0xee, 0x29, 0xfd, 0xcd, 0x64, 0x34, 0x5b,
0x25, 0xc7, 0xab, 0xf9, 0x76, 0x49, 0xcd, 0x1c, 0x53, 0xc4,
0x82, 0xcb, 0x61, 0xfa, 0x87, 0xf5, 0xa7, 0xe4, 0x63, 0x03,
0xbb, 0xbb, 0xb6, 0xc0, 0x2e, 0x5b, 0x1c, 0x28, 0xe6, 0xb8,
0xba, 0x6e, 0x89, 0xf9, 0x5a, 0x15, 0xf7, 0x49, 0x63, 0x6b,
0xb4, 0x90, 0x9c, 0xd5, 0xe8, 0xad, 0x5e, 0xa0, 0x95, 0x4b,
0xf2, 0x9a, 0xaa, 0x29, 0x1e, 0x04, 0xfe, 0xc3, 0x8d, 0xea,
0x41, 0xaf, 0xf8, 0x24, 0x7d, 0xf7, 0x3d, 0x24, 0x4d, 0xdd,
0xcb, 0xad, 0x84, 0x94, 0xba, 0x32, 0xb8, 0x26, 0x3b, 0x48,
0x55, 0xe2, 0x07, 0x13, 0x4d, 0x58, 0x1f, 0x61, 0x01, 0x92,
0x40, 0x53, 0x23, 0x8f, 0x3d, 0x7d, 0x6a, 0x65, 0xe3, 0xb1,
0xd3, 0xe3, 0x39, 0xd1, 0xd8, 0x77, 0xcd, 0x7d, 0x37, 0x9f,
0x54, 0x76, 0x3f, 0xc3, 0x3f, 0x38, 0xe4, 0xab, 0xaf, 0x99,
0x09, 0x1e, 0x96, 0x9d, 0x6e, 0x8e, 0x66, 0xb2, 0x5c, 0x39,
0xee, 0xf7, 0x7e, 0x65, 0x89, 0x69, 0x9d, 0xdc, 0x20, 0xe7,
0x63, 0x20, 0x75, 0x60, 0x75, 0x34, 0x22, 0x00, 0x2f, 0x74,
0x5c, 0x4c, 0x0a, 0xdf, 0xb5, 0x12, 0xfa, 0x9d, 0xc1, 0xed,
0x7f, 0x2e, 0xfc, 0xef, 0xd3, 0xb1, 0x62, 0x8a, 0xd7, 0x68,
0x3c, 0xa6, 0x61, 0xa9, 0x3e, 0x9e, 0x27, 0x3c, 0x6e, 0x02,
0xd1, 0x78, 0x0e, 0xe7, 0x8b, 0xb7, 0x91
},
Exponent = new byte[] { 0x01,0x00,0x01 }
};
private static readonly RSAParameters PRIVATE_KEY = new RSAParameters
{
Modulus = new byte[]
{
0xc2, 0xdb, 0x06, 0xc3, 0xe0, 0x43, 0x3b, 0x38, 0x72,
0x43, 0xc1, 0x87, 0xa4, 0x8b, 0x8c, 0x31, 0x64, 0x8a, 0xa4,
0xf3, 0x0d, 0x96, 0xc9, 0x3c, 0x46, 0x5e, 0x16, 0x8a, 0x57,
0xb6, 0x8c, 0x62, 0x0d, 0x3b, 0x78, 0x2f, 0xd8, 0x23, 0x6d,
0x1a, 0x74, 0x0b, 0x1e, 0x7e, 0xd9, 0x44, 0xec, 0x74, 0x99,
0xaf, 0x83, 0xe1, 0x84, 0x5c, 0x8b, 0x31, 0xac, 0x83, 0xe3,
0x09, 0xc9, 0xff, 0xee, 0x29, 0xfd, 0xcd, 0x64, 0x34, 0x5b,
0x25, 0xc7, 0xab, 0xf9, 0x76, 0x49, 0xcd, 0x1c, 0x53, 0xc4,
0x82, 0xcb, 0x61, 0xfa, 0x87, 0xf5, 0xa7, 0xe4, 0x63, 0x03,
0xbb, 0xbb, 0xb6, 0xc0, 0x2e, 0x5b, 0x1c, 0x28, 0xe6, 0xb8,
0xba, 0x6e, 0x89, 0xf9, 0x5a, 0x15, 0xf7, 0x49, 0x63, 0x6b,
0xb4, 0x90, 0x9c, 0xd5, 0xe8, 0xad, 0x5e, 0xa0, 0x95, 0x4b,
0xf2, 0x9a, 0xaa, 0x29, 0x1e, 0x04, 0xfe, 0xc3, 0x8d, 0xea,
0x41, 0xaf, 0xf8, 0x24, 0x7d, 0xf7, 0x3d, 0x24, 0x4d, 0xdd,
0xcb, 0xad, 0x84, 0x94, 0xba, 0x32, 0xb8, 0x26, 0x3b, 0x48,
0x55, 0xe2, 0x07, 0x13, 0x4d, 0x58, 0x1f, 0x61, 0x01, 0x92,
0x40, 0x53, 0x23, 0x8f, 0x3d, 0x7d, 0x6a, 0x65, 0xe3, 0xb1,
0xd3, 0xe3, 0x39, 0xd1, 0xd8, 0x77, 0xcd, 0x7d, 0x37, 0x9f,
0x54, 0x76, 0x3f, 0xc3, 0x3f, 0x38, 0xe4, 0xab, 0xaf, 0x99,
0x09, 0x1e, 0x96, 0x9d, 0x6e, 0x8e, 0x66, 0xb2, 0x5c, 0x39,
0xee, 0xf7, 0x7e, 0x65, 0x89, 0x69, 0x9d, 0xdc, 0x20, 0xe7,
0x63, 0x20, 0x75, 0x60, 0x75, 0x34, 0x22, 0x00, 0x2f, 0x74,
0x5c, 0x4c, 0x0a, 0xdf, 0xb5, 0x12, 0xfa, 0x9d, 0xc1, 0xed,
0x7f, 0x2e, 0xfc, 0xef, 0xd3, 0xb1, 0x62, 0x8a, 0xd7, 0x68,
0x3c, 0xa6, 0x61, 0xa9, 0x3e, 0x9e, 0x27, 0x3c, 0x6e, 0x02,
0xd1, 0x78, 0x0e, 0xe7, 0x8b, 0xb7, 0x91
},
Exponent = new byte[] { 0x01,0x00,0x01 },
D = new byte[]
{
0x01, 0xe9, 0x6a, 0x38, 0x93, 0xc3, 0xb5, 0x1a, 0x09, 0xac,
0xf6, 0x82, 0x21, 0x30, 0x29, 0x50, 0xf6, 0xbe, 0x91, 0x7d,
0xbc, 0xfd, 0x64, 0xbe, 0x0d, 0xa6, 0xb0, 0xab, 0xec, 0xce,
0x62, 0xb4, 0x37, 0x93, 0x04, 0xcb, 0xdb, 0x60, 0x05, 0x9b,
0x03, 0xd6, 0x74, 0x17, 0x24, 0x84, 0x93, 0x99, 0x55, 0x44,
0xae, 0x93, 0x90, 0xdb, 0xe3, 0x95, 0xba, 0x2e, 0x95, 0x14,
0xac, 0x81, 0xb1, 0x51, 0x82, 0x26, 0xf8, 0xbb, 0xb6, 0xc5,
0x39, 0x1f, 0x4b, 0xd4, 0x48, 0x47, 0x15, 0xe7, 0x10, 0x7f,
0x84, 0x05, 0x53, 0x12, 0xf0, 0x6b, 0x14, 0x47, 0x90, 0x6a,
0xd0, 0x1d, 0xab, 0xe7, 0x08, 0x87, 0xcf, 0x32, 0xec, 0x4f,
0x0f, 0xf4, 0x94, 0x98, 0xb8, 0xac, 0x73, 0x70, 0xe1, 0x46,
0xa5, 0x40, 0x94, 0xac, 0xb2, 0xbe, 0xc1, 0xee, 0x95, 0x0f,
0x2d, 0x4b, 0x4c, 0x19, 0xfa, 0x4c, 0x91, 0x98, 0x97, 0x75,
0x79, 0x6e, 0x02, 0x9d, 0xe7, 0x96, 0xe5, 0x71, 0x74, 0x67,
0x6b, 0x80, 0xc1, 0x5c, 0x30, 0x28, 0x78, 0xe6, 0x31, 0xdd,
0x09, 0x2d, 0x55, 0xd3, 0x1c, 0xbf, 0x12, 0x39, 0xe1, 0x3b,
0xf0, 0xd0, 0x92, 0xa0, 0x8e, 0xe4, 0x69, 0x2f, 0xb4, 0xe0,
0x7e, 0x6d, 0x62, 0x00, 0xd4, 0xf4, 0x80, 0x0a, 0x5e, 0xcf,
0xf3, 0x16, 0x59, 0x15, 0x0f, 0x96, 0xb5, 0x3b, 0xcd, 0xb3,
0x4d, 0x3b, 0x0f, 0x58, 0xc1, 0xaf, 0xde, 0x7d, 0x11, 0x6f,
0x53, 0x37, 0x24, 0x29, 0x12, 0xc6, 0xeb, 0x2e, 0x11, 0x74,
0x93, 0x66, 0xbd, 0x42, 0xc2, 0x28, 0x35, 0x32, 0x1f, 0xef,
0x95, 0x6b, 0x92, 0xe9, 0x7f, 0x9e, 0xc5, 0xbf, 0xf1, 0xfc,
0x48, 0x07, 0x2f, 0xda, 0xe0, 0x6b, 0xaa, 0xdd, 0x02, 0xc9,
0x4f, 0xe4, 0xff, 0x56, 0xc5, 0xfb, 0xe7, 0x1e, 0x63, 0x47,
0xa1, 0x0d, 0x44, 0x0e, 0x22, 0x1d
},
P = new byte[]
{
0xf1, 0x50, 0x52, 0x7e, 0x2b, 0xa7, 0xc3, 0x30, 0x26,
0xda, 0x14, 0xaf, 0xf9, 0xc5, 0x21, 0x49, 0xab, 0xa8, 0x09,
0x1f, 0x36, 0xe5, 0x1f, 0x0f, 0x52, 0x4d, 0x82, 0x41, 0x46,
0x7e, 0x75, 0x43, 0x80, 0x8e, 0x58, 0x6f, 0xdf, 0x9d, 0xee,
0x77, 0x4c, 0x9c, 0xff, 0x94, 0xf6, 0x90, 0xaa, 0xaf, 0x0f,
0xba, 0xb6, 0x08, 0xe6, 0x3f, 0xaa, 0xf6, 0x37, 0xc5, 0xe3,
0xf7, 0x6a, 0xcd, 0x1b, 0xb7, 0xa8, 0x78, 0x15, 0xfc, 0xd0,
0xce, 0xe0, 0x67, 0xf5, 0xd7, 0xa2, 0xa7, 0x37, 0x6f, 0x95,
0xeb, 0xe0, 0x13, 0x93, 0xc9, 0x65, 0xd8, 0x8b, 0x4e, 0x1c,
0x46, 0xd8, 0x4f, 0x50, 0x6b, 0x02, 0x87, 0xb4, 0x4e, 0x55,
0x6c, 0x21, 0xc3, 0xf5, 0xc7, 0xd9, 0x1d, 0x2a, 0xcd, 0x25,
0x9a, 0xb4, 0x79, 0x28, 0x29, 0x03, 0x45, 0x11, 0x26, 0xce,
0x76, 0x6c, 0xcb, 0x17, 0xb2, 0xc3, 0x58, 0x85, 0xe3
},
Q = new byte[]
{
0xce, 0xb6, 0xe2, 0x42, 0x9c, 0xe6, 0xd7, 0xc3, 0x52,
0x83, 0x5f, 0xa7, 0x16, 0x04, 0x61, 0xf1, 0xb6, 0xf9, 0x65,
0xb3, 0xda, 0x02, 0x10, 0x74, 0xc4, 0x67, 0x85, 0x5f, 0x1c,
0x1f, 0xcb, 0x71, 0x0e, 0xe5, 0x10, 0x21, 0xd0, 0x5f, 0xbe,
0xa4, 0x81, 0xcc, 0xdd, 0x52, 0xab, 0x6b, 0x40, 0x72, 0x09,
0x9b, 0xa0, 0x4f, 0x2f, 0x88, 0x96, 0x54, 0xae, 0x66, 0x83,
0xd5, 0x45, 0x48, 0xc7, 0x67, 0xaf, 0x5c, 0xc6, 0xfe, 0xb6,
0x94, 0x26, 0x02, 0xf7, 0x1d, 0x41, 0x67, 0x98, 0x81, 0x1a,
0x6a, 0xeb, 0xfb, 0x33, 0xc3, 0x34, 0x8a, 0x93, 0xbd, 0x74,
0x4c, 0x7d, 0x1c, 0xc4, 0x38, 0x9d, 0x72, 0x6e, 0xe3, 0x79,
0xd0, 0xec, 0xaf, 0xac, 0x14, 0xe2, 0xee, 0x27, 0x9c, 0x72,
0xe6, 0xd8, 0x43, 0x6b, 0xc8, 0xae, 0x07, 0x5b, 0x5d, 0x27,
0x61, 0xba, 0xa5, 0x7f, 0x74, 0xc8, 0x78, 0x66, 0xfb
},
DP = new byte[]
{
0x86, 0x46, 0x6e, 0x90, 0x9c, 0x54, 0x0e, 0x4d, 0x55,
0xe1, 0x15, 0x8f, 0xd2, 0x08, 0xb9, 0xfc, 0x17, 0x53, 0x3a,
0x38, 0x2f, 0x40, 0x90, 0xe6, 0xe2, 0xa2, 0x14, 0x6f, 0xa3,
0xfd, 0x2b, 0xdc, 0xf2, 0xc4, 0xc2, 0x3b, 0x06, 0x10, 0x08,
0x28, 0x43, 0xee, 0x3c, 0x5d, 0x34, 0x51, 0xcd, 0x57, 0xfa,
0x05, 0xa7, 0xd3, 0x0d, 0xe3, 0xb1, 0x8a, 0xae, 0x00, 0x24,
0x58, 0x81, 0x0a, 0x3e, 0x79, 0x14, 0x7a, 0x35, 0xa9, 0xe6,
0xba, 0xa6, 0xad, 0xd2, 0x63, 0x39, 0xb3, 0x98, 0x2a, 0x34,
0x1e, 0xfb, 0x21, 0x89, 0xa3, 0x90, 0x53, 0x4d, 0x38, 0x9a,
0x8d, 0x65, 0x41, 0xc4, 0xfa, 0xb6, 0x7e, 0xb2, 0x7a, 0xc0,
0x17, 0x9a, 0x36, 0x43, 0x26, 0x00, 0x0e, 0xb5, 0xc9, 0x4f,
0x3a, 0x65, 0x5f, 0xe1, 0x53, 0xe8, 0xe8, 0xde, 0xa1, 0x5c,
0x53, 0x13, 0x38, 0x73, 0x28, 0x5a, 0x80, 0x80, 0x87
},
DQ = new byte[]
{
0xc7, 0xe5, 0x24, 0x91, 0x84, 0x06, 0xdb, 0x19, 0x1f,
0x9e, 0xb9, 0x0d, 0xeb, 0x85, 0x9b, 0x6d, 0x52, 0x22, 0x84,
0x4d, 0xd2, 0x80, 0xf2, 0x86, 0xe8, 0x32, 0xaf, 0x4f, 0x94,
0xf3, 0xce, 0x18, 0xeb, 0x6d, 0x69, 0x17, 0x39, 0xd8, 0x8c,
0x93, 0xaa, 0x8d, 0x80, 0x6c, 0xe4, 0x25, 0x57, 0xf1, 0xaf,
0x06, 0xd6, 0x94, 0x1c, 0x84, 0x39, 0xd3, 0x73, 0xbe, 0xe0,
0xb7, 0x89, 0x43, 0x62, 0xc9, 0x0a, 0x54, 0x6e, 0x7e, 0x7b,
0xf2, 0x71, 0x7b, 0xa6, 0x99, 0x9c, 0xd8, 0xe0, 0x29, 0xe0,
0x71, 0x0a, 0xf8, 0x25, 0x4b, 0x1c, 0x70, 0xf1, 0x83, 0x60,
0x86, 0x62, 0xea, 0x41, 0x79, 0xfa, 0x0f, 0x61, 0xda, 0x09,
0xbf, 0x96, 0x52, 0x1e, 0xd7, 0x27, 0xc7, 0x63, 0x78, 0xaf,
0xc8, 0x39, 0xd3, 0xa0, 0xd4, 0x34, 0x2e, 0x1b, 0x14, 0xce,
0xf3, 0x7b, 0xb9, 0x74, 0xb2, 0x6a, 0xf5, 0xbb, 0xa7
},
InverseQ = new byte[]
{
0x83, 0x84, 0x1d, 0xa5, 0x91, 0x6a, 0xac, 0x17, 0xa3,
0xaf, 0xb5, 0x1f, 0x36, 0x77, 0xcb, 0x9a, 0x05, 0x7a, 0x79,
0xa3, 0x25, 0x77, 0xd9, 0x0e, 0x64, 0xdc, 0x18, 0xfd, 0xec,
0xc6, 0xa4, 0xdc, 0x0f, 0x6a, 0x50, 0x91, 0x97, 0x12, 0xfe,
0x05, 0x0b, 0xcd, 0x64, 0xf8, 0x6e, 0xbf, 0x12, 0x6b, 0xd2,
0x0e, 0x7c, 0x94, 0xc2, 0x5b, 0x2d, 0x53, 0x0f, 0x45, 0x41,
0xfb, 0x6b, 0x22, 0x64, 0xbb, 0x36, 0xc0, 0x24, 0x01, 0xe5,
0x1b, 0xf7, 0x76, 0xa0, 0x3d, 0x67, 0x66, 0x2f, 0x6b, 0x80,
0xb2, 0x35, 0x87, 0x68, 0xdb, 0x75, 0x4d, 0x3d, 0xc1, 0x21,
0xa8, 0xff, 0x0d, 0xe7, 0xea, 0x2c, 0x87, 0xe1, 0xe5, 0xc1,
0x81, 0x45, 0xd0, 0x2f, 0x6f, 0xbb, 0xe9, 0xbf, 0x1f, 0x93,
0x62, 0x06, 0xf5, 0x2c, 0x78, 0xb0, 0x2a, 0x22, 0xb8, 0xeb,
0xf8, 0xae, 0xdd, 0x1d, 0x25, 0x53, 0x66, 0xab, 0x94
}
};
public const string SAMPLE_PAYLOAD =
@"{ 'name': 'value', 'name2' : 'value2' }";
public static byte[] GetDigest(byte[] message)
{
using (SHA256 digestAlg = SHA256.Create())
return digestAlg.ComputeHash(message);
}
public static byte[] GetDigestSignature(byte[] digest)
{
using(RSACryptoServiceProvider rsaProvider =
new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(PRIVATE_KEY);
return rsaProvider.SignHash(digest, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
public static bool VerifyDigestSignature(
byte[] digest, byte[] digestSignature)
{
using(RSACryptoServiceProvider rsaProvider =
new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(PUBLIC_KEY);
return rsaProvider.VerifyHash(
digest, digestSignature, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
}
public static void Main(string[] args)
{
byte[] dataForSignature = Encoding.UTF8.GetBytes(SAMPLE_PAYLOAD);
byte[] digest = GetDigest(dataForSignature);
byte[] hashSignature = GetDigestSignature(digest);
Console.WriteLine("The Sample Payload: {0}", SAMPLE_PAYLOAD);
Console.WriteLine("SHA256 of Payload: {0}",
Convert.ToBase64String(digest));
Console.WriteLine("Signature of SHA256 of Payload: {0}",
Convert.ToBase64String(hashSignature));
Console.WriteLine("Verify RSA Signature of SHA256 Hash: {0}",
VerifyDigestSignature(digest,hashSignature));
}
}
Results of Execution:
The Sample Payload: { 'name': 'value', 'name2' : 'value2' }
SHA256 of Payload: Pw3YzEtDseyloxZGsfrFN1q8g39ubwlnRKJzal2sUb4=
Signature of SHA256 of Payload: rVdEq158Z8h9PHinlQ6I7lnUUcXDal25LZ+MIyW0Uvf8HpH6wRoQzqJhygVwHmeVTkZn2mZS644B/9Ey1WVbgq56zzk2XDiHyNnS1IeZvfaTkaXqiBszVQ/hTSjrDOARzrU2NDRo2/gI/UUXC2v2fx3eyd4NbztvghTKfoUBp+xZjyBHWlCwAtEmDfVT0/Di0LkrII80txrlHQpSNXEpejmrhJ10WsskUuOCOloQmcl8A5d0ibkXXqL6TLLWKpLS0wXI6+GZnlc6b3qL0rU1iDLQsYMHLvel6+DJzCvhV8FHn/yJNKwjNpFiSNektZBQovd4NgO9aBRNgxIOGEEqEA==
Verify RSA Signature of SHA256 Hash: True
One thing to notice in the above C# samples is that the Public Key and Private Keys are defined using large byte arrays and the RSAParameters struct. If you would like to read (with C# code) the base64 PEM encoded Public Key you provided you will either need to be using .NET 5.0 (as it appears Microsoft has added functions to load Keys from PEM format) or you'll probably want to reference something like the BouncyCastle libraries via NuGet. More info here: http://www.bouncycastle.org/csharp/
Otherwise once you have the RSACryptoServiceProvider instantiated and configured with the public key a simple call to the VerifyHash() or VerifyData() methods should give you the boolean answer you need: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider.verifydata?view=net-5.0
The VerifyData()
method would first Calculate the SHA256 hash (assuming you specify CryptoConfig.MapNameToOID("SHA256")
for the "halg" parameter) and then verify that the SHA256 generated matches that from which the signature was generated. If this is a web app, I personally would probably approach it the following way: Calculate the SHA256 hash of the payload. Compare that to the SHA256 passed in the HTTP Header. If they don't match, you can stop there (thus saving compute power as RSA / asymmetric encryption is a bit on the expensive side). If they do match then continue on to verify said SHA256 hash with the signature using RSA. Keep in mind the above examples assume the Padding Method for the signature is PKCS1 (which I believe is more common, but PSS is another option). I didn't fully read the RFC draft or other RFCs mentioned on the EWS site. You will definitely want to verify. However, when I ran the first code sample with PSS padding it generated an exception so that definitely lead me to believe it was PKCS1.