Wie man den Benutzer nach einem Passwort fragt


Abstract

Inhalt dieses Artikels ist es, wie man einem Benutzer unter Windows sicher nach seinem Passwort fragen kann.

Einleitung

Grundsätzlich sollte man einen Benutzer so selten wie möglich nach einem Passwort frage. Allerdings nicht, um dem Benutzer Arbeit und Mühe abzunehmen, sondern einfach aus dem Grund ihn nicht daran zu gewöhnen, ständig und überall sein Passwort einzugeben. Denn je mehr dies zur Routine wird, desto höher ist die Wahrscheinlichkeit, dass man mal sein Passwort wo eingibt, wo man es besser hätte nicht eingegeben.

Windows selbst nimmt dies sehr ernst, was die Passwortabfrage angeht. Nimmt man zum Beispiel den Login-Prompt bei Windows. Diesen kann man so konfigurieren, dass man erst Strg+Alt+Entf drücken muss, bevor das Betriebssystem den Dialog zur Eingabe der Logindaten anzeigt. Warum ist dies so? Die Tastenkombination Strg+Alt+Entf kann nicht von Programmen im User-Mode, sondern nur von Programmen im Kernel-Mode, also von Code der Teil des Betriebssystems ist, abgefangen werden. So stellt man also sicher, dass es sich wirklich um das Betriebssystem handelt, welches nach den Logindaten fragt.

Wenn man es nicht vermeiden kann, den Benutzer nach einem Passwort zu frage, dann sollte man einige Grundsätze beherzigen: Zum einem sollte man die Eingabe verstecken. Also entweder hinter Sternchen und ähnlichem oder, in der Konsole, gar nicht erst anzeigen lassen, damit eine Person, die mit auf den Monitor guckt, das Passwort nicht sehen kann. Und man sollte es im Programm verhindern, das alte Passwort, um es dem Benutzer so bequem wie möglich zu machen, in das Eingabefeld für das Passwort zu kopieren, da es Programme gibt, die verdeckte Passwörter in Passworteingabefeldern sichtbar machen.

Ein Passwort in der Konsole abfragen

Hat das Programm keine Benutzeroberfläche, sondern handelt es sich um ein Konsolenprogramm, kann man keinen Dialog anzeigen mit einem Eingabefeld für Passwörter. Da man aber trotzdem irgendwie sicher das Passwort abfragen will, muss eine andere Lösung her.

Relativ einfach ist es, die Ausgabe der Tastatureingaben auf den Standardausgabegerät einfach zu deaktivieren. Die Eingaben, die der Benutzer dann über die Tastatur macht, erscheinen dann nicht auf den Bildschirm.

uses
  Windows, SysUtils;

var
  s: String;
  Console: THandle;
  OldMode: DWORD;
  NewMode: DWORD;

begin
  Console := GetStdHandle(STD_INPUT_HANDLE);
  // Konsolenmodus sichern
  GetConsoleMode(Console, OldMode);
  // neuen Modus setzen
  NewMode := OldMode and not ENABLE_ECHO_INPUT;
  SetConsoleMode(Console, NewMode);

  Write('Passwort: ');
  Readln(s);

  // alten Konsolenmodus wiederherstellen
  SetConsoleMode(Console, OldMode);

  Writeln('');
  Writeln('Dein Passwort: ' + s);

  Write('Benutzername: ');
  Readln(s);
  Writeln('Dein Benutzername: ' + s);

  Readln;

Den Windows Passwort-Dialog anzeigen

Hat das Programm eine grafische Benutzeroberfläche kann man zum Beispiel den von Windows bereitgestellten Dialog dafür nutzen:

UIPWPrompt.jpg

Dieser Dialog steht allerdings erst ab Windows XP bzw. Windows 2003 Server zur Verfügung.

Der Einfachheit halber habe ich im folgendem Delphi Quelltext die benötigte Funktion statisch eingebunden. Ist man sich nicht sicher, auf welcher Betriebssystemversion das Programm laufen soll, ist es natürlich immer angebracht, die Funktion aus der DLL dynamisch zu importieren, um gegebenenfalls entsprechend reagieren zu können, wenn der Dialog nicht zur Verfügung steht.

const
  CRED_MAX_USERNAME_LENGTH = (256 + 1 + 256);
  CRED_MAX_CREDENTIAL_BLOB_SIZE = 512;

  CREDUI_MAX_USERNAME_LENGTH = CRED_MAX_USERNAME_LENGTH;
  CREDUI_MAX_PASSWORD_LENGTH = (CRED_MAX_CREDENTIAL_BLOB_SIZE div 2);

  CREDUI_FLAGS_INCORRECT_PASSWORD = $00001; // indicates the username is valid, but password is not
  CREDUI_FLAGS_DO_NOT_PERSIST = $00002; // Do not show "Save" checkbox, and do not persist credentials
  CREDUI_FLAGS_REQUEST_ADMINISTRATOR = $00004; // Populate list box with admin accounts
  CREDUI_FLAGS_EXCLUDE_CERTIFICATES = $00008; // do not include certificates in the drop list
  CREDUI_FLAGS_REQUIRE_CERTIFICATE = $00010;
  CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX = $00040;
  CREDUI_FLAGS_ALWAYS_SHOW_UI = $00080;
  CREDUI_FLAGS_REQUIRE_SMARTCARD = $00100;
  CREDUI_FLAGS_PASSWORD_ONLY_OK = $00200;
  CREDUI_FLAGS_VALIDATE_USERNAME = $00400;
  CREDUI_FLAGS_COMPLETE_USERNAME = $00800; //
  CREDUI_FLAGS_PERSIST = $01000; // Do not show "Save" checkbox, but persist credentials anyway
  CREDUI_FLAGS_SERVER_CREDENTIAL = $04000;
  CREDUI_FLAGS_EXPECT_CONFIRMATION = $20000;
      // do not persist unless caller later confirms credential via CredUIConfirmCredential() api
  CREDUI_FLAGS_GENERIC_CREDENTIALS = $40000; // Credential is a generic credential
  CREDUI_FLAGS_USERNAME_TARGET_CREDENTIALS = $80000; // Credential has a username as the target
  CREDUI_FLAGS_KEEP_USERNAME = $100000; // don't allow the user to change the supplied username

type
  TCREDUI_INFO = record
    cbSize: DWORD;
    hWnd: THandle;
    MessageText: PWideChar;
    CaptionText: PWideChar;
    Bitmap: HBITMAP;
  end;
  PCREDUI_INFO = ^TCREDUI_INFO;

function CredUIPromptForCredentialsW(UIINFO: PCREDUI_INFO; TargetName: PWideChar; Reserved: THandle; AuthError: DWORD;
  UserName: PWideChar; UserNameMaxChars: ULONG; Password: PWideChar; PasswordMaxChars: ULONG; var Save: BOOL;
  Flags: DWORD): DWORD; stdcall; external 'Credui.dll' name 'CredUIPromptForCredentialsW';

function PromptPWDialog(Parent: THandle; const Server: WideString; Flags: Integer; var User, PW: PWideChar): DWORD;
var
  UIInfo            : TCREDUI_INFO;
  Save              : BOOL;
begin
  UIInfo.cbSize := sizeof(TCREDUI_INFO);
  UIInfo.hWnd := Parent;
  UIInfo.MessageText := 'Bitte gebn sie Ihre Logindaten ein:';
  UIInfo.CaptionText := 'Login';
  UIInfo.Bitmap := 0;

  Result := CredUIPromptForCredentialsW(@UIInfo, PWideChar(Server), 0, 0, PWideChar(User),
    CREDUI_MAX_USERNAME_LENGTH * 2, PWideChar(PW), CREDUI_MAX_PASSWORD_LENGTH * 2, Save, Flags);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  User, PW          : PWideChar;
  ret               : DWORD;
begin
  GetMem(User, CREDUI_MAX_USERNAME_LENGTH * 2);
  GetMem(PW, CREDUI_MAX_PASSWORD_LENGTH * 2);
  try
    ZeroMemory(User, CREDUI_MAX_USERNAME_LENGTH * 2);
    Zeromemory(PW, CREDUI_MAX_PASSWORD_LENGTH * 2);
    ret := PromptPWDialog(Handle, '', CREDUI_FLAGS_ALWAYS_SHOW_UI or CREDUI_FLAGS_GENERIC_CREDENTIALS or
      CREDUI_FLAGS_DO_NOT_PERSIST, User, PW);
    if ret = 0 then
      ShowMessage(Format('Benutzer: %s' + #13#10 + 'Passwort: %s', [User, PW]))
    else
    begin
      case ret of
        ERROR_CANCELLED: ShowMessage('ERROR_CANCELLED');
        ERROR_INVALID_FLAGS: ShowMessage('ERROR_INVALID_FLAGS');
        ERROR_INVALID_PARAMETER: ShowMessage('ERROR_INVALID_PARAMETER');
        ERROR_NO_SUCH_LOGON_SESSION: ShowMessage('ERROR_NO_SUCH_LOGON_SESSION');
        NO_ERROR: ShowMessage('NO_ERROR');
      end;
    end;
  finally
    FreeMem(User);
    FreeMem(PW);
  end;
end;

Der Quellcode sollte eigentlich selbst erklärend sein. Für weitere Informationen sollte man unter dem entsprechenden Stichpunkt im MSDN oder PSDK nachschlagen: CredUIPromptForCredentials [1].

Downloads


PromptForCredentials.zip Wednesday, 29-Dec-2010 23:45:48 CET 4.1K

Links

[1] http://msdn2.microsoft.com/en-us/library/aa375177.aspx
[2] http://www.michael-puff.de/Artikel/2007/files/PromptForCredentials.zip


2010-12-29T23:44:53 +0100, mail+homepage[at]michael-puff.de