PHP的最严密码规则校验类
需求:
- 口令长度必须大于8位,长度限制为8-20个字符;
- 口令应包含大写字母、小写字母、数字、特殊符号,缺一不可(创建用户时的初始密码、用户修改密码时必须符合此规范);
- 口令中不得包含2位及以上的相同数字或字母。(如chrdw#11的最后2位,aa$HDHXT的前2位,sz&555pzc的中间3位);
- 口令中不得包含与账号相同的字母组合,含大小写组合。(如账号为guozw,密码为guoZW#16);
- 不得使用与操作系统、数据库等相关的词组作为口令。(如root、admin、mysql、oralce、system);
- 不得使用看似符合要求,实为3位及以上连续键盘序列组合作为口令。(如:123qweASD,1qaz@WSX等); 说明:建议考虑主键盘区、数字键盘区域,数字、字母、特殊字符正反向连续键盘序列组合。
- 口令不符合规则的拒绝登录,防止后台设置简单密码; 说明:未针对该规则做修改,当前实现仅在源头限制住,即:添加/修改用户密码时限制密码组成规则,但登录时仅校验密码正确性,不会再次校验密码组成规则
面对以上需求 ,特别是第六条,很奇葩有木有
废话不说上代码
<?php
/**
*
* An helper class focusing on password validation
*/
class HelperPassword
{
const MAX_PASSWORD_LIMIT = 20;
const MIN_PASSWORD_LIMIT = 8;
//是否启用严格模式 留一个后门
// 对应数据库字段ep_params key=【allow_password_sample】 = 0 表示严格
//1表示启用严格模式 0表示不启用严格模式
//这里严格模式与普通模式的区别仅仅是三连的规则
//严格模式需要支持三连
const STRICT_MODEL_STATUS = 1;
const SAMPLE_MODEL_STATUS = 0;
private static $forbidden_keywords = array("root","admin","oracle","system","mysql");
private $algorithm;
private $saltLength;
private static $allow_special_characters = '~!@#$%^&*()[]{}|:\'+="<>?,./;\\\_-’‘?、。,;:“”」「【】·`《》¥…';
private static $str_continuities = array(
"1234567890 0987654321", //数字倒序
"qwertyuiop asdfghjkl zxcvbnm QWERTYUIOP ASDFGHJKL ZXCVBNM", //主键盘顺序
"poiuytrewq lkjhgfdsa mnbvcxz POIUYTREWQ LKJHGFDSA MNBVCXZ", //主键盘逆序
"qaz wsx edc rfv tgb yhn ujm QAZ WSX EDC RFV TGB YHN UJM",//主键盘正向斜
"zaq xsw cde vfr bgt nhy mju ZAQ XSW CDE VFR BGT NHY MJU",//主键盘正向斜逆序
"esz rdx tfc ygv uhb ijn okm OKM IJN UHB YGV TFC RDX ESZ",//主键盘反向斜
"zse xdr cft vgy bhu nji mko MKO NJI BHU VGY CFT XDR ZSE",//主键盘反向斜逆序
"147 369 258 852 963 741" //小键盘
//特殊字符不计算在内 否则无休止
);
public function __construct($algorithm, $saltLength) {
$this->algorithm = $algorithm;
$this->saltLength = $saltLength;
}
/**
* 加密
* @param $password
*
* @return string
* @throws PasswordException
*/
public function encrypt($password)
{
if (self::validate($password)) {
$salt = HelperRandom::generateString($this->getSaltLength());
return $salt . hash($this->getAlgorithm(), $salt . $password);
}
return "";
}
public function verify($password, $hash)
{
$salt = substr($hash, 0, $this->getSaltLength());
$hashed = substr($hash, $this->getSaltLength());
$result = hash($this->getAlgorithm(), $salt.$password) === $hashed;
return $result;
}
protected function getAlgorithm()
{
return $this->algorithm;
}
protected function getSaltLength()
{
return $this->saltLength;
}
/**
* @param $password
* @throws PasswordException
*/
public static function validate($password, $username = "")
{
if (mb_strlen($password) > self::MAX_PASSWORD_LIMIT || mb_strlen($password) < self::MIN_PASSWORD_LIMIT) {
throw new PasswordException("密码长度必须是8到20位");
}
$char_i = $char_a = $char_A = $char_t = array();
$last_char = "";
$list_char_3 = "";//连续三个字符
$chars =preg_split('/(?<!^)(?!$)/u', $password ); //也行是中文标点
foreach ($chars as $char) {
if ($last_char === $char && !in_array($last_char,$char_t)) {
throw new PasswordException("密码不能含有两个连续相同的数字或字母[$char]");
}
$list_char_3 .= $char;
$last_char = $char;
//判断三连
$allow_password_sample = ParamModel::getVal("allow_password_sample");
$password_model_status = ($allow_password_sample == 1)?
self::SAMPLE_MODEL_STATUS:self::STRICT_MODEL_STATUS;
if($password_model_status == self::STRICT_MODEL_STATUS) {
if (strlen($list_char_3) >= 3) {
$list_char_3 = substr($list_char_3, strlen($list_char_3) - 3, 3);
foreach (self::$str_continuities as $str_continuity) {
if (strpos($str_continuity, $list_char_3) !== false) {
throw new PasswordException("密码不能包括连续的3个字符键盘键位[$list_char_3]");
}
}
}
}
if (is_numeric($char)) {
$char_i[] = $char;
continue;
}
$str = ord($char);
if ($str > 64 && $str < 91) {
//大写字母
$char_A[] = $char;
continue;
}
if ($str > 96 && $str < 123) {
//小写字母
$char_a[] = $char;
continue;
}
//这里的特殊字符指
if(strpos(self::$allow_special_characters,$char) !== false){
$char_t[] = $char;
continue;
}
//其他一切字符
throw new PasswordException("密码含有系统不允许的特殊字符");
}
if (empty($char_i) || empty($char_a) || empty($char_A) || empty($char_t)) {
throw new PasswordException("密码必须同时含有大写、小写、数字和特殊字符");
}
//关键字
$forbidden_key = self::$forbidden_keywords;
if(!empty($username)) {
$forbidden_key = array_merge(self::$forbidden_keywords, (array)strtolower($username));
}
foreach ($forbidden_key as $keyword) {
if (strpos(strtolower($password), $keyword) !== false) {
throw new PasswordException("密码包含系统禁止的词汇或用户名");
}
}
return true;
}
}
class PasswordException extends Exception
{
}
验证代码片段 自行贴到 mvc框架控制器中:
public function vp()
{
//测试代码
$password_list = array(
"12dakedegss@#$", //含有123
"dhdgafe98Dsw<", //合法
"dedeRTdxbnvmaded",//没有数字
"Ygd%s", //长度不够
"你好YHDde092+",//非法字符
"123Yhd345de#s",//含有123
"GHJhj45%¥2sde",//三连GHJ
"a^&*()_dmin78E%nihao",//含有admin
"1Qw~!\_-’‘?、。,",
"1Qw@#$%^&*()[]{}",
"1Qw|:\"<>?,./;“”」",
"1Qw「【】·`《》¥…;:",
"Emicnet@?",
"Emicnet@π1",
"你好Emicnet1@",
"Pdmin1+891",
);
foreach ($password_list as $password) {
try {
HelperPassword::validate($password);
} catch (PasswordException $exception) {
echo "$password is failed:".$exception->getMessage()."<br/>";
continue;
}
echo $password."验证通过!"."<br/>";
}
页面的输出:
12dakedegss@#$ is failed:密码不能含有两个连续相同的字符[s]
dhdgafe98Dsw<验证通过!
dedeRTdxbnvmaded is failed:密码必须同时含有大写、小写、数字和特殊字符
Ygd%s is failed:密码长度必须是8到20位
你好YHDde092+ is failed:密码含有系统不允许的特殊字符
123Yhd345de#s is failed:密码不能包括连续的3个字符键盘键位
GHJhj45%¥2sde is failed:密码不能包括连续的3个字符键盘键位
a^&*()_dmin78E%nihao验证通过!
1Qw~!\_-’‘?、。,验证通过!
1Qw@#$%^&*()[]{}验证通过!
1Qw|:"<>?,./;“”」验证通过!
1Qw「【】·`《》¥…;:验证通过!
Emicnet@? is failed:密码含有系统不允许的特殊字符
Pdmin1@π1 is failed:密码含有系统不允许的特殊字符
你好Pdmin1@ is failed:密码含有系统不允许的特殊字符
Pdmin1+891验证通过!