13

機密情報を含むローカルデータベースを保護しようとしています(この質問と同様、delphi 2010のみ)

AES暗号化をサポートするDISQLiteコンポーネントを使用していますが、データベースの復号化と読み取りに使用するこのパスワードを保護する必要があります。

私の最初のアイデアは、ランダムなパスワードを生成し、DPAPI(CryptProtectDataおよびCryptUnprotectDataCrypt32.dllにある関数)のようなものを使用して保存することでしたが、Delphiの例は見つかりませんでした

私の質問は、ランダムに生成されたパスワードを安全に保存するにはどうすればよいですか?または、DPAPI道路が安全であると仮定して、DelphiでこのDPAPIを実装するにはどうすればよいですか?

4

3 に答える 3

18

WindowsのDPAPIを使用することをお勧めします。他の方法を使用するよりもはるかに安全です。

  • CryptProtectData / CryptProtectMemory
  • CryptUnprotectData / CryptUnprotectMemory

CryptProtectMemory / CryptUnprotectMemoryは、より高い柔軟性を提供します。

  • CRYPTPROTECTMEMORY_SAME_PROCESS:データを復号化できるのはプロセスのみです
  • CRYPTPROTECTMEMORY_CROSS_PROCESS:どのプロセスでもデータをデタイプできます
  • CRYPTPROTECTMEMORY_SAME_LOGON:同じユーザーで同じセッションで実行されているプロセスのみがデータを復号化できます

長所:

  1. キーを持っている必要はありません-Windowsがあなたに代わってそれを行います
  2. きめ細かい制御:プロセスごと/セッションごと/ログインごと/マシンごと
  3. CryptProtectDataはWindows2000以降に存在します
  4. DPAPI Windowsは、あなた、私、およびRandom()が絶対に乱数を返すと信じている人々から書かれた「セキュリティ」関連のコードを使用するよりも安全です:)実際、Microsoftはセキュリティ分野で数十年の経験があり、これまでで最も攻撃されたOSを持っています。 o)

短所:

  1. CRYPTPROTECTMEMORY_SAME_PROCESSの場合、One *はプロセスに新しいスレッドを挿入するだけで、このスレッドはデータを復号化できます
  2. 誰かがユーザーのパスワードをリセットした場合(変更しない)、データを復号化できなくなります
  3. CRYPTPROTECTMEMORY_SAME_LOGONの場合:ユーザー*がハッキングされたプロセスを実行すると、データを復号化できます
  4. CRYPTPROTECT_LOCAL_MACHINEを使用する場合、そのマシンのすべてのユーザー*がデータを復号化できます。これが、パスワードを.RDPファイルに保存することが推奨されない理由です。
  5. 既知の問題点

注:「すべてのユーザー」とは、DPAPIを使用するためのツールまたはスキルを持っているユーザーです。

とにかく-あなたには選択肢があります。

@ David-Heffernanが正しいことに注意してください-コンピュータに保存されているものはすべて復号化できます-メモリからそれを読み取り、プロセスにスレッドを挿入するなど。

一方で…クラッカーの生活をもっと難しくしてみませんか?:)

経験則:機密データを含むすべてのバッファーを使用後にクリアします。これは物事を非常に安全にするわけではありませんが、メモリに機密データが含まれる可能性を減らします。もちろん、これは他の主要な問題を解決しません:他のDelphiコンポーネントがあなたがそれらに渡す機密データをどのように処理するか:)

JEDIによるセキュリティライブラリには、DPAPIに対するオブジェクト指向のアプローチがあります。また、JEDIプロジェクトには、DPAPI(JWA IIRC)用に翻訳されたWindowsヘッダーが含まれています。

更新: DPAPIを使用するサンプルコード(JEDI APIを使用)は次のとおりです。

Uses SysUtils, jwaWinCrypt, jwaWinBase, jwaWinType;

function dpApiProtectData(var fpDataIn: tBytes): tBytes;
var
  dataIn,               // Input buffer (clear-text/data)
  dataOut: DATA_BLOB;   // Output buffer (encrypted)
begin
  // Initializing variables
  dataOut.cbData := 0;
  dataOut.pbData := nil;

  dataIn.cbData := length(fpDataIn); // How much data (in bytes) we want to encrypt
  dataIn.pbData := @fpDataIn[0];     // Pointer to the data itself - the address of the first element of the input byte array

  if not CryptProtectData(@dataIn, nil, nil, nil, nil, 0, @dataOut) then
    RaiseLastOSError; // Bad things happen sometimes

  // Copy the encrypted bytes to RESULT variable
  setLength(result, dataOut.cbData);
  move(dataOut.pbData^, result[0], dataOut.cbData);
  LocalFree(HLOCAL(dataOut.pbData));                  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261(v=vs.85).aspx
//  fillChar(fpDataIn[0], length(fpDataIn), #0);  // Eventually erase input buffer i.e. not to leave sensitive data in memory
end;

function dpApiUnprotectData(fpDataIn: tBytes): tBytes;
var
  dataIn,               // Input buffer (clear-text/data)
  dataOut: DATA_BLOB;   // Output buffer (encrypted)
begin
  dataOut.cbData := 0;
  dataOut.pbData := nil;

  dataIn.cbData := length(fpDataIn);
  dataIn.pbData := @fpDataIn[0];

  if not CryptUnprotectData(
    @dataIn,  
    nil, 
    nil, 
    nil, 
    nil, 
    0,         // Possible flags: http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261%28v=vs.85%29.aspx 
               // 0 (zero) means only the user that encrypted the data will be able to decrypt it
    @dataOut
  ) then
    RaiseLastOSError;

  setLength(result, dataOut.cbData);                  // Copy decrypted bytes in the RESULT variable
  move(dataOut.pbData^, result[0], dataOut.cbData);   
  LocalFree(HLOCAL(dataOut.pbData));                  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882%28v=vs.85%29.aspx
end;

procedure testDpApi;
var
  bytesClearTextIn,       // Holds input bytes
  bytesClearTextOut,      // Holds output bytes
  bytesEncrypted: tBytes; // Holds the resulting encrypted bytes
  strIn, strOut: string;  // Input / Output strings
begin

  // *** ENCRYPT STRING TO BYTE ARRAY
  strIn := 'Some Secret Data Here';

  // Copy string contents to bytesClearTextIn
  // NB: this works for STRING type only!!! (AnsiString / UnicodeString)
  setLength(bytesClearTextIn, length(strIn) * sizeOf(char));
  move(strIn[1], bytesClearTextIn[0], length(strIn) * sizeOf(char));

  bytesEncrypted := dpApiProtectData(bytesClearTextIn);     // Encrypt data

  // *** DECRYPT BYTE ARRAY TO STRING
  bytesClearTextOut := dpApiUnprotectData(bytesEncrypted);  // Decrypt data

  // Copy decrypted bytes (bytesClearTextOut) to the output string variable
  // NB: this works for STRING type only!!! (AnsiString / UnicodeString)    
  setLength(strOut, length(bytesClearTextOut) div sizeOf(char));
  move(bytesClearTextOut[0], strOut[1], length(bytesClearTextOut));

  assert(strOut = strIn, 'Boom!');  // Boom should never booom :)

end;

ノート:

  • この例は、CryptProtectData/CryptUnprotectDataを使用した軽量バージョンです。
  • 暗号化はバイト指向であるため、tBytes(tBytes =バイトの配列)を使用する方が簡単です。
  • 入力文字列と出力文字列がUTF8Stringの場合、UTF8Stringの文字は1バイトのみであるため、「* sizeOf(char)」を削除します。
  • CryptProtectMemory / CryptUnProtectMemoryの使用法は似ています
于 2012-10-30T19:14:29.903 に答える
10

問題が単にユーザーが毎回パスワードを入力する必要がないようにすることである場合は、Windowsにすでにパスワードストレージシステムがあることを知っておく必要があります。

[コントロールパネル] ->[資格情報マネージャー]に移動した場合。そこから、 Windowsクレデンシャル->汎用クレデンシャルを探しています。

そこから、リモートデスクトップのパスワードなどが保存されている場所と同じ場所であることがわかります。

ここに画像の説明を入力してください

この機能を公開するAPIは、、、CredReadおよびCredWriteですCredDelete

これらを3つの機能にまとめました。

function CredReadGenericCredentials(const Target: UnicodeString; var Username, Password: UnicodeString): Boolean;
function CredWriteGenericCredentials(const Target, Username, Password: UnicodeString): Boolean;
function CredDeleteGenericCredentials(const Target: UnicodeString): Boolean;

ターゲットは、credentailsを識別することです。私は通常、アプリケーション名を使用します。

String target = ExtractFilename(ParamStr(0)); //e.g. 'Contoso.exe'

だからそれは単純です:

CredWriteGenericCredentials(ExtractFilename(ParamStr(0)), username, password);

その後、資格情報マネージャーでそれらを確認できます。

ここに画像の説明を入力してください

それらを読み返したいとき:

CredReadGenericCredentials(ExtractFilename(ParamStr(0)), {var}username, {var}password);

あなたがしなければならないUI作業の余分な部分があります:

  • 保存されたクレデンシャルがないことを検出し、ユーザーにクレデンシャルの入力を求める
  • 保存したユーザー名/パスワードが機能しなかったことを検出し、新しい/正しいクレデンシャルの入力を求め、接続してみて、新しい正しいクレデンシャルを保存します

保存された資格情報の読み取り:

function CredReadGenericCredentials(const Target: UnicodeString; var Username, Password: UnicodeString): Boolean;
var
    credential: PCREDENTIALW;
    le: DWORD;
    s: string;
begin
    Result := False;

    credential := nil;
    if not CredReadW(Target, CRED_TYPE_GENERIC, 0, {var}credential) then
    begin
        le := GetLastError;
        s := 'Could not get "'+Target+'" generic credentials: '+SysErrorMessage(le)+' '+IntToStr(le);
        OutputDebugString(PChar(s));
        Exit;
    end;

    try
        username := Credential.UserName;
        password := WideCharToWideString(PWideChar(Credential.CredentialBlob), Credential.CredentialBlobSize div 2); //By convention blobs that contain strings do not have a trailing NULL.
    finally
        CredFree(Credential);
    end;

    Result := True;
end;

保存されたクレデンシャルの書き込み:

function CredWriteGenericCredentials(const Target, Username, Password: UnicodeString): Boolean;
var
    persistType: DWORD;
    Credentials: CREDENTIALW;
    le: DWORD;
    s: string;
begin
    ZeroMemory(@Credentials, SizeOf(Credentials));
    Credentials.TargetName := PWideChar(Target); //cannot be longer than CRED_MAX_GENERIC_TARGET_NAME_LENGTH (32767) characters. Recommended format "Company_Target"
    Credentials.Type_ := CRED_TYPE_GENERIC;
    Credentials.UserName := PWideChar(Username);
    Credentials.Persist := CRED_PERSIST_LOCAL_MACHINE;
    Credentials.CredentialBlob := PByte(Password);
    Credentials.CredentialBlobSize := 2*(Length(Password)); //By convention no trailing null. Cannot be longer than CRED_MAX_CREDENTIAL_BLOB_SIZE (512) bytes
    Credentials.UserName := PWideChar(Username);
    Result := CredWriteW(Credentials, 0);
    end;
end;

そして、削除します:

function CredDeleteGenericCredentials(const Target: UnicodeString): Boolean;
begin
    Result := CredDelete(Target, CRED_TYPE_GENERIC);
end;

CredReadは、CryptProtectDataのラッパーです。

CredWrite/CredReadは内部的にを使用することに注意してくださいCryptProtectData

  • また、クレデンシャルをどこかに保存することも選択します
  • また、ユーザーが保存されたクレデンシャルを表示、管理、さらには手動で入力および変更するためのUIを提供します

自分自身を使用することによる違いCryptProtectDataは、ブロブしか与えられないということです。それをどこかに保存し、後で取得するのはあなた次第です。

パスワードを保存するときのラッパーは次のとおりCryptProtectDataです。CryptUnprotectData

function EncryptString(const Plaintext: UnicodeString; const AdditionalEntropy: UnicodeString): TBytes;
function DecryptString(const Blob: TBytes; const AdditionalEntropy: UnicodeString): UnicodeString;

これは使いやすいです:

procedure TForm1.TestStringEncryption;
var
    encryptedBlob: TBytes;
    plainText: UnicodeString;
const
    Salt = 'Salt doesn''t have to be secret; just different from the next application';
begin
    encryptedBlob := EncryptString('correct battery horse staple', Salt);

    plainText := DecryptString(encryptedBlob, salt);

    if plainText <> 'correct battery horse staple' then
        raise Exception.Create('String encryption self-test failed');
end;

実際の内臓は次のとおりです。

type
    DATA_BLOB = record
            cbData: DWORD;
            pbData: PByte;
    end;
    PDATA_BLOB = ^DATA_BLOB;

const
    CRYPTPROTECT_UI_FORBIDDEN = $1;

function CryptProtectData(const DataIn: DATA_BLOB; szDataDescr: PWideChar; OptionalEntropy: PDATA_BLOB; Reserved: Pointer; PromptStruct: Pointer{PCRYPTPROTECT_PROMPTSTRUCT}; dwFlags: DWORD; var DataOut: DATA_BLOB): BOOL; stdcall; external 'Crypt32.dll' name 'CryptProtectData';
function CryptUnprotectData(const DataIn: DATA_BLOB; szDataDescr: PPWideChar; OptionalEntropy: PDATA_BLOB; Reserved: Pointer; PromptStruct: Pointer{PCRYPTPROTECT_PROMPTSTRUCT}; dwFlags: DWORD; var DataOut: DATA_BLOB): Bool; stdcall; external 'Crypt32.dll' name 'CryptUnprotectData';

function EncryptString(const Plaintext: UnicodeString; const AdditionalEntropy: UnicodeString): TBytes;
var
    blobIn: DATA_BLOB;
    blobOut: DATA_BLOB;
    entropyBlob: DATA_BLOB;
    pEntropy: Pointer;
    bRes: Boolean;
begin
    blobIn.pbData := Pointer(PlainText);
    blobIn.cbData := Length(PlainText)*SizeOf(WideChar);

    if AdditionalEntropy <> '' then
    begin
        entropyBlob.pbData := Pointer(AdditionalEntropy);
        entropyBlob.cbData := Length(AdditionalEntropy)*SizeOf(WideChar);
        pEntropy := @entropyBlob;
    end
    else
        pEntropy := nil;

    bRes := CryptProtectData(
            blobIn,
            nil, //data description (PWideChar)
            pentropy, //optional entropy (PDATA_BLOB)
            nil, //reserved
            nil, //prompt struct
            CRYPTPROTECT_UI_FORBIDDEN, //flags
            {var}blobOut);
    if not bRes then
        RaiseLastOSError;

    //Move output blob into resulting TBytes
    SetLength(Result, blobOut.cbData);
    Move(blobOut.pbData^, Result[0], blobOut.cbData);

    // When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function
    LocalFree(HLOCAL(blobOut.pbData));
end;

そして復号化:

function DecryptString(const blob: TBytes; const AdditionalEntropy: UnicodeString): UnicodeString;
var
    dataIn: DATA_BLOB;
    entropyBlob: DATA_BLOB;
    pentropy: PDATA_BLOB;
    dataOut: DATA_BLOB;
    bRes: BOOL;
begin
    dataIn.pbData := Pointer(blob);
    dataIn.cbData := Length(blob);

    if AdditionalEntropy <> '' then
    begin
        entropyBlob.pbData := Pointer(AdditionalEntropy);
        entropyBlob.cbData := Length(AdditionalEntropy)*SizeOf(WideChar);
        pentropy := @entropyBlob;
    end
    else
        pentropy := nil;

    bRes := CryptUnprotectData(
            DataIn,
            nil, //data description (PWideChar)
            pentropy, //optional entropy (PDATA_BLOB)
            nil, //reserved
            nil, //prompt struct
            CRYPTPROTECT_UI_FORBIDDEN,
            {var}dataOut);
    if not bRes then
        RaiseLastOSError;

    SetLength(Result, dataOut.cbData div 2);
    Move(dataOut.pbData^, Result[1], dataOut.cbData);
    LocalFree(HLOCAL(DataOut.pbData));
end;
于 2016-10-02T18:24:45.157 に答える
0

わかりました。これがTurboPowerLockbox(バージョン2) を使用した例です。

  uses LbCipher, LbString;

  TaAES = class
  private
    Key: TKey256;
    FPassword: string;
  public
    constructor Create;

    function Code(AString: String): String;
    function Decode(AString: String): String;

    property Password: string read FPassword write FPassword;
  end;

function TaAES.Code(AString: String): String;
begin
  try
    RESULT := RDLEncryptStringCBCEx(AString, Key, SizeOf(Key), False);
  except
    RESULT := '';
  end;
end;

constructor TaAES.Create;
begin
  GenerateLMDKey(Key, SizeOf(Key), Password);
end;

function TaAES.Decode(AString: String): String;
begin
  RESULT := RDLEncryptStringCBCEx(AString, Key, SizeOf(Key), True);
end;

パスワードは、アプリケーションで変数として保存できます。ファイルに保存する例はありませんが、TFileStreamencrypted(code)パスワードを保存してから、decodeそれを読み取るために使用できます:-)

于 2012-10-30T18:55:42.580 に答える