djiga4me
djiga4me

Reputation: 345

What protection scheme for my passwords?

I'm developing a software (for personal use) with Delphi.

But I have a problem, this is it :

-> There's a main password to access a some other password file.

-> When storing these password, i use as key the main password. I think, it's ok. -> But how protect the main password, and allow modification of it ???

If I use a constant key (so stored in the code, in binary), It can be disassembled !

So, I'm crazy or there's a way to make this possible : Protect main password and derived passwords.

(Main password (choosen by user) -> use it as key when encrypting user data (other password and usernames related).

Thank you for your helps. Excuse my bad english.

Upvotes: 3

Views: 2189

Answers (5)

Ian Boyd
Ian Boyd

Reputation: 256761

i'd like to suggest turning the problem on its head. Your Windows account is already protected with a password. The Win32 API provides a mechanism where you can have Windows encrypt data with your Windows password.

This means that your data is as secure as your Windows password; and you don't need to memorize a second password.

The Windows function CredWrite and CredRead allow storing and saving of credentials; of which i just happen to have a handy wrapper function already for storing credentials:

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;

And a wrapper function to read credentials:

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;

It should be noted that CredRead and CredWrite are themselves functions that turn around and use CryptProtectData and CryptUnprotectData.

These functions let you take some arbitrary blob, and encrypt it with the user account's password1, and then hand you back the encrypted blob. You can then store that blob wherever you like (e.g. registry or file).

Later you can have the blob decrypted, and can only be decrypted by the user who originally encrypted it.

This lets you have your dream of forcing you to deal with another password, but uses Windows to protect it.

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

You can store your encrypted password anywhere you like. Then later:

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

The suggestion i'm making is the ability to abandon passwords; leveraging the security of your own account.

Just a suggestion; you're free to consider, and reject, it.


Update

Additional helper functions.

Convert a PWideChar to a WideString (if there's a built-in (Delphi 5) function for it, i've never found it):

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;

There are different "scopes" that you'r allowed to store credentails in:

  • CRED_PERSIST_NONE: No credential can be stored. This value will be returned if the credential type is not supported or has been disabled by policy.
  • CRED_PERSIST_SESSION: Only a session-specific credential can be stored.
  • CRED_PERSIST_LOCAL_MACHINE: Session-specific and computer-specific credentials can be stored. Windows XP: This credential cannot be stored for sessions in which the profile is not loaded.
  • CRED_PERSIST_ENTERPRISE: Any credential can be stored. Windows XP: This credential cannot be stored for sessions in which the profile is not loaded.

This function returns the highest supported persistence type for a given credential type (e.g. "generic" credentails). It's needed when you call CredWrite that you don't try to persist it in a location that isn't supported (i.e. in the domain when there is no domain):

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.

Upvotes: 18

Oleg Denisishin
Oleg Denisishin

Reputation: 1

You may be interested in our new SmartUtils Password SDK: http://sutils.com/index.php/smartutils-password-sdk It allows to store passwords with related info like URLs, usernames etc. in AES-256 encrypted database file. A master password may be encrypted using DPAPI in one line of code.

Upvotes: 0

Henrick Hellstr&#246;m
Henrick Hellstr&#246;m

Reputation: 2666

Check out how password safe http://passwordsafe.sourceforge.net/ solves the problem.

  1. Assemble the list-of-passwords-to-be-encrypted.
  2. Generate two random 128 bit numbers using a secure random generator. Use the first as HMAC key for later authentication and integrity checks. Use the second as AES-CBC key for encrypting the list-of-passwords-to-be-encrypted. Append the HMAC output to the end of the encrypted list.
  3. Generate a third random number. Use this as salt together with the password for deriving a key encryption key using a PBKDF. Use the key encryption key for encrypting the two random keys in step 2.
  4. Optionally, generate a password verifier by hashing your password a sufficiently large number of times.

The final file should have the following layout, formatting omitted [salt][password verifier][encrypted encryption key][encrypted hmac key][encrypted password list][hmac value]

Upvotes: 0

fge
fge

Reputation: 121740

What you could probably do is use a one-way hash for all passwords, without the need for a master password at all.

The nice thing with a hash is that it may be readable by everyone, they aren't any the smarter, since the only way to break a hashed password is a brute-force attack. Which is all the more time consuming that the hash is "large".

Of course, this won't hold if passwords stored are easily discoverable by a dictionary attack, but then is your master password secure?

Upvotes: 0

Nicolas78
Nicolas78

Reputation: 5144

Encode your password file with the main password. Don't store that password anywhere; simply query it before decrypting the password file. If someone enters a wrong password, the password file will be scrambled.

Upvotes: 2

Related Questions