【问题标题】:BCryptEncrypt returns STATUS_INVALID_PARAMETER on AES-GCMBCryptEncrypt 在 AES-GCM 上返回 STATUS_INVALID_PARAMETER
【发布时间】:2020-11-12 22:35:41
【问题描述】:

我正在尝试使用 CNG Windows API 实现 AES GCM,但停留在最后一步。


免责声明:不要害怕这么多代码,其中大部分只是 WinAPI 函数和结构声明,向下滚动到实际问题文本。谢谢。


用途:

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

接口部分不是这里一切都是正确的(参见accepted answer,添加以防万一有人尝试复制)

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;

实施:

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;

这里开始了实际问题

我知道似乎有很多代码,但这不是重点。除了最后一个返回STATUS_INVALID_PARAMETER (0xC000000D) 的BCryptEncrypt() 调用之外,所有功能都运行良好。

我尝试关注文档,所以我不知道哪些参数不适合。我猜问题出在BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构中,但找不到。

那么,亲爱的 WinAPI 大神,你能指出我做错了什么吗?我非常感谢任何帮助:链接、调试想法等。

我是这样称呼这个函数的:

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;

附注我正在使用 Delphi 10.3 社区。​​em>


更新

我尝试检查此 API 在 C++ 中的工作方式,然后……看看。这是BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO 结构(复制自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;

让我们计算总大小:4 + 4 + 8 + 4 + 8 + 4 + 8 + 4 + 8 + 4 + 4 + 8 + 4 = 72但是 sizeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO) 返回88。我不知道 16 个额外的字节是从哪里来的。谁能解释一下?

【问题讨论】:

  • @RbMm,我的数据长 16 个字节,必须是 16 的倍数。另外关于 BCRYPT_BLOCK_PADDING,引用自文档:"此标志不得与经过身份验证的加密一起使用模式(AES-CCM 和 AES-GCM)。”。 AES 是对称算法,那么如何使用非对称密钥对呢?或者当文档中需要时,我怎么能不通过BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO?抱歉,不太清楚您的留言。
  • @RitaHan-MSFT,这很糟糕,但我找到了解决办法。刚刚将packed record 替换为record,我认为delphi 编译器以某种方式在内部修复了该填充。
  • 例如 pbAuthData 必须在 x64 上对齐 8 个字节,因此在 cbNoncepbAuthData 之间将填充 4 个字节,您无需考虑。等等
  • 所以这里有 4 个字节的 3 个填充 - (cbNonce, pbAuthData) (cbAuthData, pbTag )(cbTag,pbMacContext) 并且结构的大小必须是对齐的倍数。结构对齐 8 个字节。所以必须在 and 处填充 4 个字节。 +4*4=16 字节

标签: delphi winapi cryptography aes cng


【解决方案1】:

BCryptEncrypt() 调用返回 STATUS_INVALID_PARAMETER (0xC000000D)。

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

显示上面函数中传递给我的值,然后你可以和你的比较。

更新:

对比后发现BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO结构体的大小(cbSize)计算错误。它需要88,但传入72。与“Padding and Alignment of Structure Members”相关。

cbSize

此结构的大小(以字节为单位)。不要直接设置该字段。 请改用BCRYPT_INIT_AUTH_MODE_INFO 宏。

小更新

为了让问题中的代码正常工作,请将BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO = packed record 更改为BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO = record

【讨论】:

  • @OlvinR​​oght dwFlags 参数的值是多少BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
  • @OlvinR​​oght 你可以查看this thread。首次调用BCryptEncrypt 成功。
  • 我不知道我做错了什么,但是literally same code,刚刚在delphi上实现返回错误,纯属疯狂。最糟糕的是整个结构(除了大小和版本)只是指向数据的指针和该数据的大小,所以它不会出错。
  • @OlvinR​​oght 你试过将pPaddingInfo设置为NULL吗?
  • 是的,同样的错误信息。但我可以看到其中的逻辑,GCM 模式期望这种结构。 .... 安装 Visual Studio,想在 C++ 中本地检查此代码。
猜你喜欢
  • 2015-08-23
  • 2021-05-29
  • 1970-01-01
  • 2018-02-19
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 2021-09-23
  • 2013-07-17
相关资源
最近更新 更多