-->

Create RSA-SHA1 signature

2019-02-19 19:06发布

问题:

Is it possible to create a RSA-SHA1 signature with a X509-certificate stored in Azure Key Vault? [non-repudiation certificate]

Unfortunately, I can't change the hash-algorithm to SHA256 or something safer, and I really need to store the certficate in Azure Key Vault as Key.

So far I've tried

await kvClient.SignAsync(keyVaultUrl, "RSNULL", digest); // digest = 20byte SHA1
await kvClient.SignAsync(keyVaultUrl, "RSNULL", ans1Digest); // asn1Digest = 35byte SHA1 wrapped in ANS1 structure

The signature length seems to be correct (256 bytes), but verification fails (on a node with a correctly implemented signature-verification implementation).

I've also tried to implement the signature-algorithm manually like this (using keyVault.EncryptAsync):

  • Data >> Calculate SHA1 digest
  • Wrap SHA1 digest in ASN.1 structure (together with SHA1 object identifier)
  • Pad ANS.1 structure so digest matches the key-length of the certificate
  • Encrypt the padded ANS1.structure using RSA1_5 >> Signature

I must be doing something wrong. Not sure if all steps are needed.

If anyone from Microsoft reads this. Can you implement SHA1 signing even if it isn't considered safe? Pretty, please with sugar on top :-)

回答1:

My blindly shot "professional" opinion is that you are not constructring PKCS#1 DigestInfo structure correctly. Following console application is working fine for me with both SHA1 and SHA256 algorithms (didn't test the others):

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace AzureKeyVaultTestApp1
{
    static class Program
    {
        static HashAlgorithmName _hashAlg = HashAlgorithmName.SHA1;
        static string _clientId = "00000000-0000-0000-0000-000000000000";
        static string _clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        static string _certId = "https://XXXXXXXX.vault.azure.net/certificates/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        static string _keyId = "https://XXXXXXXX.vault.azure.net/keys/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

        static async Task<string> AuthenticationCallback(string authority, string resource, string scope)
        {
            var context = new AuthenticationContext(authority);
            var result = await context.AcquireTokenAsync(resource, new ClientCredential(_clientId, _clientSecret));
            return result.AccessToken;
        }

        static async Task Main(string[] args)
        {
            KeyVaultClient client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(AuthenticationCallback));

            // Compute digest of data
            byte[] dataToSign = Encoding.ASCII.GetBytes("Hello world!");
            byte[] hash = HashAlgorithm.Create(_hashAlg.Name).ComputeHash(dataToSign);

            // Construct DER encoded PKCS#1 DigestInfo structure defined in RFC 8017
            byte[] pkcs1DigestInfo = CreatePkcs1DigestInfo(hash, _hashAlg);

            // Sign digest with private key
            var keyOperationResult = await client.SignAsync(_keyId, "RSNULL", pkcs1DigestInfo).ConfigureAwait(false);
            byte[] signature = keyOperationResult.Result;

            // Get public key from certificate
            var certBundle = await client.GetCertificateAsync(_certId).ConfigureAwait(false);
            X509Certificate2 cert = new X509Certificate2(certBundle.Cer);
            RSA rsaPubKey = cert.GetRSAPublicKey();

            // Verify digest signature with public key
            if (!rsaPubKey.VerifyHash(hash, signature, _hashAlg, RSASignaturePadding.Pkcs1))
                throw new Exception("Invalid signature");
        }

        private static byte[] CreatePkcs1DigestInfo(byte[] hash, HashAlgorithmName hashAlgorithm)
        {
            if (hash == null || hash.Length == 0)
                throw new ArgumentNullException(nameof(hash));

            byte[] pkcs1DigestInfo = null;

            if (hashAlgorithm == HashAlgorithmName.MD5)
            {
                if (hash.Length != 16)
                    throw new ArgumentException("Invalid lenght of hash value");

                pkcs1DigestInfo = new byte[] { 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
            }
            else if (hashAlgorithm == HashAlgorithmName.SHA1)
            {
                if (hash.Length != 20)
                    throw new ArgumentException("Invalid lenght of hash value");

                pkcs1DigestInfo = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
            }
            else if (hashAlgorithm == HashAlgorithmName.SHA256)
            {
                if (hash.Length != 32)
                    throw new ArgumentException("Invalid lenght of hash value");

                pkcs1DigestInfo = new byte[] { 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
            }
            else if (hashAlgorithm == HashAlgorithmName.SHA384)
            {
                if (hash.Length != 48)
                    throw new ArgumentException("Invalid lenght of hash value");

                pkcs1DigestInfo = new byte[] { 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
            }
            else if (hashAlgorithm == HashAlgorithmName.SHA512)
            {
                if (hash.Length != 64)
                    throw new ArgumentException("Invalid lenght of hash value");

                pkcs1DigestInfo = new byte[] { 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
            }

            return pkcs1DigestInfo;
        }
    }
}