2010年8月10日

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

前回CryptProtectDataCryptUnprotectDataでバイナリデータをTStream経由で暗号化、復号化しましたが、通常は単純に文字列を扱いたいことのほうが多いと思います。そこで簡略版として文字列のみで処理を構成してみました。
uses
  Windows, Classes, SysUtils;

function BlobToString(P: PByte; Size: DWORD): String;
begin

  Result := '';

  while Size > 0 do
  begin
    Result := Result + IntToHex(P^,2);
    Inc(P,1);
    Size := Size - 1;
  end;

end;

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

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

    { DataIn }
    with MemorySource do
    begin
      Write(PChar(Source)^,Length(Source) * SizeOf(Char));
      DataIn.cbData := Size;
      DataIn.pbData := Memory;
    end;

    { OptionalEntropy }
    if Entropy = '' then
    begin
      POptionalEntropy := nil;
    end
    else
    begin
      with MemoryEntropy do
      begin
        Write(PChar(Entropy)^,Length(Entropy) * SizeOf(Char));
        OptionalEntropy.cbData := Size;
        OptionalEntropy.pbData := Memory;
      end;
      POptionalEntropy := @OptionalEntropy;
    end;

    { Description }
    WDescription := Description;

    { 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(WDescription),POptionalEntropy,nil,
                        nil,Flags,@DataOut) = False then
    begin
      RaiseLastOSError;
    end;

    { Result }
    Dest := BlobToString(DataOut.pbData,DataOut.cbData);

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

  finally
    MemorySource.Free;
    MemoryEntropy.Free;
  end;

end;
Source、Description、Entropyに文字列を渡すとDestに暗号化されたデータが16進文字列化されて格納されます。
復号化も同様に
function StringToBlob(const S: String; Stream: TStream): Boolean;
var
  Index: Integer;
  Data: Byte;
  L: Integer;
begin

  Index := 1;
  L := Length(S);

  while Index <= L do
  begin
    try
      Data := StrToInt('$' + Copy(S,Index,2));
    except
      Result := False;
      Exit;
    end;

    Stream.Write(Data,SizeOf(Data));

    Index := Index + 2;
  end;

  Result := True;

end;

function  UnprotectString(const Source: String; var Dest: String;
                          var Description: String; const Entropy: String): 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 }
    if StringToBlob(Source,MemorySource) = False then
    begin
      Result := False;
      Exit;
    end;
    with MemorySource do
    begin
      DataIn.cbData := Size;
      DataIn.pbData := Memory;
    end;

    { OptionalEntropy }
    if Entropy = '' then
    begin
      POptionalEntropy := nil;
    end
    else
    begin
      with MemoryEntropy do
      begin
        Write(PChar(Entropy)^,Length(Entropy) * SizeOf(Char));
        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 }
    SetString(Dest,PChar(DataOut.pbData),DataOut.cbData div SizeOf(Char));

    { Description }
    Description := PDescription;

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

    Result := True;

  finally
    MemorySource.Free;
    MemoryEntropy.Free;
  end;

end;
Source、Entropyに文字列を渡すとDest、Descriptionに復号化された文字列が格納されます。

蛇足ですが、自システムのパスワードはSHAなど(MD5はいまさらな気も)何らかの形でハッシュして保存しておき、入力も同様の方法でハッシュして結果の一致を確認する、という手法が一般的です。

0 件のコメント: