File indexing completed on 2024-12-22 05:37:18

0001 <?php
0002 
0003 /**
0004  * Port of the famous GNU/Linux Password Generator ("pwgen") to PHP.
0005  * This file may be distributed under the terms of the GNU Public License.
0006  * Copyright (C) 2001, 2002 by Theodore Ts'o <tytso@alum.mit.edu>
0007  * Copyright (C) 2009 by Superwayne <superwayne@superwayne.org>
0008  */
0009 class PWGen
0010 {
0011     // Flags for the pwgen function
0012     const PW_DIGITS = 0x0001;
0013     const PW_UPPERS = 0x0002; // At least one upper letter
0014     const PW_SYMBOLS = 0x0004;
0015     const PW_AMBIGUOUS = 0x0008;
0016 
0017     // Flags for the pwgen element
0018     const CONSONANT = 0x0001;
0019     const VOWEL = 0x0002;
0020     const DIPHTHONG = 0x0004;
0021     const NOT_FIRST = 0x0008;
0022     const NO_VOWELS = 0x0010;
0023 
0024     private $pwgen;
0025     private $pwgen_flags;
0026     private $password;
0027     private $pw_length;
0028 
0029     private static $initialized = false; // static block alread called?
0030     private static $elements;
0031     private static $pw_ambiguous;
0032     private static $pw_symbols;
0033     private static $pw_digits;
0034     private static $pw_uppers;
0035     private static $pw_lowers;
0036     private static $pw_vowels;
0037 
0038     /**
0039      * @param int $length Length $length of the generated password. Default: 8
0040      * @param bool $secure Generate $secure completely random, hard-to-memorize passwords. These should only
0041      *            be used for machine passwords, since otherwise it's almost guaranteed that
0042      *            users will simply write the password on a piece of paper taped to the monitor...
0043      * @param bool $numerals Include $numerals at least one number in the password. This is the default.
0044      * @param bool $capitalize Include $capitalize at least one capital letter in the password. This is the default.
0045      * @param bool $ambiguous Don $ambiguous 't use characters that could be confused by the user when printed,
0046      *            such as 'l' and '1', or '0' or 'O'. This reduces the number of possible
0047      *            passwords significantly, and as such reduces the quality of the passwords.
0048      *            It may be useful for users who have bad vision, but in general use of this
0049      *            option is not recommended.
0050      * @param bool $no_vovels $no -vowels    Generate random passwords that do not contain vowels or numbers that might be
0051      *            mistaken for vowels. It provides less secure passwords to allow system
0052      *            administrators to not have to worry with random passwords accidentally contain
0053      *            offensive substrings.
0054      * @param bool $symbols Include $symbols at least one special character in the password.
0055      */
0056     public function __construct($length = 8, $secure = false, $numerals = true, $capitalize = true,
0057                                 $ambiguous = false, $no_vovels = false, $symbols = false)
0058     {
0059         self::__static();
0060 
0061         $this->pwgen = 'pw_phonemes';
0062 
0063         $this->setLength($length);
0064         $this->setSecure($secure);
0065         $this->setNumerals($numerals);
0066         $this->setCapitalize($capitalize);
0067         $this->setAmbiguous($ambiguous);
0068         $this->setNoVovels($no_vovels);
0069         $this->setSymbols($symbols);
0070     }
0071 
0072     /**
0073      * Length of the generated password. Default: 8
0074      */
0075     public function setLength($length)
0076     {
0077         if (is_numeric($length) && $length > 0) {
0078             $this->pw_length = $length;
0079             if ($this->pw_length < 5) {
0080                 $this->pwgen = 'pw_rand';
0081             }
0082             if ($this->pw_length <= 2) {
0083                 $this->setCapitalize(false);
0084             }
0085             if ($this->pw_length <= 1) {
0086                 $this->setNumerals(false);
0087             }
0088         } else {
0089             $this->pw_length = 8;
0090         }
0091         return $this;
0092     }
0093 
0094     /**
0095      * Generate completely random, hard-to-memorize passwords. These should only used for machine passwords,
0096      * since otherwise it's almost guaranteed that users will simply write the password on a piece of paper
0097      * taped to the monitor...
0098      * Please note that this function implies that you want passwords which include symbols, numerals and
0099      * capital letters.
0100      */
0101     public function setSecure($secure)
0102     {
0103         if ($secure) {
0104             $this->pwgen = 'pw_rand';
0105             $this->setNumerals(true);
0106             $this->setCapitalize(true);
0107         } else {
0108             $this->pwgen = 'pw_phonemes';
0109         }
0110         return $this;
0111     }
0112 
0113     /**
0114      * Include at least one number in the password. This is the default.
0115      */
0116     public function setNumerals($numerals)
0117     {
0118         if ($numerals) {
0119             $this->pwgen_flags |= self::PW_DIGITS;
0120         } else {
0121             $this->pwgen_flags &= ~self::PW_DIGITS;
0122         }
0123         return $this;
0124     }
0125 
0126     /**
0127      * Include at least one capital letter in the password. This is the default.
0128      */
0129     public function setCapitalize($capitalize)
0130     {
0131         if ($capitalize) {
0132             $this->pwgen_flags |= self::PW_UPPERS;
0133         } else {
0134             $this->pwgen_flags &= ~self::PW_UPPERS;
0135         }
0136         return $this;
0137     }
0138 
0139     /**
0140      * Don't use characters that could be confused by the user when printed, such as 'l' and '1', or '0' or
0141      * 'O'. This reduces the number of possible passwords significantly, and as such reduces the quality of
0142      * the passwords. It may be useful for users who have bad vision, but in general use of this option is
0143      * not recommended.
0144      */
0145     public function setAmbiguous($ambiguous)
0146     {
0147         if ($ambiguous) {
0148             $this->pwgen_flags |= self::PW_AMBIGUOUS;
0149         } else {
0150             $this->pwgen_flags &= ~self::PW_AMBIGUOUS;
0151         }
0152         return $this;
0153     }
0154 
0155     /**
0156      * Generate random passwords that do not contain vowels or numbers that might be mistaken for vowels. It
0157      * provides less secure passwords to allow system administrators to not have to worry with random
0158      * passwords accidentally contain offensive substrings.
0159      */
0160     public function setNoVovels($no_vovels)
0161     {
0162         if ($no_vovels) {
0163             $this->pwgen = 'pw_rand';
0164             $this->pwgen_flags |= self::NO_VOWELS | self::PW_DIGITS | self::PW_UPPERS;
0165         } else {
0166             $this->pwgen = 'pw_phonemes';
0167             $this->pwgen_flags &= ~self::NO_VOWELS;
0168         }
0169         return $this;
0170     }
0171 
0172     public function setSymbols($symbols)
0173     {
0174         if ($symbols) {
0175             $this->pwgen_flags |= self::PW_SYMBOLS;
0176         } else {
0177             $this->pwgen_flags &= ~self::PW_SYMBOLS;
0178         }
0179         return $this;
0180     }
0181 
0182     /**
0183      * @return mixed
0184      */
0185     public function generate()
0186     {
0187         if ($this->pwgen == 'pw_phonemes') {
0188             $this->pw_phonemes();
0189         } else { // $this->pwgen == 'pw_rand'
0190             $this->pw_rand();
0191         }
0192         return $this->password;
0193     }
0194 
0195     private function pw_phonemes()
0196     {
0197         $this->password = array();
0198 
0199         do {
0200             $feature_flags = $this->pwgen_flags;
0201             $c = 0;
0202             $prev = 0;
0203             $should_be = self::my_rand(0, 1) ? self::VOWEL : self::CONSONANT;
0204             $first = 1;
0205 
0206             while ($c < $this->pw_length) {
0207                 $i = self::my_rand(0, count(self::$elements) - 1);
0208                 $str = self::$elements[$i]->str;
0209                 $len = strlen($str);
0210                 $flags = self::$elements[$i]->flags;
0211 
0212                 // Filter on the basic type of the next element
0213                 if (($flags & $should_be) == 0)
0214                     continue;
0215                 // Handle the NOT_FIRST flag
0216                 if ($first && ($flags & self::NOT_FIRST))
0217                     continue;
0218                 // Don't allow VOWEL followed a Vowel/Dipthong pair
0219                 if (($prev & self::VOWEL) && ($flags & self::VOWEL) &&
0220                     ($flags & self::DIPHTHONG)
0221                 )
0222                     continue;
0223                 // Don't allow us to overflow the buffer
0224                 if ($len > $this->pw_length - $c)
0225                     continue;
0226 
0227                 // Handle the AMBIGUOUS flag
0228                 if ($this->pwgen_flags & self::PW_AMBIGUOUS) {
0229                     if (strpbrk($str, self::$pw_ambiguous) !== false)
0230                         continue;
0231                 }
0232 
0233                 /*
0234                  * OK, we found an element which matches our criteria,
0235                  * let's do it!
0236                  */
0237                 for ($j = 0; $j < $len; $j++)
0238                     $this->password[$c + $j] = $str[$j];
0239 
0240                 // Handle PW_UPPERS
0241                 if ($this->pwgen_flags & self::PW_UPPERS) {
0242                     if (($first || $flags & self::CONSONANT) && (self::my_rand(0, 9) < 2)) {
0243                         $this->password[$c] = strtoupper($this->password[$c]);
0244                         $feature_flags &= ~self::PW_UPPERS;
0245                     }
0246                 }
0247 
0248                 $c += $len;
0249 
0250                 // Time to stop?
0251                 if ($c >= $this->pw_length)
0252                     break;
0253 
0254                 // Handle PW_DIGITS
0255                 if ($this->pwgen_flags & self::PW_DIGITS) {
0256                     if (!$first && (self::my_rand(0, 9) < 3)) {
0257                         do {
0258                             $ch = strval(self::my_rand(0, 9));
0259                         } while (($this->pwgen_flags & self::PW_AMBIGUOUS) &&
0260                             strpos(self::$pw_ambiguous, $ch) !== false);
0261                         $this->password[$c++] = $ch;
0262                         $feature_flags &= ~self::PW_DIGITS;
0263 
0264                         $first = 1;
0265                         $prev = 0;
0266                         $should_be = self::my_rand(0, 1) ? self::VOWEL : self::CONSONANT;
0267                         continue;
0268                     }
0269                 }
0270 
0271                 // Handle PW_SYMBOLS
0272                 if ($this->pwgen_flags & self::PW_SYMBOLS) {
0273                     if (!$first && (self::my_rand(0, 9) < 2)) {
0274                         do {
0275                             $ch = self::$pw_symbols[self::my_rand(0,
0276                                 strlen(self::$pw_symbols) - 1)];
0277                         } while (($this->pwgen_flags & self::PW_AMBIGUOUS) &&
0278                             strpos(self::$pw_ambiguous, $ch) !== false);
0279                         $this->password[$c++] = $ch;
0280                         $feature_flags &= ~self::PW_SYMBOLS;
0281                     }
0282                 }
0283 
0284                 // OK, figure out what the next element should be
0285                 if ($should_be == self::CONSONANT) {
0286                     $should_be = self::VOWEL;
0287                 } else { // should_be == VOWEL
0288                     if (($prev & self::VOWEL) || ($flags & self::DIPHTHONG) ||
0289                         (self::my_rand(0, 9) > 3)
0290                     )
0291                         $should_be = self::CONSONANT;
0292                     else
0293                         $should_be = self::VOWEL;
0294                 }
0295                 $prev = $flags;
0296                 $first = 0;
0297             }
0298         } while ($feature_flags & (self::PW_UPPERS | self::PW_DIGITS | self::PW_SYMBOLS));
0299 
0300         $this->password = implode('', $this->password);
0301     }
0302 
0303     private function pw_rand()
0304     {
0305         $this->password = array();
0306 
0307         $chars = '';
0308         if ($this->pwgen_flags & self::PW_DIGITS) {
0309             $chars .= self::$pw_digits;
0310         }
0311         if ($this->pwgen_flags & self::PW_UPPERS) {
0312             $chars .= self::$pw_uppers;
0313         }
0314         $chars .= self::$pw_lowers;
0315         if ($this->pwgen_flags & self::PW_SYMBOLS) {
0316             $chars .= self::$pw_symbols;
0317         }
0318 
0319         do {
0320             $len = strlen($chars);
0321             $feature_flags = $this->pwgen_flags;
0322             $i = 0;
0323 
0324             while ($i < $this->pw_length) {
0325                 $ch = $chars[self::my_rand(0, $len - 1)];
0326                 if (($this->pwgen_flags & self::PW_AMBIGUOUS) &&
0327                     strpos(self::$pw_ambiguous, $ch) !== false
0328                 )
0329                     continue;
0330                 if (($this->pwgen_flags & self::NO_VOWELS) &&
0331                     strpos(self::$pw_vowels, $ch) !== false
0332                 )
0333                     continue;
0334                 $this->password[$i++] = $ch;
0335                 if (strpos(self::$pw_digits, $ch) !== false)
0336                     $feature_flags &= ~self::PW_DIGITS;
0337                 if (strpos(self::$pw_uppers, $ch) !== false)
0338                     $feature_flags &= ~self::PW_UPPERS;
0339                 if (strchr(self::$pw_symbols, $ch) !== false)
0340                     $feature_flags &= ~self::PW_SYMBOLS;
0341             }
0342         } while ($feature_flags & (self::PW_UPPERS | self::PW_DIGITS | self::PW_SYMBOLS));
0343 
0344         $this->password = implode('', $this->password);
0345     }
0346 
0347     /**
0348      * Generate a random number n, where $min <= n < $max
0349      * Mersenne Twister is used as an algorithm
0350      */
0351     public static function my_rand($min = 0, $max = 0)
0352     {
0353         return mt_rand($min, $max);
0354     }
0355 
0356     /**
0357      * This method initializes all static vars which contain complex datatypes.
0358      * It acts somewhat like a static block in Java. Since PHP does not support this principle, the method
0359      * is called from the constructor. Because of that you can not access the static vars unless there
0360      * exists at least one object of the class.
0361      */
0362     private static function __static()
0363     {
0364         if (!self::$initialized) {
0365             self::$initialized = true;
0366             self::$elements = array(
0367                 new PWElement('a', self::VOWEL),
0368                 new PWElement('ae', self::VOWEL | self::DIPHTHONG),
0369                 new PWElement('ah', self::VOWEL | self::DIPHTHONG),
0370                 new PWElement('ai', self::VOWEL | self::DIPHTHONG),
0371                 new PWElement('b', self::CONSONANT),
0372                 new PWElement('c', self::CONSONANT),
0373                 new PWElement('ch', self::CONSONANT | self::DIPHTHONG),
0374                 new PWElement('d', self::CONSONANT),
0375                 new PWElement('e', self::VOWEL),
0376                 new PWElement('ee', self::VOWEL | self::DIPHTHONG),
0377                 new PWElement('ei', self::VOWEL | self::DIPHTHONG),
0378                 new PWElement('f', self::CONSONANT),
0379                 new PWElement('g', self::CONSONANT),
0380                 new PWElement('gh', self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST),
0381                 new PWElement('h', self::CONSONANT),
0382                 new PWElement('i', self::VOWEL),
0383                 new PWElement('ie', self::VOWEL | self::DIPHTHONG),
0384                 new PWElement('j', self::CONSONANT),
0385                 new PWElement('k', self::CONSONANT),
0386                 new PWElement('l', self::CONSONANT),
0387                 new PWElement('m', self::CONSONANT),
0388                 new PWElement('n', self::CONSONANT),
0389                 new PWElement('ng', self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST),
0390                 new PWElement('o', self::VOWEL),
0391                 new PWElement('oh', self::VOWEL | self::DIPHTHONG),
0392                 new PWElement('oo', self::VOWEL | self::DIPHTHONG),
0393                 new PWElement('p', self::CONSONANT),
0394                 new PWElement('ph', self::CONSONANT | self::DIPHTHONG),
0395                 new PWElement('qu', self::CONSONANT | self::DIPHTHONG),
0396                 new PWElement('r', self::CONSONANT),
0397                 new PWElement('s', self::CONSONANT),
0398                 new PWElement('sh', self::CONSONANT | self::DIPHTHONG),
0399                 new PWElement('t', self::CONSONANT),
0400                 new PWElement('th', self::CONSONANT | self::DIPHTHONG),
0401                 new PWElement('u', self::VOWEL),
0402                 new PWElement('v', self::CONSONANT),
0403                 new PWElement('w', self::CONSONANT),
0404                 new PWElement('x', self::CONSONANT),
0405                 new PWElement('y', self::CONSONANT),
0406                 new PWElement('z', self::CONSONANT)
0407             );
0408             self::$pw_ambiguous = 'B8G6I1l0OQDS5Z2';
0409             self::$pw_symbols = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
0410             self::$pw_digits = '0123456789';
0411             self::$pw_uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
0412             self::$pw_lowers = 'abcdefghijklmnopqrstuvwxyz';
0413             self::$pw_vowels = '01aeiouyAEIOUY';
0414         }
0415     }
0416 
0417     /**
0418      * Returns the last generated password. If there is none, a new one will be generated.
0419      */
0420     public function __toString()
0421     {
0422         return (empty($this->password) ? $this->generate() : $this->password);
0423     }
0424 
0425 }
0426 
0427 class PWElement
0428 {
0429     public $str;
0430     public $flags;
0431 
0432     public function __construct($str, $flags)
0433     {
0434         $this->str = $str;
0435         $this->flags = $flags;
0436     }
0437 }