不败君

前端萌新&初级后端攻城狮

PHP实现虚位密码防偷窥

PHP实现虚位密码防偷窥

2020-07-13 18:16:00

围观(2065)

使用场景

先说一下什么是"虚位密码"

例如微信支付 / 支付宝支付 都有支付密码并且都是六位的长度.

输入密码的时候, 如果旁边的人盯着你输入密码, 密码就极其容易的泄露, 因为密码才六位非常容易记住.

此时虚位密码的作用就来了, 假设支付密码是 147258 , 开启虚位密码之后, 输入 265599465265265914725826526569556 也能验证密码正确, 原因就是这段密码中含有 147258, 而旁观者即使在旁边看了, 也不可能记得住你输入的密码了...


当然, 虚位密码防止了偷窥, 但是也存在一些安全隐患.

例如, 密码是六位的那么六位数字的排序方法无非就是 000000 到 999999, 假如恶意的从 000000 开始排序数字组合进行尝试验证密码, 到最后肯定会验证成功. 那么就需要加一个判断, 验证每次的密码长度及频繁请求, 例如每次密码长度不能大于 64 位, 一分钟最多输入 10 次. 


使用 PHP 实现

说一下博主想到的实现原理, 假设如果是交易系统, 设置密码长度都是六位, 那么设置密码(用户注册)的时候将密码进行 哈希 / MD5 / 其他加密 后存入数据库, 验证密码(用户登录)的时候将用户输入的密码进行偏移获取六位, 然后对比数据库存的密码(密码经过了加密, 如果经过了 MD5 则需要将偏移获取到的密码也经过加密后才对比).


博主使用 Laravel 框架简单写写代码实现了一下. 为了简单实现, 不写前端代码, 直接写 API 接口, 然后使用 PostMan 进行请求测试.


首先在 .ENV 文件配置数据库, 例如博主这样的配置(具体要根据自己环境及数据库设置而定):

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=xuwei
DB_USERNAME=root
DB_PASSWORD=root


在 API 的路由文件写下两个路由:

Route::post('register', 'Api\AuthController@register');
Route::post('login', 'Api\AuthController@login');


创建 users 表:

CREATE TABLE `users` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8_unicode_ci',
    `password` VARCHAR(128) NOT NULL DEFAULT '0' COLLATE 'utf8_unicode_ci',
    `length` INT(11) NOT NULL DEFAULT '0',
    `created_at` TIMESTAMP NULL DEFAULT NULL,
    `updated_at` TIMESTAMP NULL DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE
)
COMMENT='用户'
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;


使用命令创建模型:

php artisan make:model Model/User

如果使用 Laravel 自带的用户系统, 则可以直接使用 APP 目录下的 User , 博主是为了方便简单实现, 不使用 Laravel 自带的.


使用命令创建控制器:

php artisan make:controller Api/AuthController


在控制器里面写入两个方法:

public function register(Request $request)
{
    $input = $request->all();
    $validator = Validator::make($input, [
        'name' => 'required|unique:users|max:50',
        'password' => 'required|min:6|max:128',
    ]);

    if ($validator->fails()) {
        return response()->json(['msg' => $validator->errors()->first()]);
    }

    $model_user = new User();
    $model_user->name = $input['name'];
    $model_user->password = Hash::make($input['password']);
    $model_user->length = mb_strlen($input['password']);
    $model_user->save();

    return response()->json(['msg' => 'success']);
}

public function login(Request $request)
{
    $input = $request->all();
    $validator = Validator::make($input, [
        'name' => 'required|max:50',
        'password' => 'required|min:6|max:128',
    ]);

    if ($validator->fails()) {
        return response()->json(['msg' => $validator->errors()->first()]);
    }

    $model_user = new User();
    // 博主为了偷懒(快速实现) 直接在这查询 正常应该是写入模型 为后面复用
    $row_user = $model_user->where('name', $input['name'])->first();

    $password_length = mb_strlen($input['password']);

    if (!$row_user || $password_length < $row_user['length']) {
        return response()->json(['msg' => 'account or password does not exist']);
    }

    if ($password_length == $row_user['length']) {
        if (!Hash::check($input['password'], $row_user['password'])) {
            return response()->json(['msg' => 'account or password does not exist']);
        }

        // 登录成功 继续操作 这里正常是返回 token 博主就不继续写了...
        // code...

        return response()->json(['msg' => 'login success']);
    }

    $offset = 0;
    $offset_times = $password_length - $row_user['length'] + 1;

    for ($i = 0; $i < $offset_times; $i++) {
        $password = mb_substr($input['password'], $offset, $row_user['length']);
        $offset++;

        if (Hash::check($password, $row_user['password'])) {

            // 登录成功 继续操作 这里正常是返回 token 博主就不继续写了...
            // code...

            return response()->json(['msg' => 'login success']);
        }
    }

    return response()->json(['msg' => 'account or password does not exist']);
}


别忘了需要引入:

use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;

use App\Model\User;  // 这个应该不用多说了


接下来测试下注册:

1.png

注册是成功的, 这里说一下, 注册方法里面计算了一下密码长度, 这是为了登录(验证密码)的时候可以偏移组合密码, 不然不知道密码真实的长度就没办法做虚位了, 除非定死每个人的密码只能设置为 XX 位.


测试正常登录:

2.png


测试虚位密码登录:

3.png


为了验证是否正确验证(将虚位密码中的 147258 去掉):

4.png


至此, 虚位密码已经成功实现.

博主限制的长度是 128 位, 其实可以减少到 64 位, 另外博主的实现方法没限制请求频率, 如果使用 Laravel 可以通过中间件实现限制.

本文地址 : bubaijun.com/page.php?id=196

版权声明 : 未经允许禁止转载!

评论:我要评论

不败君 沙发

如果使用明文存储,也可以使用 mb_strpos 函数,或者查询数据库的时候用 where password like 查询,但是不建议存储明文。

评论时间:2021-06-05 12:37:59

回复

发布评论:
Copyright © 不败君 粤ICP备18102917号-1

不败君

首 页 作 品 微 语