PHP中的命令行密码提示


Command Line Password Prompt in PHP

我正在编写一个命令行工具来帮助我的web应用程序。它需要一个密码才能连接到服务。我希望脚本显示密码提示,这样我就不必将其作为命令行参数传递。

这很容易,但我希望它不要在输入密码时将密码回声到屏幕上。我如何使用PHP来实现这一点?

使用纯PHP(无system('stty'))并用*替换字符可获得额外积分。

编辑:

该脚本将在类似unix的系统(linux或mac)上运行。该脚本是用PHP编写的,很可能会保持原样。

此外,为了记录在案,stty的方法是:

echo "Password: ";
system('stty -echo');
$password = trim(fgets(STDIN));
system('stty echo');
// add a new line since the users CR didn't echo
echo "'n";

我不想让system()的电话在那里。

在sitepoint上找到。

function prompt_silent($prompt = "Enter Password:") {
  if (preg_match('/^win/i', PHP_OS)) {
    $vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
    file_put_contents(
      $vbscript, 'wscript.echo(InputBox("'
      . addslashes($prompt)
      . '", "", "password here"))');
    $command = "cscript //nologo " . escapeshellarg($vbscript);
    $password = rtrim(shell_exec($command));
    unlink($vbscript);
    return $password;
  } else {
    $command = "/usr/bin/env bash -c 'echo OK'";
    if (rtrim(shell_exec($command)) !== 'OK') {
      trigger_error("Can't invoke bash");
      return;
    }
    $command = "/usr/bin/env bash -c 'read -s -p '""
      . addslashes($prompt)
      . "'" mypassword && echo '$mypassword'";
    $password = rtrim(shell_exec($command));
    echo "'n";
    return $password;
  }
}

根据您的环境(即,不在Windows上),您可以使用ncurses库(特别是ncurses_noecho()函数来停止键盘回声,使用ncurses_getch()来读取输入)来获取密码,而无需在屏幕上显示。

您可以使用我的hiddenput.exe文件来获得真正的隐藏输入,而不会在屏幕上的任何地方泄露信息。

<?php
echo 'Enter password: ';
$password = exec('hiddeninput.exe');
echo PHP_EOL;
echo 'Password was: ' . $password . PHP_EOL;

如果删除最后一个echo,密码应该永远不会出现,但您可以直接使用它进行验证。

下面的方法适用于Linux CLI,但不适用于Windows CLI或Apache。它也只适用于标准Ascii表中的字符(不过,要使它与扩展字符集兼容并不需要太多时间)。

我放了一些代码来防止复制和粘贴密码。如果删除了两个注释之间的位,则可以在其中插入/粘贴密码。

我希望这能帮助到别人。

<?php
    echo("Password: ");
    $strPassword=getObscuredText();
    echo("'n");
    echo("You entered: ".$strPassword."'n");
    function getObscuredText($strMaskChar='*')
    {
        if(!is_string($strMaskChar) || $strMaskChar=='')
        {
            $strMaskChar='*';
        }
        $strMaskChar=substr($strMaskChar,0,1);
        readline_callback_handler_install('', function(){});
        $strObscured='';
        while(true)
        {
            $strChar = stream_get_contents(STDIN, 1);
            $intCount=0;
// Protect against copy and paste passwords
// Comment '/'/'/ to remove password injection protection
            $arrRead = array(STDIN);
            $arrWrite = NULL;
            $arrExcept = NULL;
            while (stream_select($arrRead, $arrWrite, $arrExcept, 0,0) && in_array(STDIN, $arrRead))            
            {
                stream_get_contents(STDIN, 1);
                $intCount++;
            }
//        /'/'/'
// End of protection against copy and paste passwords
            if($strChar===chr(10))
            {
                break;
            }
            if ($intCount===0)
            {
                if(ord($strChar)===127)
                {
                    if(strlen($strObscured)>0)
                    {
                        $strObscured=substr($strObscured,0,strlen($strObscured)-1);
                        echo(chr(27).chr(91)."D"." ".chr(27).chr(91)."D");
                    }
                }
                elseif ($strChar>=' ')
                {
                    $strObscured.=$strChar;
                    echo($strMaskChar);
                    //echo(ord($strChar));
                }
            }
        }
        readline_callback_handler_remove();
        return($strObscured);
    }
?>

这是适用于所有平台的最简单的解决方案:

function prompt($message = 'prompt: ', $hidden = false) {
    if (PHP_SAPI !== 'cli') {
        return false;
    }
    echo $message;
    $ret = 
        $hidden
        ? exec(
            PHP_OS === 'WINNT' || PHP_OS === 'WIN32'
            ? __DIR__ . ''prompt_win.bat'
            : 'read -s PW; echo $PW'
        )
        : rtrim(fgets(STDIN), PHP_EOL)
    ;
    if ($hidden) {
        echo PHP_EOL;
    }
    return $ret;
}

然后在同一目录中创建prompt_win.bat

SetLocal DisableDelayedExpansion
Set "Line="
For /F %%# In ('"Prompt;$H & For %%# in (1) Do Rem"') Do (
    Set "BS=%%#"
)
:loop_start
    Set "Key="
    For /F "delims=" %%# In ('Xcopy /L /W "%~f0" "%~f0" 2^>Nul') Do (
        If Not Defined Key (
            Set "Key=%%#"
        )
    )
    Set "Key=%Key:~-1%"
    SetLocal EnableDelayedExpansion
    If Not Defined Key (
        Goto :loop_end
    )
    If %BS%==^%Key% (
        Set "Key="
        If Defined Line (
            Set "Line=!Line:~0,-1!"
        )
    )
    If Not Defined Line (
        EndLocal
        Set "Line=%Key%"
    ) Else (
        For /F "delims=" %%# In ("!Line!") Do (
            EndLocal
            Set "Line=%%#%Key%"
        )
    )
    Goto :loop_start
:loop_end
Echo;!Line!

我想如果不使用stty-echo,就没有简单的方法(实际上我想不出任何方法)。如果你打算在windows上运行它,你可以创建一个批处理脚本,为php脚本提供未选择的类型信息。

@echo off
cls
SET /P uname=Enter Username:
echo hP1X500P[PZBBBfh#b##fXf-V@`$fPf]f3/f1/5++u5>in.com
set /p password=Enter password :<nul
for /f “tokens=*” %%i in (’in.com’) do set password=%%i
del in.com
echo.
c:'php'php.exe d:'php'test.php %uname% “%password%”
Pause

示例取自http://www.indiangnu.org/2008/php-hide-user-input-using-batch-script-windows/

适用于所有支持powershell的windows系统(来源:http://www.qxs.ch/2013/02/08/php-cli-password-prompts-on-windows-7/)

<?php
// please set the path to your powershell, here it is: C:'Windows'system32'WindowsPowerShell'v1.0'powershell.exe
$pwd=shell_exec('C:'Windows'system32'WindowsPowerShell'v1.0'powershell.exe -Command "$Password=Read-Host -assecurestring '"Please enter your password'" ; $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) ; echo $PlainPassword;"');
$pwd=explode("'n", $pwd); $pwd=$pwd[0];
echo "You have entered the following password: $pwd'n";
system('stty -echo');

禁用当前终端回波,以及:

system('stty echo');

重新启用。在fgets之前和之后设置。

接受的答案不够好。首先,Windows解决方案不适用于Windows7及以上版本。其他操作系统的解决方案依赖于Bash和Bash内置的"read"。然而,有些系统不使用Bash(例如OpenBSD),而且这显然不起作用。

在这个博客中,我讨论了这个解决方案,它适用于从95到8的几乎所有基于Unix的操作系统和Windows。Windows解决方案使用在Win32 API上用C编写的外部程序。其他操作系统的解决方案使用外部命令"stty"。我还没有看到一个基于Unix的系统没有"stty"

为什么不使用SSH连接?您可以将命令抽象出来,重定向输入/输出,并拥有完全的控制权。

你可以为某人提供一个纯粹干净的shell,只需少量的权限,并让密码与SSH2::Connect()一起发布即可打开shell。

我创建了一个很好的类来使用phpSSH2扩展,也许它对您有所帮助;(它还可以进行安全的文件传输)

<?php
/**
 * SSH2
 * 
 * @package Pork
 * @author SchizoDuckie
 * @version 1.0
 * @access public
 */
class SSH2
{
    private $host;
    private $port;
    private $connection;
    private $timeout;
    private $debugMode;
    private $debugPointer;
    public $connected; 
    public $error;

    /**
     * SSH2::__construct()
     * 
     * @param mixed $host
     * @param integer $port
     * @param integer $timeout
     * @return
     */
    function __construct($host, $port=22, $timeout=10)
    {
        $this->host = $host;
        $this->port = $port;
        $this->timeout = 10;
        $this->error = 'not connected';
        $this->connection = false;
        $this->debugMode = Settings::Load()->->get('Debug', 'Debugmode');
        $this->debugPointer = ($this->debugMode) ? fopen('./logs/'.date('Y-m-d--H-i-s').'.log', 'w+') : false;
        $this->connected = false;
    }

    /**
     * SSH2::connect()
     * 
     * @param mixed $username
     * @param mixed $password
     * @return
     */
    function connect($username, $password)
    {
        $this->connection = ssh2_connect($this->host, $this->port);
        if (!$this->connection) return $this->error("Could not connect to {$this->host}:{$this->port}");
        $this->debug("Connected to {$this->host}:{$this->port}");
        $authenticated = ssh2_auth_password($this->connection, $username, $password);
        if(!$authenticated) return $this->error("Could not authenticate: {$username}, check your password");
        $this->debug("Authenticated successfully as {$username}");
        $this->connected = true;
        return true;
    }
    /**
     * SSH2::exec()
     *
     * @param mixed $command shell command to execute
     * @param bool $onAvailableFunction a function to handle any available data.
     * @param bool $blocking blocking or non-blocking mode. This 'hangs' php execution until the command has completed if you set it to true. If you just want to start an import and go on, use this icm onAvailableFunction and false
     * @return
     */
    function exec($command, $onAvailableFunction=false, $blocking=true)
    {
        $output = '';
        $stream = ssh2_exec($this->connection, $command);
        $this->debug("Exec: {$command}");
        if($onAvailableFunction !== false)
        {
            $lastReceived = time();
            $timeout =false;
            while (!feof($stream) && !$timeout)
            {
                $input = fgets($stream, 1024);
                if(strlen($input) >0)
                {
                    call_user_func($onAvailableFunction, $input);
                    $this->debug($input);
                    $lastReceived = time();
                }
                else
                {
                    if(time() - $lastReceived >= $this->timeout)
                    {
                        $timeout = true;
                        $this->error('Connection timed out');
                        return($this->error);
                    }
                }
            }
        }
        if($blocking === true && $onAvailableFunction === false)
        {
            stream_set_blocking($stream, true);
            $output = stream_get_contents($stream);
            $this->debug($output);
        }
        fclose($stream);
        return($output);
    }

    /**
     * SSH2::createDirectory()
     *
     * Creates a directory via sftp
     *
     * @param string $dirname
     * @return boolean success
     *  
     */
    function createDirectory($dirname)
    {
        $ftpconnection = ssh2_sftp ($this->connection);
        $dircreated = ssh2_sftp_mkdir($ftpconnection, $dirname, true);
        if(!$dircreated) 
        {
            $this->debug("Directory not created: ".$dirname);
        }
        return $dircreated;
    }
    public function listFiles($dirname)
    {
        $input = $this->exec(escapeshellcmd("ls  {$dirname}"));
        return(explode("'n", trim($input)));
    }
    public function sendFile($filename, $remotename)
    {
        $this->debug("sending {$filename} to {$remotename} ");
        if(file_exists($filename) && is_readable($filename))
        {
            $result = ssh2_scp_send($this->connection, $filename, $remotename, 0664);
        }
        else
        {
            $this->debug("Unable to read file : ".$filename);
            return false;
        }
        if(!$result) $this->debug("Failure uploading {$filename} to {$remotename}");
        return $result;
    }
    public function getFile($remotename, $localfile)
    {
        $this->debug("grabbing {$remotename} to {$localfile}");
        $result = ssh2_scp_recv($this->connection, $remotename, $localfile);
        if(!$result) $this->debug("Failure downloading {$remotename} to {$localfile}");
        return $result;
    }
    /**
     * SSH2::debug()
     * 
     * @param mixed $message
     * @return
     */
    function debug($message) 
    {
        if($this->debugMode)
        {
            fwrite($this->debugPointer, date('Y-m-d H:i:s')." : ".$message."'n");
        }
    }

    /**
     * SSH2::error()
     * 
     * @param mixed $errorMsg
     * @return
     */
    function error($errorMsg) 
    {
        $this->error = $errorMsg;
        $this->debug($errorMsg);
        return false;
    }   
    /**
     * SSH2::__destruct()
     * 
     * @return
     */
    function __destruct() 
    {
        if($this->connection){
            $this->connection = null;
        }
        if($this->debugMode && $this->debugPointer)
        {
            fclose($this->debugPointer);
        }
    }       

}

用法示例:

$settings = Settings::Load()->Get("SecureServer");
$ssh = new SSH2($settings['host']);
if( $ssh->connect($settings['username'], $settings['password']))
{
    echo $ssh->exec("ls -la ".$settings['path'], false, true);  
    flush();    
}

从理论上讲,您可以使用stream_set_blocking()来实现这一点,但在管理STDIN时似乎存在一些PHP错误。

看:http://bugs.php.net/bug.php?id=34972http://bugs.php.net/bug.php?id=36030

试试看:

echo "Enter Password: ";
$stdin = fopen('php://stdin','r');
// Trying to disable stream blocking
stream_set_blocking($stdin, FALSE) or die ('Failed to disable stdin blocking');
// Trying to set stream timeout to 1sec
stream_set_timeout ($stdin, 1) or die ('Failed to enable stdin timeout');