Skip to content

Using a custom CryptoProvider

Maria Furman edited this page May 15, 2019 · 2 revisions

By default, CryptoProviderFactory provides support for many different algorithms (see here for a complete list), but in the case that you would like to sign and/or verify using an algorithm that we do not support by default, you will need to plug in a custom CryptoProvider.

This custom CryptoProvider must implement the ICryptoProvider interface, and can either be set on CryptoProviderFactory.Default or directly on a SecurityKey.

For example, here is a custom CryptoProvider that supports signing and verifying operations using the SHA1 algorithm:

public class Sha1Provider : ICryptoProvider
    {
        public object Create(string algorithm, params object[] args)
        {
            if (algorithm == SignedXml.XmlDsigRSASHA1Url)
                return new Sha1SigProvider((SecurityKey)args[0], algorithm);
            if (algorithm == SignedXml.XmlDsigSHA1Url)
                return SHA1.Create();
            return null;
        }

        public bool IsSupportedAlgorithm(string algorithm, params object[] args)
        {
            return new[] { SignedXml.XmlDsigRSASHA1Url, SignedXml.XmlDsigSHA1Url }.Contains(algorithm);
        }

        public void Release(object cryptoInstance)
        {
        }
    }
    public class Sha1SigProvider : SignatureProvider
    {
        private readonly RSA _puk;
        private readonly RSA _prk;

        public Sha1SigProvider(SecurityKey key, string algorithm) : base(key, algorithm)
        {
            if (!(key is X509SecurityKey x509Key)) return;
            _puk = x509Key.PublicKey as RSA;
            _prk = x509Key.PrivateKey as RSA;
        }

        public override byte[] Sign(byte[] input)
        {
            if (input == null || input.Length == 0)
                throw new ArgumentNullException(nameof(input));

            return _prk.SignData(input, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
        }

        public override bool Verify(byte[] input, byte[] signature)
        {
            if (input == null || input.Length == 0)
                throw new ArgumentNullException(nameof(input));

            if (signature == null || signature.Length == 0)
                throw new ArgumentNullException(nameof(signature));

            return _puk.VerifyData(input, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
        }

        protected override void Dispose(bool disposing)
        {
            //throw new NotImplementedException();
        }
    }

As mentioned earlier, you can set the custom CryptoProvider on CryptoProviderFactory.Default:

CryptoProviderFactory.Default.CustomCryptoProvider = new Sha1Provider();

Or alternatively you can set it on the SecurityKey being used:

var key = new X509SecurityKey(new X509Certificate2(new byte[]));
key.CryptoProviderFactory.CustomCryptoProvider = new Sha1Provider();

NOTE: Since you can only set one custom CryptoProvider, if you want to provide support for more than one cryptographic algorithm it may be necessary to create a wrapper class that will loop through a set of custom CryptoProviders.

This sample code was taken from: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/973

Clone this wiki locally