Reputation: 345
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
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
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
Reputation: 2666
Check out how password safe http://passwordsafe.sourceforge.net/ solves the problem.
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
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
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