4

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??

Olvin Roght
  • 7,677
  • 2
  • 16
  • 35
  • @RbMm, my data is 16 bytes long, which have to be multiple to 16. Also about `BCRYPT_BLOCK_PADDING`, quote from docs: *"This flag must not be used with the authenticated encryption modes (AES-CCM and AES-GCM)."*. AES is symmetric algorithm, so how can I use asymmetric key pair? Or how can I not pass `BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO`, when it required in docs? Sorry, don't clearly understand your message. – Olvin Roght Nov 13 '20 at 07:13
  • 1
    It seems related to [Padding and Alignment of Structure Members](https://learn.microsoft.com/en-us/cpp/c-language/padding-and-alignment-of-structure-members?view=msvc-160). – Rita Han Nov 13 '20 at 09:47
  • @RitaHan-MSFT, It's sick, but I found a soluction. Just replaced `packed record` with `record`, I think delphi compiler somehow fix that padding internally. – Olvin Roght Nov 13 '20 at 09:51
  • for example `pbAuthData` must be aligned on 8 bytes on x64, so between `cbNonce` and `pbAuthData` will be pad 4 bytes which you not take to account. and so on – RbMm Nov 13 '20 at 12:15
  • 1
    so here 3 padding of 4 bytes - (cbNonce, pbAuthData) (cbAuthData, pbTag )(cbTag,pbMacContext) and size of structure must be multiple of it aligned. structure aligned on 8 bytes. so must be pad 4 bytes at the and too. +4*4=16 bytes – RbMm Nov 13 '20 at 12:20
  • @RbMm, yes, thanks to *Rita* I've found what was wrong. Thank you for clarification anyway. – Olvin Roght Nov 13 '20 at 12:22

1 Answers1

4

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.

Olvin Roght
  • 7,677
  • 2
  • 16
  • 35
Rita Han
  • 9,574
  • 1
  • 11
  • 24
  • @OlvinRoght What's the value of `dwFlags` paremeter of `BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO`? – Rita Han Nov 13 '20 at 07:19
  • @OlvinRoght You can check [this thread](https://learn.microsoft.com/en-us/answers/questions/86872/is-it-possible-to-encrypt-a-non-multiple-of-16-byt.html). The first call of `BCryptEncrypt` successfully. – Rita Han Nov 13 '20 at 07:31
  • I have no idea what am I doing wrong, but [literally same code](https://learn.microsoft.com/answers/answers/88051/view.html), just implemented on delphi returns error, pure madness. The worst is that whole structure (*except size and version*) is just pointers to data and size of that data, so it can't be wrong. – Olvin Roght Nov 13 '20 at 07:56
  • @OlvinRoght Have you tried set `pPaddingInfo` to `NULL`? – Rita Han Nov 13 '20 at 08:04
  • Yes, same error message. But I can see logic in that, GCM mode expect that structure. .... Installing Visual Studio, want to check this code natively in C++. – Olvin Roght Nov 13 '20 at 08:06
  • @OlvinRoght But it helps. So the error is not in this structure. Continue to check other parameters. This works for me: `BCryptEncrypt(keyHandle, pt, sizeof(pt), NULL, NULL, 0, NULL, 0, &bytesDone, 0);` So left are second and third ones. – Rita Han Nov 13 '20 at 08:12
  • Surprisingly, this returns 0 (success). But when I add buffer to output - failed. – Olvin Roght Nov 13 '20 at 08:16
  • @OlvinRoght If you mean [`pbOutput`](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptencrypt), what's the return value of `pcbResult` when `pbOutput` set to `NULL`. – Rita Han Nov 13 '20 at 08:32
  • It's `16` which is exactly same as input length. – Olvin Roght Nov 13 '20 at 08:35