Command Line Password Prompt in PHP

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Command Line Password Prompt in PHP



I'm writing a command line tool to help my web app. It needs a password to connect to the service. I'd like the script to show a password prompt so I don't have to pass it as a command line argument.



That's easy enough, but I'd like it to not echo the password to the screen as it's typed. How can I do this with PHP?



Bonus points for doing it in pure PHP (no system('stty')) and replacing the characters with *.


system('stty')


*



EDIT:



The script will run on a unix like system (linux or mac). The script is written in PHP, and will most likely stay like that.



Also, for the record, the stty way of doing it is:


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



I'd prefer to not have the system() calls in there.


system()





What OS will the command line script run on? Will the command line script be written in PHP or the OS's batch scripting language?
– Brendan Kidwell
Oct 9 '08 at 15:38




11 Answers
11



Found on 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;






Does not work on Windows 7. According to various online forums, won't work on anything else than Windows XP and 2003 Server.
– Tgr
Dec 12 '11 at 13:15





See my answer below (or go to github.com/Seldaek/hidden-input directly) for a solution that works from XP up to 7, 32/64bit, and no ugly prompt popping up.
– Seldaek
Aug 25 '12 at 22:23





qxs.ch/2013/02/08/php-cli-password-prompts-on-windows-7
– JMW
Sep 3 '16 at 18:20





VBS and bash? We should throw in a few more languages.
– Ivan Vučica
Oct 7 '16 at 15:18





rtrim could strip valid characters (i.e., any whitespace ending the string), but you can omit it and use echo -n instead.
– Synexis
Oct 8 '16 at 19:41



rtrim


echo -n



You can use my hiddeninput.exe file to get real hidden input without leaking the information anywhere on screen.


<?php

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

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



If you remove the last echo, the password should never show up, but you can use that for validation obvoiusly.





Doesn't work on my linux box for obvious reasons.
– Brian Hannay
Oct 24 '17 at 21:02





I am sure that hiddeninput.exe from the project is not a massive security threat.. but it is just not good practice to use random binary blobs from the internet to handle passwords. Even if this code is secure, it is just a placeholder for a malicious actor to inject something nasty...
– ftrotter
Jul 11 at 6:34



Depending on your environment (i.e., not on Windows), you can use the ncurses library (specifically, the ncurses_noecho() function to stop keyboard echo and ncurses_getch() to read the input) to get the password without displaying it on screen.



I guess that there is no simple way of doing it (actually I can't think of any way) without using stty -echo.
If you intent running it on windows, you could create a batch script that would provide the unechoed typed info to your php script.


@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:phpphp.exe d:phptest.php %uname% “%password%”
Pause



example taken from http://www.indiangnu.org/2008/php-hide-user-input-using-batch-script-windows/





nice trick to create a text-only COM file (looks a bit like the EICAR anti-virus test ;-) ) unfortunately this wont work under 64-bit Windows... (no more 16-bit COM support... and creating a EXE file this way... good luck!)
– Ale
Jan 9 '17 at 13:56



This is the easiest solution for all platforms:


function prompt($message = 'prompt: ', $hidden = false) PHP_OS === 'WIN32'
? __DIR__ . 'prompt_win.bat'
: 'read -s PW; echo $PW'
)
: rtrim(fgets(STDIN), PHP_EOL)
;
if ($hidden)
echo PHP_EOL;

return $ret;



Then create prompt_win.bat in the same directory:


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!





rtrim could strip valid characters (i.e., any whitespace ending the string), but you can omit it and use echo -n instead.
– Synexis
Oct 8 '16 at 19:41



rtrim


echo -n



Why not use an SSH connection? You can abstract the commands away, redirect input/output and have full control.



You can provide someone with a pure clean shell with as little rights as neccesary, and let the password just be POST'ed along with to SSH2::Connect() to open the shell.



I created a nice class to work with the php SSH2 extension, maybe it helps you;
(and it also does secure file transfers)


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







Usage example:


$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();





i am getting error: PHP Fatal error: Class 'Settings' not found in /home/tester/tools/SSH/conn_ssh3.php on line 2, i named the ssh2 class as Settings.php, and also tried to change the Settings::Load() to SSH2::Load()
– kamal
Jan 17 '11 at 16:11



The accepted answer is not good enough. First of all, the Windows solution doesn't work on Windows 7 and above. The solution for other OSs depends on Bash and bash built-in 'read'. However, there are systems which does not use Bash (eg. OpenBSD) and where this obviously won't work.



In this blog I've discussed solution which works on almost any Unix based OS and Windows from 95 to 8. The Windows solution uses external program written in C on top Win32 API. The solution for other OSs uses external command 'stty'. I have yet to see a Unix based system which does not have 'stty'





I think it will be better if you provide a concise version of your post here (may be with only examples for each approach), cause simply posting a link is against the initial idea of SE sites.
– user907860
Sep 11 '13 at 8:33





Is there an generic library that offers the ability to prompt the user for passwords, normal plain text, and menu options all cross platform?
– CMCDragonkai
Nov 13 '13 at 1:52





@CMCDragonkai No, there isn't. Such feature is not implemented in PHP, so it can't be done using only PHP. There is a ncurses PHP extension but it doesn't work on Windows.
– robert petranovic
Nov 18 '13 at 16:36



Works on every windows system, that has powershell support. (source from: 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:Windowssystem32WindowsPowerShellv1.0powershell.exe
$pwd=shell_exec('C:Windowssystem32WindowsPowerShellv1.0powershell.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: $pwdn";



I've re-formatted JMW's 3 line solution so you can just cut and paste it into your existing PHP code.


function getPassword()

$pwd=shell_exec('C:Windowssystem32WindowsPowerShellv1.0powershell.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];
return $pwd;



To use it:


$usersPassword=getPassword();



I'm on Powershell V5.0 but the directory path still shows as v1.0 so the quoted string in the shell_exec call should be OK.



The below method works under Linux CLI but not under Windows CLI or Apache. It also only works with chars in the standard Ascii table (It would not take much to make it compatible with extended char sets though).



I have put a bit of code in to protect against copy and paste passwords. If the bit between the two comments is removed then a password can be injected/pasted in.



I hope this helps someone.


<?php

echo("Password: ");
$strPassword=getObscuredText();
echo("n");
echo("You entered: ".$strPassword."n");

function getObscuredText($strMaskChar='*')

if(!is_string($strMaskChar)
?>



Theorically you can do it using stream_set_blocking(), but looks like there are some PHP bugs managing STDIN.



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



Try yourself:


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');






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard