initialization

This commit is contained in:
Reindl David (IT-PTR-CEN2-SL10) committed 2026-10-07 01:16:57 +02:00
commit 9ff09693ed
92 files changed
+46209

No files matched your search

+317
View File
@@ -0,0 +1,317 @@
<?php
declare(strict_types=1);
/**
* PHP fallback implementation for pwgen logic (pronounceable passwords).
*
* Based on NIST FIPS 181 "Automated Password Generator" standard
* and inspired by roderik/pwgen-php (https://github.com/roderik/pwgen-php)
*
* Original pwgen concept:
* Copyright (c) 2001 Theodore Ts'o <tytso@alum.mit.edu>
*
* PHP implementation credits:
* Copyright (c) 2012 Roderik van der Veer
* BSD License (see https://github.com/roderik/pwgen-php/blob/master/LICENSE)
* Original source: https://github.com/roderik/pwgen-php
*
* Modernized for PHP 8+ by Password Generator Project, 2025.
* This class provides a fallback for pwgen-style password generation when
* the native pwgen executable is not available.
*
* Based on NIST FIPS 181 - Federal Information Processing Standards Publication 181
* "Automated Password Generator" (1993)
*/
class PwGenPhpNg
{
// Character classification flags (based on NIST FIPS 181)
private const VOWEL = 0x01;
private const CONSONANT = 0x02;
private const DIPHTHONG = 0x04;
private const NOT_FIRST = 0x08;
// Unit definitions with pronunciability rules
private array $elements = [
'a' => ['flags' => self::VOWEL, 'weight' => 4],
'ae' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'ah' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'ai' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'b' => ['flags' => self::CONSONANT, 'weight' => 2],
'c' => ['flags' => self::CONSONANT, 'weight' => 2],
'ch' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'd' => ['flags' => self::CONSONANT, 'weight' => 2],
'e' => ['flags' => self::VOWEL, 'weight' => 4],
'ee' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'ei' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'f' => ['flags' => self::CONSONANT, 'weight' => 2],
'g' => ['flags' => self::CONSONANT, 'weight' => 2],
'gh' => ['flags' => self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST, 'weight' => 1],
'h' => ['flags' => self::CONSONANT, 'weight' => 2],
'i' => ['flags' => self::VOWEL, 'weight' => 4],
'ie' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'j' => ['flags' => self::CONSONANT, 'weight' => 1],
'k' => ['flags' => self::CONSONANT, 'weight' => 2],
'l' => ['flags' => self::CONSONANT, 'weight' => 2],
'm' => ['flags' => self::CONSONANT, 'weight' => 2],
'n' => ['flags' => self::CONSONANT, 'weight' => 2],
'ng' => ['flags' => self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST, 'weight' => 1],
'o' => ['flags' => self::VOWEL, 'weight' => 4],
'oh' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'oo' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'p' => ['flags' => self::CONSONANT, 'weight' => 2],
'ph' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'qu' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'r' => ['flags' => self::CONSONANT, 'weight' => 2],
'rh' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
's' => ['flags' => self::CONSONANT, 'weight' => 2],
'sh' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
't' => ['flags' => self::CONSONANT, 'weight' => 2],
'th' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'u' => ['flags' => self::VOWEL, 'weight' => 4],
'v' => ['flags' => self::CONSONANT, 'weight' => 1],
'w' => ['flags' => self::CONSONANT, 'weight' => 2],
'wh' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'x' => ['flags' => self::CONSONANT | self::NOT_FIRST, 'weight' => 1],
'y' => ['flags' => self::VOWEL, 'weight' => 2],
'z' => ['flags' => self::CONSONANT, 'weight' => 1],
];
private array $vowels = [];
private array $consonants = [];
public function __construct()
{
$this->initializePhonemes();
}
/**
* Initialize phoneme arrays based on element classification
*/
private function initializePhonemes(): void
{
foreach ($this->elements as $phoneme => $properties) {
if ($properties['flags'] & self::VOWEL) {
for ($i = 0; $i < $properties['weight']; $i++) {
$this->vowels[] = $phoneme;
}
}
if ($properties['flags'] & self::CONSONANT) {
for ($i = 0; $i < $properties['weight']; $i++) {
$this->consonants[] = $phoneme;
}
}
}
}
/**
* Generate pronounceable passwords using pwgen logic
*
* @param int $length Minimum length of password
* @param int $count Number of passwords to generate
* @param bool $includeNumbers Include numbers in passwords
* @param bool $includeCapitals Include capital letters
* @return array Array of generated passwords
*/
public function generate(int $length = 8, int $count = 1, bool $includeNumbers = true, bool $includeCapitals = true): array
{
if ($length < 4) {
$length = 4; // Minimum for pronounceable passwords
}
$passwords = [];
for ($i = 0; $i < $count; $i++) {
$password = $this->generatePassword($length, $includeNumbers, $includeCapitals);
$passwords[] = $password;
}
return $passwords;
}
/**
* Generate a single pronounceable password
*/
private function generatePassword(int $length, bool $includeNumbers, bool $includeCapitals): string
{
$password = '';
$currentLength = 0;
$lastWasVowel = false;
$syllableLength = 0;
$maxAttempts = 100;
$attempts = 0;
while ($currentLength < $length && $attempts < $maxAttempts) {
$attempts++;
// Start new syllable with consonant, then alternate
if ($syllableLength === 0 || ($syllableLength > 0 && $lastWasVowel)) {
// Choose consonant
$phoneme = $this->selectConsonant($currentLength === 0);
$lastWasVowel = false;
} else {
// Choose vowel
$phoneme = $this->selectVowel();
$lastWasVowel = true;
}
// Check if adding this phoneme exceeds length
if ($currentLength + strlen($phoneme) > $length) {
// Try to find a shorter phoneme or end the password
if ($lastWasVowel) {
$phoneme = $this->selectVowel(1); // Single character vowel
} else {
$phoneme = $this->selectConsonant(false, 1); // Single character consonant
}
if ($currentLength + strlen($phoneme) > $length) {
break; // Can't fit anything more
}
}
$password .= $phoneme;
$currentLength += strlen($phoneme);
$syllableLength++;
// End syllable after 2-4 phonemes
if ($syllableLength >= 2 && random_int(0, 2) === 0) {
$syllableLength = 0;
$lastWasVowel = false;
}
}
// Apply post-processing
if ($includeCapitals) {
$password = $this->addCapitalization($password);
}
if ($includeNumbers && strlen($password) < $length) {
$password = $this->addNumbers($password, $length);
}
return $password;
}
/**
* Select a consonant phoneme
*/
private function selectConsonant(bool $isFirst = false, int $maxLength = PHP_INT_MAX): string
{
$candidates = [];
foreach ($this->consonants as $consonant) {
$element = $this->elements[$consonant];
// Skip if not allowed at beginning
if ($isFirst && ($element['flags'] & self::NOT_FIRST)) {
continue;
}
// Skip if too long
if (strlen($consonant) > $maxLength) {
continue;
}
$candidates[] = $consonant;
}
if (empty($candidates)) {
return 'b'; // Fallback
}
return $candidates[random_int(0, count($candidates) - 1)];
}
/**
* Select a vowel phoneme
*/
private function selectVowel(int $maxLength = PHP_INT_MAX): string
{
$candidates = [];
foreach ($this->vowels as $vowel) {
if (strlen($vowel) <= $maxLength) {
$candidates[] = $vowel;
}
}
if (empty($candidates)) {
return 'a'; // Fallback
}
return $candidates[random_int(0, count($candidates) - 1)];
}
/**
* Add capitalization to password (typically first letter)
*/
private function addCapitalization(string $password): string
{
if (strlen($password) === 0) {
return $password;
}
// Capitalize first letter
return ucfirst(strtolower($password));
}
/**
* Add numbers to password if there's remaining length
*/
private function addNumbers(string $password, int $targetLength): string
{
$remaining = $targetLength - strlen($password);
if ($remaining <= 0) {
return $password;
}
// Add 1-2 numbers
$numbersToAdd = min($remaining, random_int(1, 2));
for ($i = 0; $i < $numbersToAdd; $i++) {
$password .= (string)random_int(0, 9);
}
return $password;
}
/**
* Check if a password meets basic pronounceability rules
*/
public function isPronounceablePassword(string $password): bool
{
if (strlen($password) < 3) {
return false;
}
$vowelCount = 0;
$consonantCount = 0;
// Simple heuristic: check for reasonable vowel/consonant distribution
for ($i = 0; $i < strlen($password); $i++) {
$char = strtolower($password[$i]);
if (in_array($char, ['a', 'e', 'i', 'o', 'u', 'y'])) {
$vowelCount++;
} elseif (ctype_alpha($char)) {
$consonantCount++;
}
}
// Must have at least one vowel and one consonant
// And vowels shouldn't be more than 60% of letters
$totalLetters = $vowelCount + $consonantCount;
return $vowelCount >= 1 && $consonantCount >= 1 &&
($totalLetters === 0 || $vowelCount / $totalLetters <= 0.6);
}
/**
* Get available phoneme elements (for debugging/inspection)
*/
public function getPhonemeCount(): array
{
return [
'vowels' => count(array_unique($this->vowels)),
'consonants' => count(array_unique($this->consonants)),
'total_elements' => count($this->elements)
];
}
}