问题
I have a UWA (Universal Windows Application) signing some data with the KeyCredential.RequestSignAsync method.
The signature is created with:
- RSA 2048-bit key (public portion can be retrieved with KeyCredential.RetrievePublicKey)
- Hashing algorithm used is SHA256
- Padding used with the signature is PKCSv1.5
And can be validated in the same UWA with the following code:
public static bool VerifySignature(
IBuffer buffPublicKey,
IBuffer buffMessageData,
IBuffer buffSignature)
{
bool b = false;
// Open the algorithm provider
var algProv = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaSignPkcs1Sha256);
// Import the public key
var ckey = algProv.ImportPublicKey(buffPublicKey);
// Verify
b = CryptographicEngine.VerifySignature(ckey, buffMessageData, buffSignature);
return b;
}
I need to verify that signature but in a regular C# application (not UWA). The Public Key, message and signature are being encoded to base 64 with CryptographicBuffer.EncodeToBase64String before being transferred.
So according System.Security.Cryptography namespace I tried with:
public static bool VerifySignature(string base64PublicKey, string base64Data, string base64Signature)
{
bool b = false;
byte[] publicKey = Convert.FromBase64String(base64PublicKey);
byte[] data = Convert.FromBase64String(base64Data);
byte[] signature = Convert.FromBase64String(base64Signature);
using (var rsa = new RSACryptoServiceProvider(2048))
{
// Import public key
rsa.ImportCspBlob(publicKey);
// Create signature verifier with the rsa key
var signatureDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
// Set the hash algorithm to SHA256.
signatureDeformatter.SetHashAlgorithm("SHA256");
b = signatureDeformatter.VerifySignature(data, siganture);
}
return b;
}
But getting a System.Security.Cryptography.CryptographicException with Additional information: Bad Version of provider. in:
rsa.ImportCspBlob(publicKey);
¿How is the proper way to verify the signature with that public key?
EDIT: Sample values (base64 encoded)
- PublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6HzbSgZPkJPfZJWydFAKdzUWlQcGHCTZhghg8HwHOfRZp3QZ/iiDORVzdIlW6XYPz76aAn8Nxm/v4NbsQsFPbwIcc7CPOJe21VT+7f6ocZ4kef0dqxUOGuK1FynrqzsAeYoaeTW+w/HElXODOEzZs3CfyE3d4hy3TTM/mVyQGV1FO/hHWB/zXq7ryQ8hXP/ueJimmJvitB7UweemRxvEYfVx52VVAgzg1RqVWeRj8L/obfm0lwQtIAHdDOnIi/cwpsyKQNikjMsf4dFgt14fcOgFdSG06jB840GnOsRZM04CWZQ9ttwAvoNGK/zjriRYGySQ4Ey0K0l5G3UVr56mQIDAQAB
- Data: dGF0b0Bmcm9td2luMzIuY29t
- Signature: lWKRRgWBA2lBAfUvBS+54s9kmHTH3nJwcvYYmjCg5QpWQ9joY7Rzpq0zZjOhyxASXoAN4Vz8+mqSqPWi/4DFH7947ZWZSbopPfxiI7jjDRMAVymG0B+dRVjiMow48ZvhgP/FGSZqeLAei77Z0aAmwN2TBxkClqBpt9uy+nkI7V/TJGAbbLcWfiPWNVOGsU0smoFDQLlJjkocahNSOqjj+9PPFVqbc/VVHQWsSoq1ZxtCPILFwPCCtUCDITXrU/riGMFJ282p/3rfhDJKYis9/izR98/zgBLRoCew8zu8Za4UNWaHaR3HP/6voQI2NiVSKtss1VjvwjwXYIOh56yeSw==
回答1:
Given that the PublicKey is ASN.1 encoded in X509SubjectPublicKeyInfo format and that rsa.ImportCspBlob(publicKey) expects a blob that is compatible with the unmanaged Microsoft Cryptographic API (CAPI), I've created a helper method based on this solution that extracts the public key parameters.
With the following code, the signature is verified successfuly:
using System;
using System.IO;
using System.Security.Cryptography;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var verified = false;
byte[] data = Convert.FromBase64String("dGF0b0Bmcm9td2luMzIuY29t");
byte[] signature = Convert.FromBase64String("lWKRRgWBA2lBAfUvBS+54s9kmHTH3nJwcvYYmjCg5QpWQ9joY7Rzpq0zZjOhyxASXoAN4Vz8+mqSqPWi/4DFH7947ZWZSbopPfxiI7jjDRMAVymG0B+dRVjiMow48ZvhgP/FGSZqeLAei77Z0aAmwN2TBxkClqBpt9uy+nkI7V/TJGAbbLcWfiPWNVOGsU0smoFDQLlJjkocahNSOqjj+9PPFVqbc/VVHQWsSoq1ZxtCPILFwPCCtUCDITXrU/riGMFJ282p/3rfhDJKYis9/izR98/zgBLRoCew8zu8Za4UNWaHaR3HP/6voQI2NiVSKtss1VjvwjwXYIOh56yeSw==");
byte[] publicKey = Convert.FromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6HzbSgZPkJPfZJWydFAKdzUWlQcGHCTZhghg8HwHOfRZp3QZ/iiDORVzdIlW6XYPz76aAn8Nxm/v4NbsQsFPbwIcc7CPOJe21VT+7f6ocZ4kef0dqxUOGuK1FynrqzsAeYoaeTW+w/HElXODOEzZs3CfyE3d4hy3TTM/mVyQGV1FO/hHWB/zXq7ryQ8hXP/ueJimmJvitB7UweemRxvEYfVx52VVAgzg1RqVWeRj8L/obfm0lwQtIAHdDOnIi/cwpsyKQNikjMsf4dFgt14fcOgFdSG06jB840GnOsRZM04CWZQ9ttwAvoNGK/zjriRYGySQ4Ey0K0l5G3UVr56mQIDAQAB");
byte[] modulus;
byte[] exponent;
ExtractPublicKeyParameters(publicKey, out modulus, out exponent);
using (var rsa = new RSACryptoServiceProvider())
{
// Create parameters
var rsaParam = new RSAParameters()
{
Modulus = modulus,
Exponent = exponent
};
// Import public key
rsa.ImportParameters(rsaParam);
// Create signature verifier with the rsa key
var signatureDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
// Set the hash algorithm to SHA256.
signatureDeformatter.SetHashAlgorithm("SHA256");
// Compute hash
byte[] hash;
using (SHA256 sha256 = SHA256.Create())
{
hash = sha256.ComputeHash(data);
}
verified = signatureDeformatter.VerifySignature(hash, signature);
}
}
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
static readonly byte[] SeqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
public static void ExtractPublicKeyParameters(byte[] publicKey, out byte[] modulus, out byte[] exponent)
{
modulus = new byte[0];
exponent = new byte[0];
byte[] seq = new byte[15];
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(publicKey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return;
seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, SeqOid)) //make sure Sequence for OID is correct
return;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8203)
binr.ReadInt16(); //advance 2 bytes
else
return;
bt = binr.ReadByte();
if (bt != 0x00) //expect null byte next
return;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return;
twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;
if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
else if (twobytes == 0x8202)
{
highbyte = binr.ReadByte(); //advance 2 bytes
lowbyte = binr.ReadByte();
}
else
return;
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
int modsize = BitConverter.ToInt32(modint, 0);
int firstbyte = binr.PeekChar();
if (firstbyte == 0x00)
{ //if first byte (highest order) of modulus is zero, don't include it
binr.ReadByte(); //skip this null byte
modsize -= 1; //reduce modulus buffer size by 1
}
modulus = binr.ReadBytes(modsize); //read the modulus bytes
if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
return;
int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
exponent = binr.ReadBytes(expbytes);
}
finally
{
binr.Close();
}
}
private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
}
}
回答2:
The public key is in the SubjectPublicKeyInfo format defined on page 15 of https://tools.ietf.org/html/rfc3280.
I don't know of any (public) standard classes that can decode that, however the Bouncy Castle APIs have classes which you can use to get an RSA instance with the public key information loaded.
Also when calling signatureDeformatter.VerifySignature(data, siganture) you need to pass in the hash of the data and not the data itself.
With respect to all this I believe your code should be:
using System;
using System.Linq;
using System.Security.Cryptography;
using Org.BouncyCastle.Asn1;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
byte[] data = Convert.FromBase64String("dGF0b0Bmcm9td2luMzIuY29t");
byte[] signature = Convert.FromBase64String("lWKRRgWBA2lBAfUvBS+54s9kmHTH3nJwcvYYmjCg5QpWQ9joY7Rzpq0zZjOhyxASXoAN4Vz8+mqSqPWi/4DFH7947ZWZSbopPfxiI7jjDRMAVymG0B+dRVjiMow48ZvhgP/FGSZqeLAei77Z0aAmwN2TBxkClqBpt9uy+nkI7V/TJGAbbLcWfiPWNVOGsU0smoFDQLlJjkocahNSOqjj+9PPFVqbc/VVHQWsSoq1ZxtCPILFwPCCtUCDITXrU/riGMFJ282p/3rfhDJKYis9/izR98/zgBLRoCew8zu8Za4UNWaHaR3HP/6voQI2NiVSKtss1VjvwjwXYIOh56yeSw==");
byte[] publicKey = Convert.FromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6HzbSgZPkJPfZJWydFAKdzUWlQcGHCTZhghg8HwHOfRZp3QZ/iiDORVzdIlW6XYPz76aAn8Nxm/v4NbsQsFPbwIcc7CPOJe21VT+7f6ocZ4kef0dqxUOGuK1FynrqzsAeYoaeTW+w/HElXODOEzZs3CfyE3d4hy3TTM/mVyQGV1FO/hHWB/zXq7ryQ8hXP/ueJimmJvitB7UweemRxvEYfVx52VVAgzg1RqVWeRj8L/obfm0lwQtIAHdDOnIi/cwpsyKQNikjMsf4dFgt14fcOgFdSG06jB840GnOsRZM04CWZQ9ttwAvoNGK/zjriRYGySQ4Ey0K0l5G3UVr56mQIDAQAB");
byte[] hash;
using (SHA256 sha256 = SHA256.Create())
{
hash = sha256.ComputeHash(data);
}
bool b = false;
var rsaParam = GetPublicKeyRSAParameters(publicKey);
using (var rsa = new RSACryptoServiceProvider())
{
// Import public key
rsa.ImportParameters(rsaParam);
// Create signature verifier with the rsa key
var signatureDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
// Set the hash algorithm to SHA256.
signatureDeformatter.SetHashAlgorithm("SHA256");
b = signatureDeformatter.VerifySignature(hash, signature);
}
}
public static RSAParameters GetPublicKeyRSAParameters(byte[] subjectPublicKeyInfoBytes)
{
var publicKeyObject = (DerSequence)Asn1Object.FromByteArray(subjectPublicKeyInfoBytes);
var rsaPublicKeyParametersBitString = (DerBitString)publicKeyObject[1];
var rsaPublicKeyParametersObject = (DerSequence)Asn1Object.FromByteArray(rsaPublicKeyParametersBitString.GetBytes());
var modulus = ((DerInteger)rsaPublicKeyParametersObject[0]).Value.ToByteArray().Skip(1).ToArray();
var exponent = ((DerInteger)rsaPublicKeyParametersObject[1]).Value.ToByteArray();
return new RSAParameters() { Modulus = modulus, Exponent = exponent };
}
}
}
来源:https://stackoverflow.com/questions/32504866/verify-signature-generated-with-rsa-2048-bit-key-sha256-algorithm-and-pkcsv1-5