2010年8月6日

データを暗号化して保存する(1)

他システムやデータベース、FTPサーバなどのパスワードをファイルやレジストリに保存する必要がある場合、何らかの形で暗号化して保存し、使用するときに復号化する、ということが必要になります。このようなときにCryptProtectDataCryptUnprotectDataを使用する方法が考えられます。もちろんこれで万全かといわれると難しいところですが、少なくとも生のまま保存するよりはまし、といったところでしょうか。
CryptProtectDataで暗号化するときに第6パラメータ(dwFlags)にCRYPTPROTECT_LOCAL_MACHINEを含めると同一PC上の全てのユーザで復号化できてしまいますが、含めなければ同一PC上の同一のユーザでのみ復号化できます。また第3パラメータ(pOptionalEntropy)でエントロピを指定して暗号化しておけばCryptUnprotectDataで同じエントロピを指定しないと復号化できないようにすることができます。なおWindows 2000では第2パラメータのszDataDescrを省略する(NULLにする)ことができないので注意が必要です。
まず使用するCryptAPIと構造体を定義します。
uses
  Windows;

type
  _CRYPTOAPI_BLOB = record
    cbData: DWORD;
    pbData: PBYTE;
  end;
  {$EXTERNALSYM _CRYPTOAPI_BLOB}

  DATA_BLOB = _CRYPTOAPI_BLOB;
  {$EXTERNALSYM DATA_BLOB}
  PDATA_BLOB = ^DATA_BLOB;
  {$EXTERNALSYM PDATA_BLOB}

  PCRYPTPROTECT_PROMPTSTRUCT = ^CRYPTPROTECT_PROMPTSTRUCT;
  {$EXTERNALSYM PCRYPTPROTECT_PROMPTSTRUCT}
  _CRYPTPROTECT_PROMPTSTRUCT = record
    cbSize: DWORD;
    dwPromptFlags: DWORD;
    hwndApp: HWND;
    szPrompt: LPCWSTR;
  end;
  {$EXTERNALSYM _CRYPTPROTECT_PROMPTSTRUCT}
  CRYPTPROTECT_PROMPTSTRUCT = _CRYPTPROTECT_PROMPTSTRUCT;
  {$EXTERNALSYM CRYPTPROTECT_PROMPTSTRUCT}

const
  CRYPTPROTECT_LOCAL_MACHINE = $4;
  {$EXTERNALSYM CRYPTPROTECT_LOCAL_MACHINE}

function CryptProtectData(pDataIn: PDATA_BLOB; szDataDescr: PWideChar;
                          pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer;
                          pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT;
                          dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall;
  external 'crypt32.dll' name 'CryptProtectData';
{$EXTERNALSYM CryptProtectData}

function CryptUnprotectData(pDataIn: PDATA_BLOB; var ppszDataDescr: PWideChar;
                            pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer;
                            pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT;
                            dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall;
  external 'crypt32.dll' name 'CryptUnprotectData';
{$EXTERNALSYM CryptUnprotectData}

CryptProtectData/CryptUnprotectDataで扱うデータは基本的にバイナリなので、まずはTStreamを暗号化してみます。
uses
  Windows, Classes, SysUtils;

procedure ProtectStream(Source: TStream; Dest: TStream;
{$IFDEF Unicode}
                        const Description: String;
{$ELSE}
                        const Description: WideString;
{$ENDIF}
                        Entropy: TStream; LocalMachine: Boolean);
var
  DataIn: DATA_BLOB;
  OptionalEntropy: DATA_BLOB;
  POptionalEntropy: PDATA_BLOB;
  DataOut: DATA_BLOB;
  Flags: DWORD;
  MemorySource: TMemoryStream;
  MemoryEntropy: TMemoryStream;
begin

  MemorySource := nil;
  MemoryEntropy := nil;
  try
    MemorySource := TMemoryStream.Create;
    MemoryEntropy := TMemoryStream.Create;

    { DataIn }
    with MemorySource do
    begin
      Source.Position := 0;
      LoadFromStream(Source);
      DataIn.cbData := Size;
      DataIn.pbData := Memory;
    end;

    { OptionalEntropy }
    if Entropy = nil then
    begin
      POptionalEntropy := nil;
    end
    else
    begin
      with MemoryEntropy do
      begin
        Entropy.Position := 0;
        LoadFromStream(Entropy);
        OptionalEntropy.cbData := Size;
        OptionalEntropy.pbData := Memory;
      end;
      POptionalEntropy := @OptionalEntropy;
    end;

    { Flags }
    Flags := 0;
    if LocalMachine = True then
    begin
      Flags := CRYPTPROTECT_LOCAL_MACHINE;
    end;

    { DataOut }
    FillChar(DataOut,SizeOf(DataOut),0);

    { Protect data }
    if CryptProtectData(@DataIn,PWideChar(Description),POptionalEntropy,nil,
                        nil,Flags,@DataOut) = False then
    begin
      RaiseLastOSError;
    end;

    { Result }
    Dest.Write(DataOut.pbData^,DataOut.cbData);

    { Free allocated memory }
    LocalFree(HLOCAL(DataOut.pbData));

  finally
    MemorySource.Free;
    MemoryEntropy.Free;
  end;

end;

対象データと(指定されていれば)エントロピをTMemoryStreamに格納してそのMemory/SizeプロパティをDATA_BLOB構造体(=CRYPT_INTEGER_BLOB構造体)のpbData/cbDataにセットし、CryptProtectDataを呼び出します。DataOutには暗号化されたデータが格納されていますので、これを取り出し、最後に忘れずにLocalFreeでDataOutの内容を解放します。
復号化も同様に
function UnprotectStream(Source: TStream; Dest: TStream;
{$IFDEF Unicode}
                         var Description: String;
{$ELSE}
                         var Description: WideString;
{$ENDIF}
                         Entropy: TStream): Boolean;
var
  DataIn: DATA_BLOB;
  OptionalEntropy: DATA_BLOB;
  POptionalEntropy: PDATA_BLOB;
  DataOut: DATA_BLOB;
  PDescription: PWideChar;
  MemorySource: TMemoryStream;
  MemoryEntropy: TMemoryStream;
begin

  MemorySource := nil;
  MemoryEntropy := nil;
  try
    MemorySource := TMemoryStream.Create;
    MemoryEntropy := TMemoryStream.Create;

    { DataIn }
    with MemorySource do
    begin
      Source.Position := 0;
      LoadFromStream(Source);
      DataIn.cbData := Size;
      DataIn.pbData := Memory;
    end;

    { OptionalEntropy }
    if Entropy = nil then
    begin
      POptionalEntropy := nil;
    end
    else
    begin
      with MemoryEntropy do
      begin
        Entropy.Position := 0;
        LoadFromStream(Entropy);
        OptionalEntropy.cbData := Size;
        OptionalEntropy.pbData := Memory;
      end;
      POptionalEntropy := @OptionalEntropy;
    end;

    { DataOut }
    FillChar(DataOut,SizeOf(DataOut),0);

    { Unprotect data }
    if CryptUnprotectData(@DataIn,PDescription,POptionalEntropy,nil,
                          nil,0,@DataOut) = False then
    begin
      Result := False;
      Exit;
    end;

    { Result }
    Dest.Write(DataOut.pbData^,DataOut.cbData);

    { Description }
    Description := PDescription;

    { Free allocated memory }
    LocalFree(HLOCAL(DataOut.pbData));
    LocalFree(HLOCAL(PDescription));

    Result := True;

  finally
    MemorySource.Free;
    MemoryEntropy.Free;
  end;

end;

対象データと(指定されていれば)エントロピをTMemoryStreamに格納してそのMemory/SizeプロパティをDATA_BLOB構造体のpbData/cbDataにセットし、CryptUnprotectDataを呼び出します。DataOutとPDescriptionには復号化されたデータと暗号化のときに指定したDescriptionが格納されていますので、これらを取り出し、最後に忘れずにLocalFreeでDataOutとPDescriptionの内容を解放します。もし復号化に失敗した場合は戻値がFalseになります。

長くなったので続きます。

元ねたはNyaRuRuさんローカルストレージに保存するデータの暗号化 ― Windows の場合 - NyaRuRuの日記EternalWindowsさんDPAPIによる暗号化

0 件のコメント: