3

I am trying to use CryptProtectData in Delphi XE7 on Windows 10. I use the David's answer as a base.

The code works when compiled on 32-bit platform, but I can't get it to work on 64-bit platform (complies ok, but I have runtime errors).

Here's the code (I slightly changed the David's code to use pOptionalEntropy parameter):

unit Unit1;

interface

uses
  System.SysUtils, Winapi.Windows;

const
  CRYPTPROTECT_LOCAL_MACHINE = 4;

type
  TLargeByteArray = array [0 .. Pred(MaxInt)] of byte;
  PLargeByteArray = ^TLargeByteArray;

  _CRYPTOAPI_BLOB = record
    cbData: DWORD;
    pbData: PByte;
  end;

  DATA_BLOB = _CRYPTOAPI_BLOB;
  PDATA_BLOB = ^DATA_BLOB;

type
  _CRYPTPROTECT_PROMPTSTRUCT = record
    cbSize: DWORD;
    dwPromptFlags: DWORD;
    hwndApp: HWND;
    szPrompt: PWideChar;
  end;

  CRYPTPROTECT_PROMPTSTRUCT = _CRYPTPROTECT_PROMPTSTRUCT;
  PCRYPTPROTECT_PROMPTSTRUCT = ^CRYPTPROTECT_PROMPTSTRUCT;

function CryptProtectData(pDataIn: PDATA_BLOB;
  szDataDescr: PWideChar; pOptionalEntropy: PDATA_BLOB;
  pReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD;
  pDataOut: PDATA_BLOB): BOOL; stdcall; external 'Crypt32.dll';

function CryptUnprotectData(pDataIn: PDATA_BLOB; ppszDataDescr: PPWideChar;
  pOptionalEntropy: PDATA_BLOB; pReserved: Pointer;
  pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD;
  pDataOut: PDATA_BLOB): BOOL; stdcall; external 'Crypt32.dll';

function EncryptData(const AStr: String; const AdditionalEntropy: string): string;


implementation


procedure FreeDataBlob(var Data: DATA_BLOB);
begin
  if Assigned(Data.pbData) then
    LocalFree(HLOCAL(Data.pbData));
  FillChar(Data, SizeOf(DATA_BLOB), 0);
end;

function GetDataBlobText(Data: DATA_BLOB): string;
begin
  SetString(Result, PChar(Data.pbData), Data.cbData div SizeOf(Char));
end;

function SetDataBlobText(const Text: string; var Data: DATA_BLOB): Boolean;
begin
  FillChar(Data, SizeOf(DATA_BLOB), 0);
  if Length(Text) > 0 then
  begin
    Data.cbData := SizeOf(Char) * Length(Text);
    Data.pbData := Pointer(LocalAlloc(LPTR, Data.cbData));
    if Assigned(Data.pbData) then
    begin
      Move(Pointer(Text)^, Data.pbData^, Data.cbData);
      Result := True;
    end
    else
      Result := False;
  end
  else
    Result := True;
end;

function EncryptData(const AStr: String; const AdditionalEntropy: string): string;
var
  DataIn: DATA_BLOB;
  DataOut: DATA_BLOB;
  DataEntropy: DATA_BLOB;
  pDataEntropy: Pointer;
  bRes: Boolean;
begin
  if SetDataBlobText(AStr, DataIn) then
  begin
    DataOut.cbData := 0;
    DataOut.pbData := nil;
    try
      pDataEntropy := nil;
      if AdditionalEntropy <> '' then
      begin
        if not SetDataBlobText(AdditionalEntropy, DataEntropy) then
          Exit;
        pDataEntropy := @DataEntropy;
      end;

      try
        FillChar(DataOut, SizeOf(DATA_BLOB), 0);
        bRes := CryptProtectData(
                  @DataIn,
                  nil, //data description (PWideChar)
                  pDataEntropy, //optional entropy (PDATA_BLOB)
                  nil, //reserved
                  nil, //prompt struct
                  CRYPTPROTECT_LOCAL_MACHINE, //flags
                  @DataOut
                );
        if bRes then
        begin
          Result := GetDataBlobText(DataOut);

          FreeDataBlob(DataOut);
        end
        else
          RaiseLastOSError;
      finally
        if pDataEntropy <> nil then
          FreeDataBlob(DataEntropy);
      end;

    finally
      FreeDataBlob(DataIn);
    end;
  end;
end;

end.

When the AdditionalEntropy constant is not empty, I fill the entropy parameter in CryptProtectData, but CryptProtectData returns False and last OS error is the following:

'c0000005 ACCESS_VIOLATION'

When the AdditionalEntropy constant is empty, I pass nil to the third parameter of CryptProtectData, and this time I get

'System Error. Code: 87. The parameter is incorrect'

In the latter case the program sometimes hangs.

What is wrong with the code?

whosrdaddy
  • 11,720
  • 4
  • 50
  • 99
Petro K
  • 148
  • 8
  • 1
    What is the effective [offset of `pbData`](https://stackoverflow.com/q/8460862/11683) in your `_CRYPTOAPI_BLOB`? – GSerg Dec 16 '18 at 12:02
  • 1
    @GSerg: Should be 8, but who knows? Good question. `Writeln(NativeUInt(@PDATA_BLOB(nil)^.pbData));` or similar should give the answer. – Rudy Velthuis Dec 16 '18 at 12:35
  • @GSerg cbData occupies 4 bytes. – Petro K Dec 16 '18 at 12:36
  • 1
    @PetroK: yes, but since `pbData` is an 8 byte type (a 64 bit pointer), it should be aligned on an 8 byte boundary. So what is its offset? You can test that with the `Writeln` from my other comment. – Rudy Velthuis Dec 16 '18 at 12:38
  • @RudyVelthuis It is 4 too. – Petro K Dec 16 '18 at 12:47
  • It seems, replacing DWORD with NativeUInt does the trick. – Petro K Dec 16 '18 at 12:48
  • 1
    @PetroK. Huh? pbData should **never** be aligned on 4 in 64 bit. Are you sure you did not use an alignment setting of 1, 2 or 4, e.g. in code {$ALIGN OFF}. {$ALIGN 1}, {$A-} or {$A1}, or as setting in the project options? Alignment should be the default of 8, both in Win32 and Win64. – Rudy Velthuis Dec 16 '18 at 12:55
  • 1
    Just add a line {$ALIGN 8} above the declaration of the record. Don't use NativeUInt, even if that works. DWORD is DWORD. – Rudy Velthuis Dec 16 '18 at 12:59
  • @RudyVelthuis Thank you! I have just checked the project options and found that 'Record Field Alignment' option is 'Byte', while in all other my projects it is 'Quad word' (which it seems is default). Don't know how it appeared there... After changing 'Byte' to 'Quad word' the problem has gone away. – Petro K Dec 16 '18 at 15:47
  • @PetroK: Quad word should be default, indeed. Glad you found the error, but it was GSerg's idea to check the offset. – Rudy Velthuis Dec 16 '18 at 15:50
  • @RudyVelthuis Yes, thanks to GSerg too. Unfortunately, I am unable yet to vote. – Petro K Dec 16 '18 at 16:13
  • No one is unable to vote, AFAIK. But only on answers. – Rudy Velthuis Dec 16 '18 at 16:18
  • @RudyVelthuis I registered today. Vote Up privilege is awarded at 15 Reputation. I didn't reach it yet :) – Petro K Dec 16 '18 at 16:24
  • @PetroK: OK, I see that now. That is so long ago that I didn't remember you need 15 reputation even to vote. – Rudy Velthuis Dec 16 '18 at 19:55

0 Answers0