关于delphi:我的密码有哪些保护方案?

What protection scheme for my passwords?

我正在用Delphi开发一个软件(供个人使用)。

但我有个问题,就是这样:

->有一个主密码可以访问其他密码文件。

->在存储这些密码时,我使用主密码作为密钥。我想,没关系。->但是如何保护主密码,并允许修改它呢????

如果我使用一个常量键(以二进制形式存储在代码中),它就可以被反汇编!

所以,我疯了,或者有一种方法可以做到这一点:保护主密码和派生密码。

(主密码(按用户选择)->加密用户数据时将其用作密钥(与其他密码和用户名相关)。

谢谢你的帮助。请原谅我的英语不好。


我建议把这个问题彻底解决。您的Windows帐户已被密码保护。win32 api提供了一种机制,您可以使用Windows密码让Windows加密数据。

这意味着你的数据和你的Windows密码一样安全;你不需要记住第二个密码。

windows函数CredWriteCredRead允许存储和保存凭证;其中,我正好有一个方便的包装函数来存储凭证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function CredWriteGenericCredentials(const Target, Username, Password: WideString): Boolean;
var
    PersistType: DWORD;
    Credentials: CREDENTIALW;
    hr: DWORD;
    s: string;
begin
    if not CredGetMaxPersistType(CRED_TYPE_GENERIC, {var}PersistType) then
    begin
        Result := False;
        Exit;
    end;

    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 := PersistType; //CRED_PERSIST_ENTERPRISE; //local machine and roaming
    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);

    if not Result then
    begin
        hr := GetLastError;
        case hr of
        CredUI.ERROR_NO_SUCH_LOGON_SESSION: s := 'The logon session does not exist or there is no credential set associated with this logon session. Network logon sessions do not have an associated credential set. (ERROR_NO_SUCH_LOGON_SESSION)';
        CredUI.ERROR_INVALID_PARAMETER: s := 'Certain fields cannot be changed in an existing credential. This error is returned if a field does not match the value in a protected field of the existing credential. (ERROR_INVALID_PARAMETER)';
        CredUI.ERROR_INVALID_FLAGS: s := 'A value that is not valid was specified for the Flags parameter. (ERROR_INVALID_FLAGS)';
        ERROR_BAD_USERNAME: s := 'The UserName member of the passed in Credential structure is not valid. For a description of valid user name syntax, see the definition of that member. (ERROR_BAD_USERNAME)';
        ERROR_NOT_FOUND: s := 'CRED_PRESERVE_CREDENTIAL_BLOB was specified and there is no existing credential by the same TargetName and Type. (ERROR_NOT_FOUND)';
//      SCARD_E_NO_READERS_AVAILABLE: raise Exception.Create('The CRED_TYPE_CERTIFICATE credential being written requires the smart card reader to be available. (SCARD_E_NO_READERS_AVAILABLE)');
//      SCARD_E_NO_SMARTCARD: raise Exception.Create('A CRED_TYPE_CERTIFICATE credential being written requires the smart card to be inserted. (SCARD_E_NO_SMARTCARD)');
//      SCARD_W_REMOVED_CARD: raise Exception.Create('A CRED_TYPE_CERTIFICATE credential being written requires the smart card to be inserted. (SCARD_W_REMOVED_CARD)');
//      SCARD_W_WRONG_CHV: raise Exception.Create('The wrong PIN was supplied for the CRED_TYPE_CERTIFICATE credential being written. (SCARD_W_WRONG_CHV)');
        else
            s := SysErrorMessage(hr)+' (0x'+IntToHex(hr, 8)+')';
        end;
        OutputDebugString(PChar(s));
    end;
end;

以及用于读取凭证的包装函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function CredReadGenericCredentials(const Target: WideString; var Username, Password: WideString): Boolean;
var
    Credential: PCREDENTIALW;
begin
    Credential := nil;
    if CredReadW(Target, CRED_TYPE_GENERIC, 0, Credential) then
    begin
        try
            username := Credential.UserName;
            password := WideCharToWideString(PWideChar(Credential.CredentialBlob), Credential.CredentialBlobSize); //By convention blobs that contain strings do not have a trailing NULL.
        finally
            CredFree(Credential);
        end;

        Result := True;
    end
    else
        Result := False;
end;

需要注意的是,CredReadCredWrite本身就是一种功能,可以反过来使用CryptProtectDataCryptUnprotectData

这些函数允许您获取一些任意的blob,并使用用户帐户的密码1对其进行加密,然后将加密的blob返回给您。然后,您可以将该blob存储在任何您喜欢的地方(例如注册表或文件)。

稍后,您可以对blob进行解密,并且只能由最初对其进行加密的用户进行解密。

这让你有了强迫你处理另一个密码的梦想,但是使用Windows来保护它。

1
"MyPassword04" --> CryptProtectData() -->"TXlQYXNzd29yZDA0"

你可以在任何你喜欢的地方存储你的加密密码。稍后:

1
"TXlQYXNzd29yZDA0" --> CryptUnprotectData() -->"MyPassword04"

我的建议是能够放弃密码;利用您自己帐户的安全性。

只是一个建议;你可以自由地考虑和拒绝它。

更新

其他助手函数。

PWideChar转换为WideString(如果有内置(delphi 5)函数,我从来没有找到它):

1
2
3
4
5
6
7
8
9
10
11
function WideCharToWideString(Source: PWideChar; SourceLen: Integer): WideString;
begin
    if (SourceLen <= 0) then
    begin
        Result := '';
        Exit;
    end;

    SetLength(Result, SourceLen div 2);
    Move(Source^, Result[1], SourceLen);
end;

您可以将不同的"范围"存储在以下位置:

  • CRED_PERSIST_NONE:不能存储凭证。如果凭证类型不受支持或已被策略禁用,则返回此值。
  • CRED_PERSIST_SESSION:只能存储会话特定的凭证。
  • CRED_PERSIST_LOCAL_MACHINE:可以存储特定于会话和特定于计算机的凭证。Windows XP:无法为未加载配置文件的会话存储此凭据。
  • CRED_PERSIST_ENTERPRISE:可以存储任何凭证。Windows XP:无法为未加载配置文件的会话存储此凭据。

此函数返回给定凭证类型(例如"generic"credentives)支持的最高持久性类型。当您调用CredWrite时,需要不要尝试将其保存在不受支持的位置(即在没有域的域中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type
    TCredGetSessionTypes = function(MaximumPersistCount: DWORD; MaximumPersist: LPDWORD): BOOL; stdcall;
function CredGetMaxPersistType(CredType: DWORD; var MaxCredPersistType: DWORD): Boolean;
const
    CRED_TYPE_MAXIMUM = 5;
var
    _CredGetSessionTypes: TCredGetSessionTypes;
    MaximumPersist: array[0..CRED_TYPE_MAXIMUM-1] of DWORD;
begin
    _CredGetSessionTypes := GetProcedureAddress(advapi32, 'CredGetSessionTypes');

    if Assigned(_CredGetSessionTypes) then
    begin
        Result := _CredGetSessionTypes(CRED_TYPE_MAXIMUM, PDWORD(@MaximumPersist[0]));
        if Result then
            MaxCredPersistType := MaximumPersist[CredType]
        else
            MaxCredPersistType := 0;
    end
    else
    begin
        SetLastError(ERROR_INVALID_FUNCTION);
        Result := False;
        MaxCredPersistType := 0;
    end;
end;

Note: Any code is released into the public domain. No attribution required.


用主密码对密码文件进行编码。不要将密码存储在任何地方;只需在解密密码文件之前查询它。如果有人输入了错误的密码,密码文件将被置乱。


您可能对我们新推出的smartutils密码sdk感兴趣:http://sutils.com/index.php/smartutils-password-sdk。它允许在aes-256加密数据库文件中存储包含URL、用户名等相关信息的密码。主密码可以在一行代码中使用dpapi加密。


查看密码安全http://password safe.sourceforge.net/如何解决问题。

  • 整理要加密的密码列表。
  • 使用安全随机生成器生成两个随机128位数字。使用第一个作为HMAC密钥,以便以后进行身份验证和完整性检查。使用第二个作为aes-cbc密钥加密要加密的密码列表。将HMAC输出附加到加密列表的末尾。
  • 生成第三个随机数。将其与密码一起用作salt,以便使用pbkdf派生密钥加密密钥。使用密钥加密密钥对步骤2中的两个随机密钥进行加密。
  • 或者,通过将密码散列足够多次来生成密码验证器。
  • 最终文件应具有以下布局,省略格式[salt][password verifier][encrypted encryption key][encrypted hmac key][encrypted password list][hmac value]


    您可能要做的是对所有密码使用单向哈希,而根本不需要主密码。

    散列的好处在于,每个人都可以阅读它,但它们并不是最聪明的,因为破解散列密码的唯一方法是暴力攻击。散列是"大"的,这会花费更多的时间。

    当然,如果存储的密码很容易被字典攻击发现,这就不成立了,但是您的主密码是安全的吗?