-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c11a631
Showing
5 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
|
||
wordlist.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright 2018 Johann Werner | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
<?php | ||
|
||
/** | ||
* A password generator that generates memorable passwords similar to the | ||
* macOS keychain. For this it uses a public RSS feed to build up a list of | ||
* words to be used in passwords. | ||
* | ||
* After successfully creating a list of words that list is written to disk | ||
* in the file <i>wordlist.json</i>. The next time you create an instance | ||
* of this class and the URL is unavailable that cached version is then used. | ||
* | ||
* Consecutive password generations with the very same instance won't | ||
* recreate the word list but reuse the former one. | ||
* | ||
* @example PasswordGenerator.example.php Class in action. | ||
* | ||
* @author Johann Werner <johann.werner@posteo.de> | ||
* @version 1.0.0 | ||
* @license MIT License | ||
*/ | ||
class PasswordGenerator { | ||
private $url; | ||
private $minlength; | ||
private $maxlength; | ||
private $wordlist = []; | ||
private $wordcache = 'wordlist.json'; | ||
|
||
/** | ||
* Creates an instance of the password generator. You can pass an optional | ||
* array with values that should override the default values for the keys: | ||
* <dl> | ||
* <dt>url</dt> | ||
* <dd>The URL to fetch XML from which is used to create a wordlist from | ||
* it's description nodes.</dd> | ||
* <dt>minlength</dt> | ||
* <dd>The miminum length of characters a word must have.</dd> | ||
* <dt>maxlength</dt> | ||
* <dd>The maxinum length of characters a word must have.</dd> | ||
* </dl> | ||
* | ||
* @param array optional config array | ||
* @param boolean true if data from URL should be fetched, false to use | ||
* only cached wordlist; defaults to true | ||
* | ||
* @throws InvalidArgumentException if the URL is not valid | ||
*/ | ||
public function __construct($params = [], $fetch = true) { | ||
foreach($params as $key => $value) { | ||
$this->$key = $value; | ||
} | ||
if ($fetch) { | ||
if (!isset($this->url) || !filter_var($this->url, FILTER_VALIDATE_URL)) { | ||
throw new InvalidArgumentException('Invalid URL: ' . $this->url); | ||
} | ||
if ($this->minlength > $this->maxlength) { | ||
throw new InvalidArgumentException('Invalid word lengths: min=' | ||
. $this->minlength . ' max=' . $this->maxlength); | ||
} | ||
$this->populate_wordlist(); | ||
} else { | ||
$this->read_wordlist(); | ||
} | ||
} | ||
|
||
/** | ||
* Creates an instance of password generator that will use German wordlist. | ||
* | ||
* @static | ||
* @return PasswordGenerator configured instance | ||
*/ | ||
public static function DE() { | ||
return new self([ | ||
'url' => 'http://www.tagesschau.de/newsticker.rdf', | ||
'minlength' => 8, | ||
'maxlength' => 15, | ||
]); | ||
} | ||
|
||
/** | ||
* Creates an instance of password generator that will use English wordlist. | ||
* | ||
* @static | ||
* @return PasswordGenerator configured instance | ||
*/ | ||
public static function EN() { | ||
return new self([ | ||
'url' => 'http://rss.dw.com/rdf/rss-en-all', | ||
'minlength' => 4, | ||
'maxlength' => 12, | ||
]); | ||
} | ||
|
||
/** | ||
* Creates an instance of password generator that will use the cached wordlist. | ||
* No HTTP reqeust to the URL source will be made. Be sure that you have an | ||
* appropriate file <i>wordlist.json</i> present. | ||
* | ||
* @static | ||
* @return PasswordGenerator configured instance that uses cache only | ||
*/ | ||
public static function CACHED() { | ||
return new self([], false); | ||
} | ||
|
||
/** | ||
* Generates a password and returns it. If the used wordlist is empty and no | ||
* password can be generated the value null is returned. | ||
* | ||
* @return string|null generated password or null if there is no wordlist | ||
*/ | ||
public function generate() { | ||
$listlength = count($this->wordlist); | ||
if ($listlength < 1) { | ||
$this->read_wordlist(); | ||
$listlength = count($this->wordlist); | ||
if ($listlength < 1) { | ||
return null; | ||
} | ||
} | ||
|
||
$words = []; | ||
$times = 2; | ||
while ($times--) { | ||
$r = $this->random_int(0, $listlength - 1); | ||
$words[] = $this->wordlist[$r]; | ||
} | ||
|
||
return $words[0] . $this->random_int(1, 999) . chr($this->random_int(33, 47)) . $words[1]; | ||
} | ||
|
||
private function populate_wordlist() { | ||
$input = $this->get_url_data($this->url); | ||
$doc = new DOMDocument(); | ||
@$doc->loadXML($input); | ||
$descriptions = $doc->getElementsByTagName('description'); | ||
$wordlist = array(); | ||
foreach($descriptions as $description) { | ||
$text = $description->textContent; | ||
$words = explode(' ', $text); | ||
foreach($words as $word) { | ||
$cleanword = preg_replace('/[,.;:?!\'"]+/', '', trim($word)); | ||
$wordlength = strlen($cleanword); | ||
|
||
if ($wordlength >= $this->minlength && $wordlength <= $this->maxlength && ctype_alpha($cleanword)) { | ||
$wordlist[strtoupper(substr($cleanword, 0, 1)) . strtolower(substr($cleanword, 1))] = 1; | ||
} | ||
} | ||
} | ||
$this->wordlist = array_keys($wordlist); | ||
|
||
if (count($wordlist) > 0) { | ||
$this->save_wordlist(); | ||
} | ||
} | ||
|
||
private function save_wordlist() { | ||
file_put_contents($this->wordcache, json_encode($this->wordlist)); | ||
} | ||
|
||
private function read_wordlist() { | ||
if (file_exists($this->wordcache)) { | ||
$this->wordlist = json_decode(file_get_contents($this->wordcache), true); | ||
} | ||
} | ||
|
||
private function random_int($low, $high) { | ||
if (version_compare(PHP_VERSION, '7.0.0', '<')) { | ||
return rand($low, $high); | ||
} | ||
return random_int($low, $high); | ||
} | ||
|
||
private function get_url_data($url) { | ||
$ch = curl_init(); | ||
curl_setopt($ch, CURLOPT_URL, $url); | ||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | ||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); | ||
$data = curl_exec($ch); | ||
curl_close($ch); | ||
|
||
return $data; | ||
} | ||
} | ||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
include 'PasswordGenerator.class.php'; | ||
|
||
|
||
// create instance with German word list | ||
$gen = PasswordGenerator::DE(); | ||
// generate a password | ||
echo $gen->generate(), "\n"; | ||
|
||
// reuse the existing wordlist without triggering a new HTTP request | ||
echo $gen->generate(), "\n"; | ||
|
||
|
||
// create instance with English word list | ||
$gen = PasswordGenerator::EN(); | ||
// generate a password | ||
echo $gen->generate(), "\n"; | ||
echo $gen->generate(), "\n"; | ||
echo $gen->generate(), "\n"; | ||
|
||
|
||
// new instance with custom params | ||
$gen = new PasswordGenerator([ | ||
'url' => 'http://www.tagesschau.de/newsticker.rdf', | ||
'minlength' => 3, | ||
'maxlength' => 6, | ||
]); | ||
// generate a password | ||
echo $gen->generate(), "\n"; | ||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# php-password-generator | ||
|
||
The PHP class PasswordGenerator serves as a password generator to create memorable passwords like the macOS keychain does. | ||
|
||
## Getting Started | ||
|
||
Copy the file *PasswordGenerator.class.php* into your project and include it in your own PHP file(s) with `include 'PasswordGenerator.class.php';`. Then create an instance either by using the predefined static methods for specific languages or customize it yourself by using the standard constructor. | ||
|
||
```php | ||
include 'PasswordGenerator.class.php'; | ||
|
||
// create instance with English word list | ||
$gen = PasswordGenerator::EN(); | ||
|
||
// generate a password | ||
echo $gen->generate(); | ||
``` | ||
|
||
### Prerequisites | ||
|
||
This class works with PHP >= 5.4 and needs a working internet connection. | ||
|
||
### Password Syntax | ||
|
||
The generated passwords follow a specific syntax: | ||
|
||
``` | ||
<random word><number between 1 and 999><special character><random word> | ||
``` | ||
|
||
Some examples of generated passwords: | ||
|
||
* Theyre778+Breakthrough | ||
* Reforms13)Translated | ||
* When249*Awards | ||
|
||
## Word Lists | ||
|
||
The class uses RSS feeds to build a word list from which random words are used for password generation. The class has some predefined configuration for the languages English and German but can be customized too: | ||
|
||
```php | ||
include 'PasswordGenerator.class.php'; | ||
|
||
// create instance with English word list | ||
$gen = PasswordGenerator::EN(); | ||
|
||
// create instance with German word list | ||
$gen = PasswordGenerator::DE(); | ||
|
||
// create instance with custom parameters | ||
$gen = new PasswordGenerator([ | ||
'url' => 'http://www.tagesschau.de/newsticker.rdf', | ||
'minlength' => 3, | ||
'maxlength' => 6, | ||
]); | ||
``` | ||
|
||
The params *minlength* and *maxlength* denote the allowed lengths of the words from the URL source to get into the word list. If a word list has been successfully built that list is saved into the file `wordlist.json`. The next time you create an instance of PasswordGenerator and the URL source cannot be contacted or does not contain any usable words that cached list is loaded instead. If you reuse the very same instance the word list is reused so no further HTTP requests are generated. | ||
|
||
```php | ||
include 'PasswordGenerator.class.php'; | ||
|
||
$gen = PasswordGenerator::EN(); | ||
|
||
// reuse word list without rebuilding | ||
echo 'Password 1: ', $gen->generate(); | ||
echo 'Password 2: ', $gen->generate(); | ||
echo 'Password 3: ', $gen->generate(); | ||
``` | ||
|
||
### Caching | ||
|
||
If you need to use that class in contexts where you do not have an internet connection you can prebuild a word list and copy the generated `wordlist.json` file into your project. When using the PasswordGenerator you can tell it to only use that cached list and skip the URL source request: | ||
|
||
```php | ||
include 'PasswordGenerator.class.php'; | ||
|
||
$gen = PasswordGenerator::CACHED(); | ||
|
||
echo $gen->generate(); | ||
``` | ||
|
||
### URL Sources | ||
|
||
As source for word lists this class uses a configurable RSS feed. The feed has to be in XML format and contain *description* tags from which the textual content is extracted. | ||
|
||
## License | ||
|
||
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. |