Skip to content

Instantly share code, notes, and snippets.

@aldoyh
Last active October 30, 2025 17:11
Show Gist options
  • Select an option

  • Save aldoyh/f48315ff61c94e7d75376952e193c6cf to your computer and use it in GitHub Desktop.

Select an option

Save aldoyh/f48315ff61c94e7d75376952e193c6cf to your computer and use it in GitHub Desktop.
Passwd.php another duel face php password generator. For both Web & CLI
<?php
/**
* Password Generator
*
* A simple password generator written in PHP.
*
* Hand crafted by: [HASAN ALDOY](@aldoyh) for Radio Bahrain @RadioBahrain)
* RELEASED: 2025-09-23
*/
declare(strict_types=1);
namespace PassGen;
const VERSION = '1.0.0';
const AUTHOR = 'Mehmet Kose';
const GREEN = "\e[32m";
const RED = "\e[31m";
const YELLOW = "\e[33m";
const RESET = "\e[0m";
class PasswordGenerator
{
private const CHARS_LOWER = 'abcdefghijklmnopqrstuvwxyz';
private const CHARS_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
private const CHARS_NUMBERS = '0123456789';
private const CHARS_SYMBOLS = '!@#$%^&*()_+-=[]{}|;:,.<>/?`~';
private const AMBIGUOUS = 'l1oO0';
private const HISTORY_FILE = 'password_history.json';
public function generate(int $length = 8, bool $numbers = true, bool $symbols = true, bool $ambiguous = false): string
{
if ($length < 1) {
throw new InvalidArgumentException('Password length must be at least 1.');
}
$chars = self::CHARS_LOWER . self::CHARS_UPPER;
$pool = $chars;
if ($numbers) {
$pool .= self::CHARS_NUMBERS;
}
if ($symbols) {
$pool .= self::CHARS_SYMBOLS;
}
if (!$ambiguous) {
$pool = str_replace(str_split(self::AMBIGUOUS), '', $pool);
}
$password = '';
$poolLength = strlen($pool);
if ($poolLength === 0) {
throw new RuntimeException('Character pool is empty.');
}
for ($i = 0; $i < $length; $i++) {
$password .= $pool[random_int(0, $poolLength - 1)];
}
$this->saveHistory($password);
return $password;
}
private function saveHistory(string $password): void
{
$history = $this->getHistory();
$history[] = [
'password' => $password,
'generated_at' => date('Y-m-d H:i:s'),
];
if (!file_put_contents(self::HISTORY_FILE, json_encode($history, JSON_PRETTY_PRINT))) {
error_log('Failed to write to history file: ' . self::HISTORY_FILE);
}
}
public function getHistory(): array
{
if (!file_exists(self::HISTORY_FILE)) {
return [];
}
$content = file_get_contents(self::HISTORY_FILE);
if ($content === false) {
error_log('Failed to read from history file: ' . self::HISTORY_FILE);
return [];
}
$history = json_decode($content, true);
if ($history === null) {
error_log('Failed to decode JSON from history file: ' . self::HISTORY_FILE);
return [];
}
return $history;
}
}
// Main application logic based on environment
if (php_sapi_name() === 'cli') {
// CLI mode
$generator = new PasswordGenerator();
$length = 12;
$numbers = true;
$symbols = false;
$ambiguous = false;
for ($i = 1; $i < count($argv); $i++) {
switch ($argv[$i]) {
case '--length':
$length = (int)($argv[++$i]);
break;
case '--no-numbers':
$numbers = false;
break;
case '--symbols':
$symbols = true;
break;
case '--ambiguous':
$ambiguous = true;
break;
case '--history':
case '--list':
case '-l':
echo "Password History:\n";
$history = $generator->getHistory();
foreach ($history as $item) {
echo " - " . $item['password'] . YELLOW . " (Generated at: "
. $item['generated_at'] . ")" . RESET . PHP_EOL;
}
exit;
case '--help':
echo "Usage: php password_generator.php [options]\n";
echo "Options:\n";
echo " --length <int> Set password length (default: 12)\n";
echo " --no-numbers Exclude numbers\n";
echo " --no-symbols Exclude symbols\n";
echo " --ambiguous Include ambiguous characters (l, 1, o, O, 0)\n";
echo " --history Display password generation history\n";
echo " --help Display this help message\n";
exit;
}
}
try {
$password = $generator->generate($length, $numbers, $symbols, $ambiguous);
echo "Generated Password: " . $password . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}
} else {
// Web mode
$generator = new PasswordGenerator();
$password = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$length = (int)($_POST['length'] ?? 12);
$numbers = isset($_POST['numbers']);
$symbols = isset($_POST['symbols']);
$ambiguous = isset($_POST['ambiguous']);
try {
$password = $generator->generate($length, $numbers, $symbols, $ambiguous);
} catch (Exception $e) {
$error = $e->getMessage();
}
}
$history = $generator->getHistory();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #1a202c;
color: #e2e8f0;
}
.container {
max-width: 500px;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
</style>
</head>
<body class="bg-gray-900 flex items-center justify-center min-h-screen p-4">
<div class="container mx-auto bg-gray-800 p-6 rounded-lg shadow-xl border border-gray-700">
<h1 class="text-2xl font-bold text-center text-white mb-6">Password Generator</h1>
<form method="POST" class="space-y-4">
<div>
<label for="length" class="block text-gray-400 font-medium mb-1">Length</label>
<input type="number" id="length" name="length" value="<?= isset($_POST['length']) ? htmlspecialchars($_POST['length']) : '12'; ?>" min="1" class="w-full p-2 bg-gray-700 border border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white">
</div>
<div class="space-y-2">
<div class="flex items-center">
<input type="checkbox" id="numbers" name="numbers"
checked class="h-4 w-4 text-blue-500 border-gray-600 rounded-md focus:ring-blue-500 bg-gray-700">
<label for="numbers" class="ml-2 text-gray-400">Include Numbers</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="symbols" name="symbols" checked
class="h-4 w-4 text-blue-500 border-gray-600 rounded-md focus:ring-blue-500 bg-gray-700">
<label for="symbols" class="ml-2 text-gray-400">Include Symbols</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="ambiguous" name="ambiguous" class="h-4 w-4 text-blue-500 border-gray-600 rounded-md focus:ring-blue-500 bg-gray-700">
<label for="ambiguous" class="ml-2 text-gray-400">Include Ambiguous Chars</label>
</div>
</div>
<button type="submit" class="w-full bg-blue-600 text-white font-bold py-2 px-4 rounded-md shadow hover:bg-blue-700 transition-colors duration-200">
Generate Password
</button>
</form>
<?php if ($password) : ?>
<div class="mt-6 bg-green-900 text-green-300 p-3 rounded-md border border-green-800 text-center font-mono text-lg break-words">
<?= htmlspecialchars($password); ?>
</div>
<?php elseif (isset($error)) : ?>
<div class="mt-6 bg-red-900 text-red-300 p-3 rounded-md border border-red-800 text-center">
<?= htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<div class="mt-8">
<h2 class="text-xl font-bold text-white mb-3 text-center">History</h2>
<?php if (empty($history)) : ?>
<p class="text-gray-500 text-center text-sm">No passwords generated yet.</p>
<?php else : ?>
<ul class="space-y-2 max-h-40 overflow-y-auto pr-2">
<?php foreach ($history as $item) : ?>
<li class="bg-gray-700 p-3 rounded-md border border-gray-600 flex
justify-between items-center text-sm">
<span class="font-mono break-all text-gray-200">
<?= htmlspecialchars($item['password']); ?></span>
<span class="text-gray-400 ml-4 whitespace-nowrap">
<?= htmlspecialchars($item['generated_at']); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
</body>
</html>
<?php } ?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment