Какая схема защиты для моих паролей? - PullRequest
3 голосов
/ 24 декабря 2011

Я разрабатываю программное обеспечение (для личного использования) с Delphi.

Но у меня есть проблема, вот она:

-> Есть главный пароль для доступа к некоторому другомуфайл паролей.

-> При сохранении этих паролей я использую в качестве ключа основной пароль.Я думаю, все в порядке.-> Но как защитить основной пароль и разрешить его изменение ???

Если я использую постоянный ключ (хранящийся в коде, в двоичном виде), его можно разобрать!

Итак, я сумасшедший или есть способ сделать это возможным: защитить основной пароль и производные пароли.

(Основной пароль (выбирается пользователем) -> использовать его в качестве ключа при шифровании пользовательских данных (другой пароль)и связанные имена пользователей).

Спасибо за вашу помощь. Извините, мой плохой английский.

Ответы [ 5 ]

15 голосов
/ 24 декабря 2011

Я хотел бы предложить перевернуть проблему с ног на голову.Ваша учетная запись Windows уже защищена паролем.Win32 API предоставляет механизм, с помощью которого вы можете зашифровать данные Windows с помощью вашего пароля Windows.

Это означает, что ваши данные защищены так же, как ваш пароль Windows;и вам не нужно запоминать второй пароль.

Функция Windows CredWrite и CredRead позволяет хранить и сохранять учетные данные;из которых у меня просто есть удобная функция-обертка для хранения учетных данных:

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;

и функция-обертка для чтения учетных данных:

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;

Следует отметить, что CredReadи CredWrite сами являются функциями, которые поворачиваются и используют CryptProtectData и CryptUnprotectData.

Эти функции позволяют вам взять произвольный blob , и зашифруйте его с помощью пароля учетной записи пользователя 1 , а затем верните зашифрованный blob .Затем вы можете хранить этот blob в любом месте (например, в реестре или файле).

Позже вы можете расшифровать BLOB-объект, и расшифровать его может только тот пользователь, который первоначально зашифровал его.

Это позволяет вам мечтать заставить работать с другим паролем, но использует Windows для его защиты.

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

Вы можете хранить зашифрованный пароль где угодно.Позже:

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

Я предлагаю вам отказаться от паролей;используя безопасность своей учетной записи.

Просто предложение;Вы можете рассмотреть и отклонить его.


Обновление

Дополнительные вспомогательные функции.

Преобразование PWideChar вWideString (если для него есть встроенная (Delphi 5) функция, я ее никогда не нашел):

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;

Существуют разные «области», в которых вам разрешено хранить учетные данныеin:

  • CRED_PERSIST_NONE: учетные данные не могут быть сохранены.Это значение будет возвращено, если тип учетных данных не поддерживается или был отключен политикой.
  • CRED_PERSIST_SESSION: можно сохранить только учетные данные, специфичные для сеанса.
  • CRED_PERSIST_LOCAL_MACHINE: сеанс-специфичные и компьютерные учетные данные могут быть сохранены. Windows XP : эти учетные данные нельзя сохранить для сеансов, в которых профиль не загружен.
  • CRED_PERSIST_ENTERPRISE: могут быть сохранены любые учетные данные. Windows XP : эти учетные данные нельзя сохранить для сеансов, в которых профиль не загружен.

Эта функция возвращает максимальный поддерживаемый тип персистентности для данного типа учетных данных (например, «универсальный»)."credentails).Когда вы звоните CredWrite, это необходимо, чтобы вы не пытались сохранить его в местоположении, которое не поддерживается (то есть в домене, когда нет домена):

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;

Примечание : любой код публикуется в открытом доступе.Указание авторства не требуется.

2 голосов
/ 24 декабря 2011

Кодируйте ваш файл пароля с помощью основного пароля. Никогда не храните этот пароль; просто запросите его перед расшифровкой файла паролей. Если кто-то введет неправильный пароль, файл пароля будет зашифрован.

0 голосов
/ 07 апреля 2013

Вас может заинтересовать наш новый SDK для паролей SmartUtils: http://sutils.com/index.php/smartutils-password-sdk Он позволяет хранить пароли с соответствующей информацией, такой как URL-адреса, имена пользователей и т. Д., В зашифрованном файле базы данных AES-256. Главный пароль может быть зашифрован с использованием DPAPI в одну строку кода.

0 голосов
/ 08 февраля 2012

Узнайте, как безопасный пароль http://passwordsafe.sourceforge.net/ решает проблему.

  1. Соберите список паролей для шифрования.
  2. Создайте два случайных128-битные числа с использованием безопасного генератора случайных чисел.Используйте первый как ключ HMAC для последующих проверок подлинности и целостности.Используйте второй в качестве ключа AES-CBC для шифрования списка паролей, которые необходимо зашифровать.Добавьте вывод HMAC в конец зашифрованного списка.
  3. Создайте третье случайное число.Используйте это как соль вместе с паролем для получения ключа шифрования ключа с использованием PBKDF.Используйте ключ шифрования ключа для шифрования двух случайных ключей на шаге 2.
  4. По желанию, сгенерируйте верификатор пароля, хешируя пароль достаточно большое количество раз.

Окончательный файлдолжен иметь следующую разметку, пропущенное форматирование [соль] [верификатор пароля] [зашифрованный ключ шифрования] [зашифрованный ключ hmac] [список зашифрованных паролей] [значение hmac]

0 голосов
/ 24 декабря 2011

Что вы, вероятно, могли бы сделать, это использовать однонаправленный хеш для всех паролей, при этом вообще не требуется мастер-пароль.

Хорошая вещь с хэшем состоит в том, что он может быть прочитан всеми, они не умнее, так как единственный способ взломать хешированный пароль - это атака методом перебора. Что тем более отнимает много времени, что хеш "большой".

Конечно, это не сработает, если сохраненные пароли легко обнаружить с помощью атаки по словарю, но тогда ваш главный пароль безопасен?

...