PHP中的命令行密码提示

时间:2008-10-09 15:13:38

标签: php passwords

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

这很容易,但我希望它不会在键入屏幕时回显密码。我怎么能用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()来电。

10 个答案:

答案 0 :(得分:39)

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;
  }
}

答案 1 :(得分:9)

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

答案 2 :(得分:9)

您可以使用我的hiddeninput.exe文件获取真正隐藏的输入,而不会在屏幕上的任何位置泄露信息。

<?php

echo 'Enter password: ';
$password = exec('hiddeninput.exe');
echo PHP_EOL;

echo 'Password was: ' . $password . PHP_EOL;

如果删除最后一个回显,则密码永远不会显示,但您可以使用该密码进行验证。

答案 3 :(得分:2)

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

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!

答案 4 :(得分:1)

我想没有使用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/

的示例

答案 5 :(得分:1)

以下方法在Linux CLI下有效,但在Windows CLI或Apache下无效。它也只能与标准Ascii表中的chars一起使用(不过,使其与扩展chars集兼容并不需要花费太多时间。)

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

我希望这对某人有帮助。

<?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);
    }
?>

答案 6 :(得分:0)

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

你可以为某人提供一个纯粹的干净外壳,只需要几乎不需要的权利,然后让密码与SSH2 :: Connect()一起发布,以打开外壳。

我创建了一个很好的类来使用php SSH2扩展,也许它可以帮助你; (它还确保文件传输安全)

<?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();    
}

答案 7 :(得分:0)

理论上你可以使用stream_set_blocking()来做,但看起来有一些PHP错误管理STDIN。

查找: http://bugs.php.net/bug.php?id=34972 http://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');

答案 8 :(得分:0)

接受的答案还不够好。首先,Windows解决方案不适用于Windows 7及更高版本。其他操作系统的解决方案依赖于Bash和bash内置的“读取”。但是,有些系统不使用Bash(例如OpenBSD),而这显然不起作用。

在这个blog中,我讨论了几乎适用于任何基于Unix的操作系统和Windows 95到8的解决方案.Windows解决方案使用C语言编写的顶级Win32 API上的外部程序。其他操作系统的解决方案使用外部命令'stty'。我还没有看到基于Unix的系统没有'stty'

答案 9 :(得分:0)

适用于每个拥有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";