PHP开发 之 摘要和签名

本文的示例代码参考digest_signature

目录

  • 哈希算法

    • 原理

    • 破解

    • 最佳实践password_hash

  • 消息验证码

    • 原理

    • 最佳实践hash_hmac

    • 破解

  • 数字签名

    • 原理

哈希算法

周所周知

绝不将用户的密码存储为明文

通常的做法就是使用哈希算法等单向加密算法存储密码的摘要

以使任何能够访问你的帐户数据库的人都无法发现你用户的密码

原理

哈希算法的过程很简单

输入 -> 哈希算法 -> 输出(哈希值)

但是它有如下几个非常鲜明的特点

  • 输出定长: 输入无论是普通密码还是大文件 输出长度都是固定的

  • 相同输入的输出相同: 因此哈希算法可以通过输出的摘要校验输入数据的完整性

  • 单向(one way): 即通过输出是无法得到输入的

哈希算法的实现有很多 常用的实现有

  • MD5: 摘要长度128bit

  • SHA-1: 摘要长度160bit

  • SHA-256: 摘要长度256bit

  • SHA-512: 摘要长度512bit

这里 以MD5为例来看下哈希算法的效果

vim md5.php
  • 测试
php md5.php
Would you like a green or red apple?

破解

虽然 破解难度随着摘要长度的增加而增大

但是 仍然无法防范彩虹表的攻击:

即 通过预先计算所有常见单词、短语、修改的单词或随机字符串的哈希值来匹配出密码

不过 想要防范此问题可以通过如下两种方法

  • 强制用户使用复杂密码 而非简单数字、单词等

  • 给输入加盐Salt 示例效果如下

vim salt.php
  • 测试
php salt.php
Incorrect login attempt!
Correct Password!

Tips: 为了进一步增加破解成本 还可以为不同的输入添加不同的Salt

最佳实践password_hash

上述哈希算法+Salt的方法已经接近"理想方案"了

不过PHP官方提供了一个更标准、更强大也具弹性的实现: password_hash

简单来说: password_hash = hash + salt + cost

vim password_hash.php
 14]);
echo $hash."\n";
  • 测试
php password_hash.php # $2y$14$GtRwvdNuHMo1uF/ynDxEbudaT5NEOyj3SiZl5TNxPiuSXdQ0IBt8y

php password_hash.php # $2y$14$uIcl9GdK6WG4LFr3mmsbZul.TFg9.Xvx0j5O8eW3B8nXjZRqrmUQe

php password_hash.php # $2y$14$rLgJU.5s235c3npMp5nitu00kZXlOLqTHJl4dMp4g7TAWeUnXtHZG

细心的读者 可能已经发现 对于相同输入password_hash每次输出的哈希值是变化的

原因很简单 password_hash每一次添加了不同的Salt

另外 之所以说password_hash函数具有很强的弹性 不仅仅是因为可以配置不同的哈希算法(第二个参数)

而且 还可以通过第三个参数配置生成hash值的成本: ['cost' => 14] 同样这也会增加破解的成本

消息验证码

消息验证码是什么? 它又有何作用呢?

消息验证码即message authentication code 简写成MAC

原理

概念很抽象 我们还是先来看一看生成消息验证码的过程

输入 + secret key -> 算法 -> 输出(MAC)

由于这里的算法通常使用的是哈希算法 所以MAC常常又被成为HMAC

输入 + secret key -> 哈希算法 -> 输出(HMAC)

那么 它到底有何作用呢? 这首先得从它的特点说起

  • 相同输入+相同secret key的输出相同: 两者任一不同则输出不同

  • shared secret key: 想要校验输入和输出 必须使用共享的同一个密钥

基于上述特点 不难看出

基于对称加密的HMAC可以用于数字签名

好吧 对称加密很好理解 因为用的是相同的secret key

那么 如何理解签名呢? 我们来看下面的例子

最佳实践hash_hmac

完整示例代码参考signedroute

composer create-project laravel/laravel signedroute
# cd signedroute
vim routes/web.php
 25, 'user' => 100, 'response' => 'yes']);
});

Route::get('event/{id}/rsvp/{user}/{response}', function ($id, $user, $response) {
    return 'id: '.$id.' user: '.$user.' response: '.$response;
})->name('event.rsvp')->middleware('signed');
  • 测试
php artisan serve
curl localhost:8000 # http://localhost:8000/event/25/rsvp/100/yes?signature=ec1b848899966625876fa3681b4ca2fe1222a50918d7b6a725b685448ff11f3a

这里 生成签名的源码如下

// vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php
class UrlGenerator implements UrlGeneratorContract
{
    public function signedRoute($name, $parameters = [], $expiration = null)
    {
        $parameters = $this->formatParameters($parameters);

        $key = call_user_func($this->keyResolver);

        return $this->route($name, $parameters + [
            'signature' => hash_hmac('sha256', $this->route($name, $parameters), $key),
        ]);
    }
}

其中

$this->route($name, $parameters)的值: "http://localhost:8000/event/25/rsvp/100/yes"

$key的值: .env中的APP_KEY=base64:MrXTUePq8uaCousYg0RCfSBcTkd+VexYZqAdttvsZ9g=

这样 修改URL中任一部分(域名, 接口, 参数, 签名)都会导致访问错误

curl http://localhost:8000/event/25/rsvp/100/yes?signature=ec1b848899966625876fa3681b4ca2fe1222a50918d7b6a725b685448ff11f3a
# id: 25 user: 100 response: yes

curl http://localhost:8000/event/250/rsvp/100/yes?signature=ec1b848899966625876fa3681b4ca2fe1222a50918d7b6a725b685448ff11f3a | grep "InvalidSignatureException: Invalid signature"
# Illuminate\Routing\Exceptions\InvalidSignatureException: Invalid signature. in file vendor/laravel/framework/src/Illuminate/Routing/Middleware/ValidateSignature.php on line 25

curl http://localhost:8000/event/25/rsvp/100/yes?signature=aa1b848899966625876fa3681b4ca2fe1222a50918d7b6a725b685448ff11f3a | grep "InvalidSignatureException: Invalid signature"
# Illuminate\Routing\Exceptions\InvalidSignatureException: Invalid signature. in file vendor/laravel/framework/src/Illuminate/Routing/Middleware/ValidateSignature.php on line 25

Tips: 想要设置签名有效期可以使用URL::temporarySignedRoute

破解

上述签名的方法 基于服务端签名后 服务端再验证签名

由于私钥只存在于服务本身 所以并不存在共享私钥带来的密钥配送问题

即 既然无法保证数据传输安全 那么也无法保证密钥传输安全 的密钥配送问题

为此 基于非对称加密算法的数字签名被广泛使用

数字签名

原理

基于非对称加密算法的原理如下

有一对公钥与私钥

公钥加密的信息 -> 只有匹配的私钥才能解密信息

私钥生成的签名 -> 只要匹配的公钥就能验证其签名

鉴于非对称加密的数字签名仍然是一个很大的话题

本文预留一个空间以后再单独深入讨论下去 敬请期待

参考

  • md5

  • password_hash

  • hash_hmac

  • 现代 PHP 中的密码安全性

  • Laravel 5.6 新功能 -- 路由签名

  • 最佳实践系列(六):PHP 开发者如何做好密码保护 & Laravel 底层密码存储和验证实现

你可能感兴趣的:(PHP开发 之 摘要和签名)