Search code examples
delphiwinapicryptographyaescng

BCryptEncrypt returns STATUS_INVALID_PARAMETER on AES-GCM


I'm trying to implement AES GCM using CNG Windows API and stuck on last step.


Disclaimer: Do not be afraid with that amount of code, most of it is just WinAPI functions and structures declaration, scroll down to actual question text. Thanks.


Uses:

uses System.Classes, Winapi.Windows, System.SysUtils;

Interface section (NOT everything is correct here (see accepted answer), added just in case if somebody will try to reproduce):

type
  BCRYPT_KEY_LENGTHS_STRUCT = packed record
    dwMinLength, dwMaxLength, dwIncrement: ULONG;
  end;
  BCRYPT_AUTH_TAG_LENGTHS_STRUCT = BCRYPT_KEY_LENGTHS_STRUCT;
  
  BCRYPT_KEY_DATA_BLOB_HEADER = packed record
    dwMagic, dwVersion, cbKeyData: ULONG;
  end;
  
  BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO = packed record
    cbSize, dwInfoVersion: ULONG;
    pbNonce: Pointer;
    cbNonce: ULONG;
    pbAuthData: Pointer;
    cbAuthData: ULONG;
    pbTag: Pointer;
    cbTag: ULONG;
    pbMacContext: Pointer;
    cbMacContext, cbAAD: ULONG;
    cbData: ULONGLONG;
    dwFlags: ULONG;
  end;

const
  BCRYPT_CHAINING_MODE = 'ChainingMode';
  BCRYPT_CHAIN_MODE_GCM = 'ChainingModeGCM';
  BCRYPT_AUTH_TAG_LENGTH = 'AuthTagLength';
  BCRYPT_KEY_DATA_BLOB = 'KeyDataBlob';
  //
  BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION = $00000001;
  //
  BCrypt = 'Bcrypt.dll';

function BCryptOpenAlgorithmProvider(var phAlgorithm: Pointer;
  pszAlgId: PWideChar; pszImplementation: PWideChar; dwFlags: ULONG): DWORD;
  stdcall; external BCrypt;
function BCryptSetProperty(hObject: Pointer; pszProperty: PWideChar;
  pbInput: Pointer; cbInput: ULONG; dwFlags: ULONG): DWORD; stdcall;
  external BCrypt;
function BCryptGetProperty(hObject: Pointer; pszProperty: PWideChar;
  pbOutput: Pointer; cbOutput: ULONG; var pcbResult: ULONG; dwFlagd: ULONG)
  : DWORD; stdcall; external BCrypt;
function BCryptGenerateSymmetricKey(hAlgorithm: Pointer; var phKey: Pointer;
  pbKeyObject: Pointer; cbKeyObject: ULONG; pbSecret: Pointer; cbSecret: ULONG;
  dwFlags: ULONG): DWORD; stdcall; external BCrypt;
function BCryptGenRandom(phAlgorithm: Pointer; pbBuffer: Pointer;
  cbBuffer: ULONG; dwFlags: ULONG): DWORD; stdcall; external BCrypt;
function BCryptExportKey(hKey: Pointer; hExportKey: Pointer;
  pszBlobType: PWideChar; pbOutput: Pointer; cbOutput: ULONG;
  var pcbResult: ULONG; dwFlags: ULONG): DWORD; stdcall; external BCrypt;
function BCryptEncrypt(hKey: Pointer; pbInput: Pointer; cbInput: ULONG;
  pPaddingInfo: Pointer; pbIV: Pointer; cbIV: ULONG; pbOutput: Pointer;
  cbOutput: ULONG; var pcbResult: ULONG; dwFlags: ULONG): DWORD; stdcall;
function BCryptDestroyKey(hKey: Pointer): DWORD; stdcall; external BCrypt;
function BCryptCloseAlgorithmProvider(hAlgorithm: Pointer; dwFlags: ULONG)
  : DWORD; stdcall; external BCrypt;

Implementation:

function GetCryptoRandomBytes(var Buffer: TBytes; Size: DWORD): Boolean;
var
  Status: DWORD;
  hAlgorithm, hKey: Pointer;
begin
  result := False;
  Status := BCryptOpenAlgorithmProvider(hAlgorithm,
    BCRYPT_RNG_ALGORITHM, nil, 0);
  if Status = 0 then
  begin
    SetLength(Buffer, Size);
    Status := BCryptGenRandom(hAlgorithm, Buffer, Size, 0);

    if Status = 0 then
      result := True;
  end;
  BCryptCloseAlgorithmProvider(hAlgorithm, 0)
end;


function AESGCMEncrypt(var Data, AAD, Key, IV, Tag, EncryptedData: TBytes;
  KeyLength: DWORD = 32): Boolean;
const
  AES_GCM_IV_LENGTH = 12;  // nonce
var
  Status, KeyLen, BytesDone, BlockLength: DWORD;
  hAlgorithm, hKey: Pointer;
  TagLength: BCRYPT_AUTH_TAG_LENGTHS_STRUCT;
  KeyDataBlobHeader: BCRYPT_KEY_DATA_BLOB_HEADER;
  AuthCiferModeInfo: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO;
  KeyTemp: TBytes;
begin
  result := False;
  BytesDone := 0;

  Status := BCryptOpenAlgorithmProvider(hAlgorithm,
    BCRYPT_AES_ALGORITHM, nil, 0);
  if Status = 0 then
  begin
    KeyLen := Length(BCRYPT_CHAIN_MODE_GCM);
    Status := BCryptSetProperty(hAlgorithm, BCRYPT_CHAINING_MODE,
      PChar(BCRYPT_CHAIN_MODE_GCM), BytesDone, 0);

    if Status = 0 then
    begin
      KeyLen := SizeOf(TagLength);
      Status := BCryptGetProperty(hAlgorithm, BCRYPT_AUTH_TAG_LENGTH,
        @TagLength, KeyLen, BytesDone, 0);

      if (Status = 0) and GetCryptoRandomBytes(KeyTemp, KeyLength) then
      begin
        Status := BCryptGenerateSymmetricKey(hAlgorithm, hKey, nil, 0, KeyTemp,
          KeyLength, 0);

        if Status = 0 then
        begin
          Status := BCryptExportKey(hKey, nil, BCRYPT_KEY_DATA_BLOB, nil, 0,
            KeyLen, 0);  // Get size

          if Status = 0 then
          begin
            SetLength(KeyTemp, KeyLen);
            Status := BCryptExportKey(hKey, nil, BCRYPT_KEY_DATA_BLOB, KeyTemp,
              KeyLen, KeyLen, 0);

            if Status = 0 then
            begin
              Move(KeyTemp[0], KeyDataBlobHeader, SizeOf(KeyDataBlobHeader));
              SetLength(Key, KeyDataBlobHeader.cbKeyData);
              Move(KeyTemp[SizeOf(KeyDataBlobHeader)], Key[0],
                KeyDataBlobHeader.cbKeyData);

              if GetCryptoRandomBytes(IV, AES_GCM_IV_LENGTH) then
              begin
                SetLength(Tag, TagLength.dwMaxLength);
                SetLength(EncryptedData, Length(Data)); // same length as source

                FillChar(AuthCiferModeInfo, SizeOf(AuthCiferModeInfo), #0);
                with AuthCiferModeInfo do
                begin
                  cbSize := SizeOf(AuthCiferModeInfo);
                  dwInfoVersion := BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION;
                  pbNonce := IV;
                  cbNonce := AES_GCM_IV_LENGTH;
                  pbAuthData := AAD;
                  cbAuthData := Length(AAD);
                  pbTag := Tag;
                  cbTag := TagLength.dwMaxLength;
                end;

                KeyLen := Length(Data);
                Status := BCryptEncrypt(hKey, Data, KeyLen, @AuthCiferModeInfo,
                  nil, 0, EncryptedData, KeyLen, BytesDone, 0);
                  
                // Status = $C000000D = STATUS_INVALID_PARAMETER

                if Status = 0 then
                  result := True
                else // Free all buffers
                begin
                  SetLength(Tag, 0);
                  SetLength(EncryptedData, 0);
                  SetLength(Key, 0);
                  SetLength(IV, 0);
                end;
              end
              else // Free all buffers
              begin
                SetLength(Key, 0);
                SetLength(IV, 0);
              end;
            end;
          end;
          BCryptDestroyKey(hKey);
        end;
        SetLength(KeyTemp, 0); // Free buffer
      end;
    end;
  end;
  BCryptCloseAlgorithmProvider(hAlgorithm, 0);
end;

Here actual question started

I know it seems like there's a lot of code, but it's not the point. All works perfectly except last BCryptEncrypt() call which returns STATUS_INVALID_PARAMETER (0xC000000D).

I've tried to follow docs, so I have no idea which of parameters doesn't fit. I guess issue is in BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure, but can't find it.

So, dear WinAPI Gods, could you please point me what have I done wrong? I would really appreciate any help: links, debugigng ideas, etc.

Here's how I call this function:

var
  Key, Tag, IV, AAD, Data, EncData: TBytes;
  src, add_data: string;
begin
  src := 'test_string_1234';
  add_data := '12345678';
  Data := TEncoding.UTF8.GetBytes(src);
  AAD := TEncoding.UTF8.GetBytes(add_data);

  AESGCMEncrypt(Data, AAD, Key, IV, Tag, EncData);
end;

P.S. I'm using Delphi 10.3 Community.


UPDATE

I've tried to check how this API works in C++ and ... just look. Here is BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure (copied from docs):

typedef struct _BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO {
  ULONG     cbSize;         // 4 bytes
  ULONG     dwInfoVersion;  // 4 bytes
  PUCHAR    pbNonce;        // 8 bytes (on x64)
  ULONG     cbNonce;        // 4 bytes
  PUCHAR    pbAuthData;     // 8 bytes (on x64)
  ULONG     cbAuthData;     // 4 bytes
  PUCHAR    pbTag;          // 8 bytes (on x64)
  ULONG     cbTag;          // 4 bytes
  PUCHAR    pbMacContext;   // 8 bytes (on x64)
  ULONG     cbMacContext;   // 4 bytes
  ULONG     cbAAD;          // 4 bytes
  ULONGLONG cbData;         // 8 bytes
  ULONG     dwFlags;        // 4 bytes
} BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO, *PBCRYPT_AUTHENTICATED_CIPHER_MODE_INFO;

Let's calculate total size: 4 + 4 + 8 + 4 + 8 + 4 + 8 + 4 + 8 + 4 + 4 + 8 + 4 = 72. BUT sizeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO) returns 88. I have no clue, where 16 additional bytes came from. Could somebody explain me??


Solution

  • BCryptEncrypt() call which returns STATUS_INVALID_PARAMETER (0xC000000D).

    BCryptEncrypt(keyHandle, pt, sizeof(pt), &authInfo, NULL, 0, ct, sizeof(ct), &bytesDone, 0);
    

    Show values passed in above function that work for me, then you can compare with yours.

    enter image description here

    Update:

    After comparing, it turns out that the size (cbSize) of BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure is wrong calculated. It expects 88 but pass in 72. It is related to "Padding and Alignment of Structure Members".

    cbSize

    The size, in bytes, of this structure. Do not set this field directly. Use the BCRYPT_INIT_AUTH_MODE_INFO macro instead.

    Small update

    To let code from question work properly change BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO = packed record to BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO = record.