本文的示例代码参考digest_signature
目录
哈希算法
周所周知
绝不将用户的密码存储为明文
通常的做法就是使用哈希算法等单向加密算法存储密码的摘要
以使任何能够访问你的帐户数据库的人都无法发现你用户的密码
原理
哈希算法的过程很简单
输入 -> 哈希算法 -> 输出(哈希值)
但是它有如下几个非常鲜明的特点
-
输出定长: 输入无论是普通密码还是大文件 输出长度都是固定的
-
相同输入的输出相同: 因此哈希算法可以通过输出的摘要校验输入数据的完整性
-
单向(one way): 即通过输出是无法得到输入的
哈希算法的实现有很多 常用的实现有
-
MD5: 摘要长度128bit
-
SHA-1: 摘要长度160bit
-
SHA-256: 摘要长度256bit
-
SHA-512: 摘要长度512bit
这里 以MD5为例来看下哈希算法的效果
vim md5.php
<?php
$str = 'apple';
if (md5($str) === '1f3870be274f6c49b3e31a0c6728957f') {
echo "Would you like a green or red apple?";
}
- 测试
php md5.php
Would you like a green or red apple?
破解
虽然 破解难度随着摘要长度的增加而增大
但是 仍然无法防范彩虹表的攻击:
即 通过预先计算所有常见单词、短语、修改的单词或随机字符串的哈希值来匹配出密码
不过 想要防范此问题可以通过如下两种方法
-
强制用户使用复杂密码 而非简单数字、单词等
-
给输入加盐Salt 示例效果如下
vim salt.php
<?php
class Password {
const SALT = 'MyVoiceIsMyPassport';
public static function hash($password) {
return hash('sha512', self::SALT . $password);
}
public static function verify($password, $hash) {
return ($hash == self::hash($password));
}
}
$hash = Password::hash('password1');
if (Password::verify('password2', $hash)) {
echo "Correct Password!\n";
} else {
echo "Incorrect login attempt!\n";
}
if (Password::verify('password1', $hash)) {
echo "Correct Password!\n";
} else {
echo "Incorrect login attempt!\n";
}
- 测试
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
<?php
$hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT, ['cost' => 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
<?php
Route::get('/', function () {
return URL::signedRoute('event.rsvp', ['id' => 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
破解
上述签名的方法 基于服务端签名后 服务端再验证签名
由于私钥只存在于服务本身 所以并不存在共享私钥带来的密钥配送问题
即 既然无法保证数据传输安全 那么也无法保证密钥传输安全 的密钥配送问题
为此 基于非对称加密算法的数字签名被广泛使用
数字签名
原理
基于非对称加密算法的原理如下
有一对公钥与私钥
公钥加密的信息 -> 只有匹配的私钥才能解密信息
私钥生成的签名 -> 只要匹配的公钥就能验证其签名
鉴于非对称加密的数字签名仍然是一个很大的话题
本文预留一个空间以后再单独深入讨论下去 敬请期待
网友评论