Password Generator

Generates human-friendly strong passwords.

Image preview of Password Generator plugin.

password_bar.5m.php

Edit
Open on GitHub
#!/usr/bin/env php
<?php

# <xbar.title>Password Generator</xbar.title>
# <xbar.version>v1.0</xbar.version>
# <xbar.author>Adi</xbar.author>
# <xbar.author.github>gomedia-adi</xbar.author.github>
# <xbar.desc>Generates human-friendly strong passwords.</xbar.desc>
# <xbar.image>http://www.greatoceanmedia.com.au/images/63.png</xbar.image>
# <xbar.dependencies>php</xbar.dependencies>
# <xbar.abouturl>http://www.greatoceanmedia.com.au/bitbar/password-bar</xbar.abouturl>

/*
	BitBar plugin help: https:github.com/matryer/bitbar

	Plugin updated 22/6/20

	Version history
		1.0	- also now generates a single obscure password (i.e. one that doesn't contain words)
			- length of obscure password user definable
			- changed format element "p" to "s" (coz they're symbols more than punctuation)
			- option to select subset of symbols
			- Dark Mode compatible menubar icon
		0.1	- initial release

	Credits
		- plugin inspired by a Mac app called Arcana by Tekuris
		- thanks to Christian S for v1.0 improvement suggestions
*/

/* TODO
	- format massaging/validation?
*/

define('CONFIG_FILE', "/var/tmp/bitbar.password_bar.config.php");
define('DEFAULT_FORMAT', "3,n,5,s,4");
define('FULL_SYM', "!\"#$%&'()*+,-./:;<=>[email protected][\]^_`{|}~");
define('PARTIAL_SYM', "!#$%&*+,-.:;[email protected]^_|~"); // without the quotes, brackets, slashes
define('LIMITED_SYM', "!#$%*+[email protected]^_"); // the supposed eBay subset
define('ALPHA_NUM', "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
define('DEFAULT_LENGTH', 14);

function update_config($format, $symbols, $length) {
// rewrite config file with given values

	file_put_contents(CONFIG_FILE, "<?php\n");
	file_put_contents(CONFIG_FILE, "// PASSWORD_BAR CONFIGURATION\n", FILE_APPEND);
	file_put_contents(CONFIG_FILE, "\$format = '$format';\n", FILE_APPEND);
	file_put_contents(CONFIG_FILE, "\$symbols = '$symbols';\n", FILE_APPEND);
	file_put_contents(CONFIG_FILE, "\$length = '$length';\n", FILE_APPEND);
	file_put_contents(CONFIG_FILE, "?>\n", FILE_APPEND);
}

function generate_pwd($format, $symbols) {
// generate password using format
	global $words1, $words2, $words3, $words4, $words5, $words6, $words7, $words8, $words9; // make word lists available between function calls

	$password = '';

	$symbol = FULL_SYM;
	if ($symbols == 'partial') $symbol = PARTIAL_SYM;
	if ($symbols == 'limited') $symbol = LIMITED_SYM;

	$parts = explode(',', $format);
	foreach ($parts as $part) {
		// random single digit
		if ($part == 'n')
			$password .= rand(1,9);
		// random symbol character (from predefined list)
		if (($part == 'p') || ($part == 's')) {
			$random_symbol = $symbol[rand(0, strlen($symbol)-1)];
			$password .= $random_symbol;
		}
		// random word of n letters
		if (strpos('123456789', $part) !== FALSE) {
			$words_var = 'words'.$part;
			if (!isset($$words_var)) { // create n-letter word array for first time
				$num_char = (int)$part;
				$cmd = "grep -x '.\{".$num_char."\}' /usr/share/dict/words | grep -v '[[:upper:]]'";
				$$words_var = array();
				exec($cmd, $$words_var, $err);
			}
			$words = $$words_var;
			$rand_key = array_rand($words); // get random key
			$password .= ucfirst($words[$rand_key]); // get random Word
		}
	}

	return $password;
}

function generate_obscure_pwd($length, $symbols) {
// generate an obscure password

	$password = array();

	$string = ALPHA_NUM.FULL_SYM;
	if ($symbols == 'partial') $string = ALPHA_NUM.PARTIAL_SYM;
	if ($symbols == 'limited') $string = ALPHA_NUM.LIMITED_SYM;

	$chars = str_split($string); // convert string to array
	shuffle($chars); // randomise chars
	$rand_keys = array_rand($chars, $length); // randomly pick array keys
	foreach ($rand_keys as $key) // create password from alpha/num/symbols array using random keys
		$password[] = $chars[$key];
	// rotate password chars until begins with alpha
	while (!ctype_alpha($password[0]))
		array_push($password, array_shift($password));

	return implode('', $password); // return string
}

//-------------------------------------------------------------------------------------------------

//??? CONFIG FILE

// initialise config file
if (!file_exists(CONFIG_FILE))
	touch(CONFIG_FILE);

// read config file
include(CONFIG_FILE);

// provide defaults
if (!isset($format) || $format == '') $format = DEFAULT_FORMAT;
if (!isset($symbols) || $symbols == '' || !in_array($symbols, array('full', 'partial', 'limited'))) $symbols = 'partial';
if (!isset($length) || !$length) $length = '14';

//??? MENU ACTIONS

// menu option selected
if (isset($argv[1])) {
	// prompt for readable password format & save
	if ($argv[1] == "set_format") {
		$cmd = "osascript -e 'set theString to text returned of (display dialog \"Readable Password Format \" default answer \"".$format."\" buttons {\"Cancel\", \"Save\"} default button 2)'";
		exec($cmd, $out, $err);
		if (!$err) { // something entered
			$str = implode('', $out); // text entered by user
			$format = ($str ? $str : DEFAULT_FORMAT); // reset to default if blank or zero
			update_config($format, $symbols, $length);
		}
	}
	// prompt for obscure password length & save
	if ($argv[1] == "set_length") {
		$cmd = "osascript -e 'set theString to text returned of (display dialog \"Obscure Password Length \" default answer \"".$length."\" buttons {\"Cancel\", \"Save\"} default button 2)'";
		exec($cmd, $out, $err);
		if (!$err) { // something entered
			$str = implode('', $out); // text entered by user
			$length = (int)$str; // convert str to int (will become zero if not int)
			$length = ($length ? $length : DEFAULT_LENGTH); // reset to default if blank or zero
			update_config($format, $symbols, $length);
		}
	}
	// save symbol subset
	if ($argv[1] == "set_symbols")
		update_config($format, $argv[2], $length);
	// copy password to clipboard
	if ($argv[1] == "copy_pwd") {
		$cmd = "echo '".$argv[2]."' | base64 --decode | pbcopy"; // decode & copy to clipboard
		exec($cmd, $out, $err);
	}
	// help dialog - some special chars used below to avoid CLI interpretation: backslash=⧵ (U+29F5); apostrophe='(U+FF07), double quote=″ (U+2033)
	if ($argv[1] == "help") {
		$cmd = "osascript -e 'display dialog \"Readable Passwords Format
	1-9	- random word with the specified number of letters
		  (e.g. 3 becomes a three letter Capitalised word)
	n	- random single digit number
	s	- random symbol character (see below)

	Default format:  3,n,5,s,4
	- generates a password containing a 3 letter word,
	a digit, a 5 letter word, a symbol character,
	and finally a 4 letter word (e.g. Tig5Pesky?Muff)

Obscure Password
	This is a completely randomised string of letters,
	digits and symbols. Set the length from the menu
	(default = 14).

Symbol Sets
	Full:		!″#$%&'()*+,-./:;<=>[email protected][⧵]^_`{|}~
	Partial:	!#$%&*+,-.:;[email protected]^_|~
	Limited:	!#$%*+[email protected]^_
\" buttons {\"OK\"} default button 1'";
		exec($cmd, $out, $err);
		exit;
	}
}

//??? MENUBAR

// display icon: 36x36 png (32x32max image + 2px transparent border), 144 dpi, colour mode grey, encoded using "base64 icon.png" ... prepend "|templateImage=" & append "\n"
echo "|templateImage=iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAFiUAABYlAUlSJPAAAAAHdElNRQfkBhUWCih+dz6LAAABaUlEQVRIx9XWv0oDQRAG8PnSRBBbLRXFUoiI4EX8TyQG7CzsLBT1CXwBjdaCIGJhZx0QuV7zAoJCihQWVgFRiEW4eFmL6GWyenuzi01um+Tj9pfN7jB3RD17YQj78PGCN1RwhYIrsweljRDj9oz/i2mPLTvmOoZRUJiXMyMGRqEmh57YtA8UsYlbfLJsVcYMsinPUTrB0qoMmoomtDDK8pMO1U5SCVJ/9CmkV5aX9BuToGZnEZRm+bBtBY2x3ThkeT1KAynFD/scA0TI4IFlx1KoaKyjgCBzpo2MwoWMWUlgfBmTQ2hkzpCSMF7XpCaOUGHf7rAkW82M9uuL31s/iWVY1FBOY7Ju7TCv7Y3nxsxqDXXhPxglZ7oPcA1l/excVlNA649qObBl5mILb9uG8Yw1nBXvES6NdwXyBtYwrGfD5SHYwI3G7Ni11DQeUcc7MkS4Z8yuy4tC309bQMnhT8Wgp6hhnXr6+gJZx0AEmEK/dgAAAABJRU5ErkJggg==\n";

//??? TOP-LEVEL MENU

// display readable human-friendly passwords
echo "---\n";
echo "Click to Copy:\n";
foreach (range(1, 10) as $i) {
	$pwd = generate_pwd($format, $symbols);		// generate password
 	$pwd_nobar = str_replace('|', '|', $pwd);	// "|" interpreted by bitbar, so substitute with a vertical line (U+FF5C)
	$pwd_enc = base64_encode($pwd);				// encode password to hide unix special characters from CLI
	echo $pwd_nobar." | terminal=false bash=\"".$argv[0]."\" param1=copy_pwd param2=".$pwd_enc." refresh=false terminal=false"."\n";
}

// display an obscure password
echo "…\n";
$pwd = generate_obscure_pwd($length, $symbols);
$pwd_nobar = str_replace('|', '|', $pwd);	// "|" interpreted by bitbar, so substitute with a vertical line (U+FF5C)
$pwd_enc = base64_encode($pwd);				// encode password to hide unix special characters from CLI
echo $pwd_nobar." | terminal=false bash=\"".$argv[0]."\" param1=copy_pwd param2=".$pwd_enc." refresh=false terminal=false"."\n";

// regenerate passwords (i.e. refresh)
echo "---\n";
echo
	'Regenerate'
	." | terminal=false bash=\"".$argv[0]."\" refresh=true terminal=false"
	."\n"
;

// display current settings
echo "---\n";
echo "Readable Format: $format\n";
echo "Obscure Length: $length\n";
echo "---\n";

// set readable password format
echo
	'Set Readable Format…'
	." |  bash=\"".$argv[0]."\" param1=set_format refresh=true terminal=false"
	."\n"
;

// set obscure password length
echo
	'Set Obscure Length…'
	." |  bash=\"".$argv[0]."\" param1=set_length refresh=true terminal=false"
	."\n"
;

// symbols subset options
echo "Symbol Sets\n";
foreach (array('full', 'partial', 'limited') as $this_symbol_set)
	echo
		'--'
		.ucfirst($this_symbol_set)
		.($this_symbol_set == $symbols ? ' ✓' : '')
		." | terminal=false bash=\"".$argv[0]."\" param1=set_symbols param2=\"$this_symbol_set\" refresh=true"
		."\n"
	;

// display help dialog
echo
	'Help'
	." |  bash=\"".$argv[0]."\" param1=help terminal=false"
	."\n"
;

?>