Search code examples
.netrsadigital-signaturepublic-key-encryptioncng

.NET - Porting RSACryptoServiceProvider to CNG for signing data


I'm trying to port an RSA signing function written in C#, from the (relatively easy to use) RSACryptoServiceProvider.SignHash method, to the CNG API, in order to use a specific cryptographic service provider.

This is the original (before porting) function:

    private static byte[] SignDigest(RSAParameters keyMaterial, byte[] digest, string hashAlgo)
    {
        using (var cryptoProvider = new RSACryptoServiceProvider())
        {
            cryptoProvider.ImportParameters( keyMaterial );
            // hashAlgo can only be one of "SHA1", "SHA256", "SHA384" and "SHA512".
            return cryptoProvider.SignHash( digest, CryptoConfig.MapNameToOID( hashAlgo ) );
        }
    }

Quite simple, don't you think? When porting to CNG, this is the best I could do so far:

    private static byte[] SignDigest(RSAParameters keyMaterial, byte[] digest, string hashAlgo)
    {
        var size = 6 * 4 + keyMaterial.Exponent.Length + keyMaterial.Modulus.Length + keyMaterial.P.Length + keyMaterial.Q.Length;
        var keyBlob = new byte[size];            
        using (var writer = new BinaryWriter(new MemoryStream(keyBlob)))
        {
            // This is BCRYPT_RSAKEY_BLOB structure (https://msdn.microsoft.com/en-us/library/windows/desktop/aa375531(v=vs.85).aspx).
            writer.Write(0x32415352); // BCRYPT_RSAPRIVATE_MAGIC
            writer.Write(keyMaterial.Modulus.Length * 8); // BitLength
            writer.Write(keyMaterial.Exponent.Length); // cbPublicExp
            writer.Write(keyMaterial.Modulus.Length); // cbModulus
            writer.Write(keyMaterial.P.Length); // cbPrime1
            writer.Write(keyMaterial.Q.Length); // cbPrime2
            writer.Write(keyMaterial.Exponent); // PublicExponent
            writer.Write(keyMaterial.Modulus); // Modulus
            writer.Write(keyMaterial.P); // Prime1
            writer.Write(keyMaterial.Q); // Prime2
        }

        // Function NCryptImportKey uses "RSAPRIVATEBLOB" to indicate a BCRYPT_RSAPRIVATE_BLOB structure.
        // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa376276(v=vs.85).aspx.

        var key = CngKey.Import( keyBlob, new CngKeyBlobFormat( "RSAPRIVATEBLOB" ), _myProvider );

        IntPtr pPaddingInfo = question; // What do I specify here?
        byte[] pbHashValue = question; // Should I calculate hash from digest? How can I make sure it will be valid for verification?
        byte[] pbSignature = question; // Is the signature size related to hash size? Or RSA key size?
        int dwFlags = question; // Should I use BCRYPT_PAD_PKCS1 here or simply zero?

        int pcbResult;
        int result;

        using ( var hKey = key.Handle )
        {
            result = NCryptSignHash( hKey, pPaddingInfo, pbHashValue, pbHashValue.Length, pbSignature, pbSignature.Length, out pcbResult, dwFlags );
        }

        if (result != 0)
            throw new Exception();

        return TrimArray(pbSignature, pcbResult);
    }

    // As described on https://msdn.microsoft.com/en-us/library/windows/desktop/aa376295(v=vs.85).aspx.
    [DllImport("ncrypt.dll")]
    internal static extern NCryptErrorCode NCryptSignHash(
        SafeNCryptKeyHandle hKey, 
        IntPtr pPaddingInfo, 
        [MarshalAs(UnmanagedType.LPArray)] byte[] pbHashValue, 
        int cbHashValue, 
        [MarshalAs(UnmanagedType.LPArray)] byte[] pbSignature, 
        int cbSignature, 
        out int pcbResult, 
        int dwFlags);

I can successfully import the key to CNG, but I don't know how to perform the signing operation. Any help is welcome...


Solution

  • If you're on .NET 4.6 or higher, then after var key = CngKey.Import(...)

    using (RSACng rsa = new RSACng(key))
    {
        // Consider changing your signature to take a HashAlgorithmName instead of string.
        // I'm assuming you want RSA-SSA PKCS#1 v1.5 instead of RSA-SSA-PSS.
        return rsa.SignHash(digest, new HashAlgorithmName(hashAlgo), RSASignaturePadding.Pkcs1);
    }
    

    Or you can just check out https://referencesource.microsoft.com/#System.Core/System/Security/Cryptography/RsaCng.cs,672daeef0962f4ad if you want to see the P/Invokes (assuming that you're not doing anything that violates the license of referencesource.microsoft.com).