Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 68 |
| iaPwd | |
0.00% |
0 / 1 |
|
0.00% |
0 / 10 |
930.00 | |
0.00% |
0 / 68 |
| __construct | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 4 |
|||
| get_policy | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
|||||
| set_policy | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 2 |
|||
| set_irregularSequences | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 2 |
|||
| passwordPolicyCheck | |
0.00% |
0 / 1 |
210.00 | |
0.00% |
0 / 23 |
|||
| has_sequence | |
0.00% |
0 / 1 |
42.00 | |
0.00% |
0 / 15 |
|||
| get_rules | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 12 |
|||
| passwordHash | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 1 |
|||
| passwordVerify | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 1 |
|||
| passwordNeedsRehash | |
0.00% |
0 / 1 |
2.00 | |
0.00% |
0 / 1 |
|||
| costRecomended | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 7 |
|||
| <?php | |
| namespace ia\Lib; | |
| use ia\Util\Str; | |
| use URLify; | |
| //@TODO | |
| // check max wrong attempts 30, block for x time? in caller | |
| // check vs last n pwds ($gsqlClass, $table, $pk)? | |
| // invalid list | |
| // reading | |
| //https://www.networkworld.com/article/3199607/linux/dealing-with-nists-about-face-on-password-complexity.html | |
| //the maximum length for passwords be set to 64 characters min 8 | |
| //passwords be checked against known bad passwords, banned lists, etc | |
| //passwords only be changed when forgotten | |
| //http://world.std.com/~reinhold/diceware.html | |
| //https://www.grc.com/haystack.htm | |
| //*** https://github.com/danielmiessler/SecLists | |
| //**** https://github.com/ircmaxell/password-policy | |
| // minLength: 0 } o 1 | |
| /** | |
| * iaPwd Check password vs characters policy, hashit and check hash vs password | |
| * | |
| */ | |
| class iaPwd { | |
| protected $options = ['cost' => 10]; | |
| protected $algorithm = PASSWORD_DEFAULT; | |
| protected $policy = [ | |
| 'pwd_min_chars' => 12, | |
| 'pwd_max_chars' => 60, | |
| 'pwd_min_digitos' => 1, | |
| 'pwd_min_minusculas' => 1, | |
| 'pwd_min_mayusculas' => 1, | |
| 'pwd_min_simbolos' => 1, | |
| 'pwd_max_sequenciales' => 4, | |
| ]; | |
| protected $irregularSequences = [ | |
| 'qwertyuiop', | |
| 'asdfghijkl', | |
| 'zxcvbnm', | |
| 'poiuytrewq', | |
| 'lkjhgfdsa', | |
| 'mnbvcxz', | |
| '986532', | |
| ]; | |
| /** | |
| * iaPwd::__construct() | |
| * | |
| * @param int $algorithm php's algorithm constant | |
| * @param integer $cost | |
| * @param array $policy | |
| */ | |
| public function __construct($algorithm = PASSWORD_DEFAULT, $cost = 10, $policy = []) { | |
| $this->algorithm = $algorithm; | |
| $this->options['cost'] = $cost; | |
| $this->set_policy($policy); | |
| } | |
| /** | |
| * Policy for min/max characters, digits, lowercase, uppercase, symbols, sequences | |
| * | |
| * @return array | |
| */ | |
| public function get_policy() {return $this->policy;} | |
| /** check password policy */ | |
| /** | |
| * Set policy for min/max characters, digits, lowercase, uppercase, symbols, sequences | |
| * | |
| * @param array $policy one or more keys from | |
| * 'pwd_min_chars','pwd_max_chars', 'pwd_min_digitos', 'pwd_min_minusculas','pwd_min_mayusculas', 'pwd_min_simbolos', 'pwd_max_sequenciales' | |
| * | |
| */ | |
| public function set_policy($policy) { | |
| $this->policy = array_merge($this->policy, $policy); | |
| } | |
| public function set_irregularSequences($irregularSequences) { | |
| $this->irregularSequences = $irregularSequences; | |
| } | |
| /** | |
| * Check the passwords complies with character's policy | |
| * | |
| * @param string $pwd | |
| * @return string error message or empty | |
| */ | |
| public function passwordPolicyCheck($pwd) { | |
| if(empty($pwd)) { | |
| return 'Falto la contresaña'; | |
| } | |
| $trim = Str::strim($pwd); | |
| if($trim !== $pwd) { | |
| return "No puede comenzar ni terminar con espacio, ni tener 2 espacios juntos"; | |
| } | |
| $str = URLify::downcode($trim); | |
| if($str !== $trim) { | |
| return "No puede llevar acentos, dierecis, eñes, ni ¿ ¡"; | |
| } | |
| $rules = $this->get_rules(); | |
| foreach($rules as $i=>$r) { | |
| $n = preg_match_all($r['regexp'], $str, $matches); | |
| if(!empty($r['min']) && $n < $r['min']) { | |
| return $r['msg']; | |
| } | |
| if(!empty($r['max']) && $n > $r['max']) { | |
| return $r['msg']; | |
| } | |
| if($n > 0 && !empty($r['existe'])) { | |
| return $r['msg']; | |
| } | |
| } | |
| if($this->has_sequence($str, $this->policy['pwd_max_sequenciales'])) { | |
| return "No puede tener $this->policy[pwd_max_sequenciales] o más letras o números consecutivos"; | |
| } | |
| foreach($this->irregularSequences as $s) { | |
| if(stripos($str, substr($s,0,$this->policy['pwd_min_chars']))!==false) { | |
| return "La sequencia de carácteres $s es inválida"; | |
| } | |
| } | |
| return ''; | |
| } | |
| /** | |
| * has_sequence() | |
| * | |
| * @param string $str | |
| * @param int $maxSecuenciales | |
| * @return bool | |
| */ | |
| private function has_sequence($str, $maxSecuenciales) { | |
| $maxSecuenciales--; | |
| if($maxSecuenciales <= 0) { | |
| return false; | |
| } | |
| $seguidos = 0; | |
| $prev = 0; | |
| $len = strlen($str); | |
| for($i = 0; $i < $len; $i++) { | |
| $c = ord( strtolower( $str[$i] ) ); | |
| if($c === $prev + 1 || $c === $prev - 1) { | |
| $seguidos++; | |
| if($seguidos >= $maxSecuenciales) { | |
| return true; | |
| } | |
| } else { | |
| $seguidos = 0; | |
| } | |
| $prev = $c; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Rules for passwords [regexp=>'to full fill', 'min'=>1, 'max'=>8, 'msg'=>'Explain rule'] | |
| * | |
| * @return array | |
| */ | |
| private function get_rules() { | |
| return [ | |
| ['regexp'=>'/./', 'min'=>$this->policy['pwd_min_chars'], 'max'=>$this->policy['pwd_max_chars'], | |
| 'msg'=>"al menos ".$this->policy['pwd_min_chars']." y máximo ".$this->policy['pwd_max_chars']." caracteres"], | |
| ['regexp'=>'/\\d/', 'min'=>$this->policy['pwd_min_digitos'], | |
| 'msg'=>"al menos ".$this->policy['pwd_min_digitos']." número"], | |
| ['regexp'=>'/[a-z]/', 'min'=>$this->policy['pwd_min_minusculas'], | |
| 'msg'=>"al menos ".$this->policy['pwd_min_minusculas']." minúscula"], | |
| ['regexp'=>'/[A-Z]/', 'min'=>$this->policy['pwd_min_mayusculas'], | |
| 'msg'=>"al menos ".$this->policy['pwd_min_mayusculas']." mayúscula"], | |
| ['regexp'=>'/[^a-zA-Z\\d\\s]/', 'min'=>$this->policy['pwd_min_simbolos'], | |
| 'msg'=>"al menos ".$this->policy['pwd_min_simbolos']." symbolo (! \" \' . , ; : ? = # $ % / ( ) - _ { } [ ] * + )"], | |
| ['regexp' => '/([a-zA-Z0-9])\1{'.($this->policy['pwd_max_sequenciales'] - 1).",}/", 'existe'=>true, | |
| 'msg'=>"máximo caracteres ".$this->policy['pwd_max_sequenciales']." iguales seguidos"], | |
| ]; | |
| } | |
| /** Hash password **/ | |
| /** | |
| * Hash the password | |
| * | |
| * @param string $password | |
| * @return string | |
| */ | |
| public function passwordHash($password) { | |
| return password_hash($password, $this->algorithm, $this->options); | |
| } | |
| /** | |
| * Check password matches hash | |
| * | |
| * @param string $password | |
| * @param string $hash | |
| * @return bool | |
| */ | |
| public function passwordVerify($password, $hash) { | |
| return password_verify($password, $hash); | |
| } | |
| /** | |
| * Rehash the pasword's hash, for example on cost, algorithm change | |
| * | |
| * @param string $hash | |
| * @return string | |
| */ | |
| public function passwordNeedsRehash($hash) { | |
| return password_needs_rehash($hash , $this->algorithm, $this->options); | |
| } | |
| /** | |
| * Recomend best cost for time target in seconds | |
| * | |
| * @param float $timeTarget (seconds) | |
| * @return int recomended password cost | |
| */ | |
| public function costRecomended($timeTarget = 0.1) { | |
| $cost = 8; | |
| do { | |
| $cost++; | |
| $start = microtime(true); | |
| password_hash("test", $this->algorithm, ["cost" => $cost]); | |
| $end = microtime(true); | |
| } while (($end - $start) < $timeTarget); | |
| return $cost; | |
| } | |
| } |