start page | rating of books | rating of authors | reviews | copyrights

Book HomePHP CookbookSearch this book

20.5. Reading Passwords

20.5.1. Problem

You need to read a string from the command line without it being echoed as it's typed; for example, when entering passwords.

20.5.2. Solution

On Unix systems, use /bin/stty to toggle echoing of typed characters:

// turn off echo
`/bin/stty -echo`;

// read password
$password = readline();

// turn echo back on
`/bin/stty echo`;

On Windows, use w32api_register_function( ) to import _getch( ) from msvcrt.dll:

// load the w32api extension and register _getch()
dl('php_w32api.dll');
w32api_register_function('msvcrt.dll','_getch','int');

while(true) {
    // get a character from the keyboard
    $c = chr(_getch());
    if ( "\r" == $c ||  "\n" == $c ) {
        // if it's a newline, break out of the loop, we've got our password
        break;
    } elseif ("\x08" == $c) {
        /* if it's a backspace, delete the previous char from $password */
        $password = substr_replace($password,'',-1,1);
    } elseif ("\x03" == $c) {
        // if it's Control-C, clear $password and break out of the loop
        $password = NULL;
        break;
    } else {
        // otherwise, add the character to the password
        $password .= $c;
    }
}

20.5.3. Discussion

On Unix, you use /bin/stty to control the terminal characteristics so that typed characters aren't echoed to the screen while you read a password. Windows doesn't have /bin/stty, so you use the W32api extension to get access _getch( ) in the Microsoft C runtime library, msvcrt.dll. The _getch( ) function reads a character without echoing it to the screen. It returns the ASCII code of the character read, so you convert it to a character using chr( ) . You then take action based on the character typed. If it's a newline or carriage return, you break out of the loop because the password has been entered. If it's a backspace, you delete a character from the end of the password. If it's a Control-C interrupt, you set the password to NULL and break out of the loop. If none of these things are true, the character is concatenated to $password. When you exit the loop, $password holds the entered password.

The following code displays Login: and Password: prompts, and compares the entered password to the corresponding encrypted password stored in /etc/passwd. This requires that the system not use shadow passwords.

print "Login: ";
$fh = fopen('php://stdin','r')   or die($php_errormsg);
$username = rtrim(fgets($fh,64)) or die($php_errormsg);

preg_match('/^[a-zA-Z0-9]+$/',$username) 
    or die("Invalid username: only letters and numbers allowed");


print 'Password: ';
`/bin/stty -echo`;
$password = rtrim(fgets($fh,64)) or die($php_errormsg);
`/bin/stty echo`;
print "\n";

// nothing more to read from the keyboard
fclose($fh);

// find corresponding line in /etc/passwd 
$fh = fopen('/etc/passwd','r')   or die($php_errormsg);
$found_user = 0;
while (! ($found_user || feof($fh))) {
    $passwd_line = fgets($fh,256);
    if (preg_match("/^$username:/",$passwd_line)) {
        $found_user = 1;
    }
}
fclose($fh);

$found_user or die ("Can't find user \"$username\"");

// parse the correct line from /etc/passwd 
$passwd_parts = split(':',$passwd_line);

/* encrypt the entered password and compare it to the password in
   /etc/passwd */
$encrypted_password = crypt($password,
                            substr($passwd_parts[1],0,CRYPT_SALT_LENGTH));

if ($encrypted_password == $passwd_parts[1]) {
    print "login successful";
} else {
    print "login unsuccessful";
}

20.5.4. See Also

Documentation on readline( ) at http://www.php.net/readline, chr( ) at http://www.php.net/chr, on w32api_register_function( ) at http://www.php.net/w32api-register-function, and on _getch( ) at http://msdn.microsoft.com/library/en-us/vccore98/HTML/_crt_ _getch.2c_._getche.asp; on Unix, see your system's stty(1) manpage.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.