Search code examples
windowscryptographycng

CryptoNG: Exporting RSA key with BCryptExportKey fails with STATUS_INVALID_HANDLE


Using the Cryptography Next Generation API (aka CryptoAPI Next Generation, aka CryptoNG, aka Cng, aka BestCrypt, aka bcrypt), i'm trying to export a newly generated RSA private key pair:

Edit: Shorter code version:

BCRYPT_ALG_HANDLE alg;
BCryptOpenAlgorithmProvider(out alg, BCRYPT_RSA_ALGORITHM, null, 0);

BCRYPT_KEY_HANDLE key;
BCryptGenerateKeyPair(alg, out key, 4096, 0);

DWORD cbResult;
BCryptExportKey(key, 0, BCRYPT_RSAFULLPRIVATE_BLOB, null, 0, out cbResult, 0);

Longer code version:

NTSTATUS nt;

// Open the RSA algorithm provider
BCRYPT_ALG_HANDLE alg;
nt = BCryptOpenAlgorithmProvider(out alg, BCRYPT_RSA_ALGORITHM, null, 0);
NTStatusCheck(nt);
      // Successfully opened the RSA algorithm provider

// Generate a 4096 bit RSA public-private key pair
BCRYPT_KEY_HANDLE key;
nt = BCryptGenerateKeyPair(alg, out key, 4096, 0);
NTStatusCheck(nt);
      // Successfully generates a key pair (key <-- $4E737A0)

// Ask for the buffer size required to export the key pair
DWORD cbResult;
nt = BCryptExportKey(key, 0, BCRYPT_RSAFULLPRIVATE_BLOB, null, 0, out cbResult, 0);
NTStatusCheck(nt);
      // Fails with 0xC0000008 (STATUS_INVALID_HANDLE)

What am I doing wrong?

Bonus Reading

Complete minimal example (Delphi)

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, ComObj, ActiveX;

type
  NTSTATUS = Cardinal;
const
    BCRYPT_RSA_ALGORITHM:       WideString = 'RSA';
  BCRYPT_RSAFULLPRIVATE_BLOB: WideString = 'RSAFULLPRIVATEBLOB';
  BCRYPT_RSAPRIVATE_BLOB:     WideString = 'RSAPRIVATEBLOB';
  BCRYPT_RSAPUBLIC_BLOB:      WideString = 'RSAPUBLICBLOB';
  LEGACY_RSAPRIVATE_BLOB:     WideString = 'CAPIPRIVATEBLOB';
  LEGACY_RSAPUBLIC_BLOB:      WideString = 'CAPIPUBLICBLOB';

function BCryptOpenAlgorithmProvider(out hAlgorithm: THandle; pszAlgId, pszImplementation: PWideChar; dwFlags: Cardinal): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptGenerateKeyPair(hAlgorithm: THandle; out phKey: THandle; dwLength: Cardinal; dwFlags: Cardinal): NTSTATUS; stdcall; external 'bcrypt.dll';
function BCryptExportKey(hKey: THandle; hExportKey: THandle; pszBlobType: PWideChar; pbOutput: Pointer; cbOutput: Cardinal; out pcbResult: Cardinal; dwFlags: Cardinal): NTSTATUS; stdcall; external 'bcrypt.dll';


procedure Main;
var
  nt: Cardinal;
  alg: THandle;
  key: THandle;
  cbResult: Cardinal;
begin
  // Open the RSA algorithm provider
  WriteLn('Opening algorithm provider');
  nt := BCryptOpenAlgorithmProvider({out} alg, PWideChar(BCRYPT_RSA_ALGORITHM), nil, 0);
  OleCheck(HRESULT(nt));

  // Generate a RSA public-private key pair
  WriteLn('Generating key pair');
  key := 0;
  nt := BCryptGenerateKeyPair(alg, {out} key, 1024, 0);
  OleCheck(HRESULT(nt));

  // Ask for the buffer size required to export the key pair
  WriteLn('Exporting full private blob');
  cbResult := 0;
  nt := BCryptExportKey(key, 0, PWideChar(BCRYPT_RSAFULLPRIVATE_BLOB), nil, 0, {out} cbResult, 0);
  OleCheck(HRESULT(nt));

  WriteLn('Success');
end;

begin
    Main;
  WriteLn('Press enter to close...');
  ReadLn;
end.

Complete Minimal Example (C#)

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        [DllImport("bcrypt.dll", CharSet = CharSet.Unicode)]
        internal static extern Int32 BCryptOpenAlgorithmProvider(out IntPtr phAlgorithm, [In] string pszAlgId, [In] string pszImplementation, [In] int dwFlags);

        [DllImport("bcrypt.dll")]
        internal static extern Int32 BCryptGenerateKeyPair([In] IntPtr hAlgorithm, out IntPtr phKey, [In] UInt32 dwLength, [In] UInt32 dwFlags);

        [DllImport("bcrypt.dll", CharSet = CharSet.Unicode)]
        internal static extern Int32 BCryptExportKey([In] IntPtr hKey, [In] IntPtr hExportKey, [MarshalAs(UnmanagedType.LPWStr)] [In] string pszBlobType, [MarshalAs(UnmanagedType.LPArray)] [Out] byte[] pbOutput, [In] int cbOutput, [In] ref UInt32 cbResult, [In] int dwFlags);

        static void Main(string[] args)
        {
            Int32 nt;

            IntPtr alg;
            nt = BCryptOpenAlgorithmProvider(out alg, "RSA", null, 0);
            if (nt < 0)
                throw new COMException("Open algorithm", (int)nt);

            IntPtr key;
            nt = BCryptGenerateKeyPair(alg, out key, 1024, 0);
            if (nt < 0)
                throw new COMException("Generate key", nt);

            UInt32 cbResult = 0;
            nt = BCryptExportKey(key, IntPtr.Zero, "RSAPRIVATEBLOB", null, 0, ref cbResult, 0);
            if (nt < 0)
                throw new COMException("Export", nt);
        }
    }
}

Solution

  • After BCryptGenerateKeyPair you should call BCryptFinalizeKeyPair. Before BCryptFinalizeKeyPair key pair doesn't actually exist.

    After you create a key by using this function, [...] the key cannot be used until the BCryptFinalizeKeyPair function is called.

    So:

    BCRYPT_ALG_HANDLE alg;
    BCryptOpenAlgorithmProvider(out alg, BCRYPT_RSA_ALGORITHM, null, 0);
    
    BCRYPT_KEY_HANDLE key;
    BCryptGenerateKeyPair(alg, out key, 4096, 0);
    
    BCryptFinalizeKeyPair(key, 0); //finalize the key so we can use it
    
    DWORD cbResult;
    BCryptExportKey(key, 0, BCRYPT_RSAFULLPRIVATE_BLOB, null, 0, out cbResult, 0);