Verify signature generated with RSA 2048-bit key, SHA256 algorithm and PKCSv1.5 padding

假装没事ソ 提交于 2019-12-19 10:38:28

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!