安全¶
概览¶
注意
需要 PHP 的openssl扩展存在于系统中
Phalcon\Encryption\Security是一个帮助开发者处理常见安全相关任务(如密码哈希和跨站请求伪造保护)的组件CSRF)。
注意
默认情况下,该组件将使用password_hash
来对字符串进行哈希处理,默认算法为Phalcon\Encrtyption\Security::CRYPT_DEFAULT
,对应于 PHP 的Phalcon\Encryption\Security::CRYPT_BCRYPT
and corresponds to PHP's PASSWORD_BCRYPT
.
密码哈希¶
将密码以明文形式存储是一种不良的安全实践。任何有权访问数据库的人都可以立即访问所有用户帐户,并能够从事未经授权的活动。为了防止这种情况,许多应用程序使用流行的单向哈希方法md5和sha1。然而,硬件每天都在发展,随着处理器变得更快,这些算法正变得容易受到暴力破解攻击。这些攻击也被称为彩虹表.
该安全组件使用bcrypt作为哈希算法。由于Eksblowfish密钥设置算法,我们可以使密码加密过程尽可能slow
慢。缓慢的算法可以最小化暴力破解攻击的影响。
Bcrypt是基于 Blowfish 对称分组密码加密算法的一种自适应哈希函数。它还引入了一个安全或工作因子,这决定了哈希函数生成哈希的速度。这有效地阻止了 FPGA 或 GPU 哈希技术的使用。
如果未来硬件变得更强大,我们可以增加工作因子来缓解这一问题。盐值是通过 PHP 的函数openssl_random_pseudo_bytes.
生成的伪随机字节生成的。该组件提供了一个简单的接口来使用该算法:
<?php
use Phalcon\Encryption\Security;
$security = new Security();
echo $security->hash('Phalcon');
// $2y$08$ZUFGUUk5c3VpcHFoVUFXeOYoA4NPFEP4G9gcm6rdo3jFPaNFdR2/O
现在我们可以检查用户通过应用程序的 UI 发送给我们的值是否与我们的哈希字符串相同:
<?php
use Phalcon\Encryption\Security;
$password = $_POST['password'] ?? '';
$security = new Security();
$hashed = $security->hash('Phalcon');
echo $security->checkHash($password, $hashed); // true / false
上述示例只是展示了如何使用checkHash()
。在生产应用中,我们绝对需要对输入进行清理,并且我们需要将哈希后的密码存储在一个数据存储中,例如数据库。通过控制器,上述示例可以表示为:
<?php
use MyApp\Models\Users;
use Phalcon\Http\Request;
use Phalcon\Mvc\Controller;
use Phalcon\Encryption\Security;
/**
* @property Request $request
* @property Security $security
*/
class SessionController extends Controller
{
public function loginAction()
{
$login = $this->request->getPost('login');
$password = $this->request->getPost('password');
$user = Users::findFirst(
[
'conditions' => 'login = :login:',
'bind' => [
'login' => $login,
],
]
);
if (false !== $user) {
$check = $this
->security
->checkHash($password, $user->password);
if (true === $check) {
// OK
}
} else {
$this->security->hash(rand());
}
// ERROR
}
public function registerAction()
{
$login = $this->request->getPost('login', 'string');
$password = $this->request->getPost('password', 'string');
$user = new Users();
$user->login = $login;
$user->password = $this->security->hash($password);
$user->save();
}
}
注意
上面的代码片段不完整,并且不应直接用于生产应用
The registerAction()
接受来自 UI 的提交数据。它通过string
过滤器对其进行清理,然后创建一个新的User
模型对象。然后它将传递的数据分配给相关的属性并保存它们。请注意,对于密码,我们使用hash()
方法来评估提供的密码哈希是否与数据库中存储的一致。Phalcon\Encryption\Security组件的方法,以免将其以明文形式存储在我们的数据库中。
The loginAction()
接受来自 UI 的提交数据,然后尝试根据login
字段在数据库中查找用户。如果用户存在,它将使用checkHash()
方法来评估提供的密码哈希是否与数据库中存储的一致。Phalcon\Encryption\Security组件的
注意
在使用checkHash()
时,无需对提供的密码(第一个参数)进行哈希处理——组件会为您完成此操作。
如果密码不正确,您可以告知用户凭据有问题。始终不要向想要入侵您站点的人提供有关用户的特定信息是一个好主意。例如,我们上面的例子可以产生两条消息:
- 用户未在数据库中找到
- 密码不正确
将错误消息分开不是一个好主意。如果黑客使用暴力破解攻击检测到第二条消息,他们可以停止猜测login
并集中精力破解密码,从而增加了他们获得访问权限的机会。更合适的适用于两种潜在错误条件的消息可能是
Invalid Login/Password combination
最后,您会在示例中注意到,当用户未找到时,我们调用:
这是为了防止时间攻击。无论用户是否存在,脚本执行所需的时间大致相同,因为它即使我们在这种情况下永远不会使用该结果,也会再次计算哈希值。
工作因子¶
工作因子也是我们所说的cost
。它是传递到crypt()
方法中以对字符串进行哈希处理的数字。工作因子可以是4
和31
之间的任何数字。数字越高,算法越慢。
可以通过setWorkFactor()
方法设置工作因子,也可以作为第二个参数的元素之一传递到hash()
方法设置容器。
<?php
use Phalcon\Encryption\Security;
$password = 'password1';
$security = new Security();
$hashed = $security->hash('Phalcon', ['cost' => 31]);
echo $security->checkHash($password, $hashed); // true / false
The workFactor
(或cost
),在以下情况下使用:- 我们正在使用旧版哈希(即不使用password_hash
方法)并且特别是Phalcon\Encryption\Security::CRYPT_BLOWFISH_A
或Phalcon\Encryption\Security::CRYPT_BLOWFISH_X
。- 我们正在使用非旧版哈希(即使用password_hash
)并带有Phalcon\Encryption\Security::CRYPT_DEFAULT
或Phalcon\Encryption\Security::CRYPT_BCRYPT
算法。
Argon2i¶
Phalcon\Encryption\Security
还支持新的Argon2i哈希算法。该算法是密码哈希竞赛的赢家,被认为是哈希密码的最佳算法。这也是 PHP 的password_hash()
方法设置容器。
<?php
use Phalcon\Encryption\Security;
$password = 'password1';
$security = new Security();
$security->setDefaultHash(Security::CRYPT_ARGON2I);
$hashed = $security->hash(
'Phalcon',
[
'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST,
'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST,
'threads' => PASSWORD_ARGON2_DEFAULT_THREADS,
]
);
echo $security->checkHash($password, $hashed); // true / false
默认使用的算法。
异常¶
如果没有设置选项,将使用默认值。安全组件中抛出的任何异常类型均为。您可以使用此异常仅捕获来自此组件的异常。如果哈希算法未知、session
服务未存在于 Di 容器中等,可能会引发异常。
<?php
use Phalcon\Encryption\Security\Exception;
use Phalcon\Mvc\Controller;
class IndexController extends Controller
{
public function index()
{
try {
$this->security->hash('123');
} catch (Exception $ex) {
echo $ex->getMessage();
}
}
}
CSRF 保护¶
跨站请求伪造 (CSRF) 是针对网站和应用程序的另一种常见攻击。设计用于执行任务(例如用户注册或添加评论)的表单容易受到此类攻击。
这个想法是为了防止表单值被发送到我们的应用程序之外。为了修复这个问题,我们在每个表单中生成一个随机数(令牌),将其添加到会话中,并在表单将数据发布回我们的应用程序时通过比较存储在会话中的令牌和表单提交的令牌来验证令牌。随机数(令牌)在每个表单中生成,将其添加到会话中,然后在表单将数据发布回我们的应用程序时通过比较存储在会话中的令牌和表单提交的令牌来验证令牌:
<form method='post' action='session/login'>
<!-- Login and password inputs ... -->
<input type='hidden'
name='<?php echo $this->security->getTokenKey() ?>'
value='<?php echo $this->security->getToken() ?>'/>
</form>
然后在控制器的操作中,你可以检查CSRF令牌是否有效:
<?php
use Phalcon\Mvc\Controller;
/**
* @property Request $request
* @property Security $security
*/
class SessionController extends Controller
{
public function loginAction()
{
if ($this->request->isPost()) {
if ($this->security->checkToken()) {
// OK
}
}
}
}
注意
重要的是要记住,你需要在依赖注入容器中注册一个有效的session
服务。否则,checkToken()
将无法工作。
添加一个验证码到表单中也推荐完全避免此攻击的风险。
功能¶
哈希¶
getDefaultHash() / setDefaultHash()
获取和设置组件将使用的默认哈希值的方法。默认情况下,哈希值设置为CRYPT_DEFAULT
(0
)。可用选项包括:
CRYPT_BLOWFISH_A
CRYPT_BLOWFISH_X
CRYPT_BLOWFISH_Y
CRYPT_MD5
CRYPT_SHA256
CRYPT_SHA512
CRYPT_DEFAULT
hash()
对字符串或密码进行哈希处理并返回哈希后的字符串。第二个参数是可选的,允许你暂时设置特定的workFactor
或者传递覆盖默认值的内容。
checkHash()
接受一个字符串(通常是密码)、一个已经被哈希处理的字符串(哈希后的密码)以及一个可选的最小密码长度。它检查两者并返回true
如果它们相同,则返回false
。
isLegacyHash()
返回true
如果传递的哈希字符串是一个有效的bcrypt哈希。
HMAC¶
computeHmac()
使用HMAC方法生成带有密钥的哈希值。它内部使用PHP的hash_hmac方法,因此它接受的所有参数都与hash_hmac.
随机¶
getRandom()
返回一个Phalcon\Encryption\Security\Random对象,这是一个安全的随机数生成器实例。该组件将在下面详细解释。
getRandomBytes()
/ setRandomBytes()
获取和设置方法以指定openssl伪随机生成器生成的字节数。默认值为16
.
getSaltBytes()
生成一个伪随机字符串,用作密码的盐。它使用getRandomBytes()
的值作为字符串的长度。但是,可以通过传递的数字参数覆盖。
令牌¶
getToken()
生成一个伪随机令牌值,在CSRF检查中用作输入值。
getTokenKey()
生成一个伪随机令牌键,在CSRF检查中用作输入名称。
getRequestToken()
返回当前请求的CSRF令牌的值。
checkToken()
检查请求中发送的CSRF令牌是否与会话中的当前令牌相同。第一个参数是令牌键,第二个参数是令牌值。它还接受第三个布尔参数destroyIfValid
如果设置为true
如果该方法返回true
.
getSessionToken()
返回会话中CSRF令牌的值
destroyToken()
从会话中删除CSRF令牌和键的值
随机¶
The Phalcon\Encryption\Security\Random类使得生成各种类型的随机数据变得非常容易,这些数据可以用于盐、新用户密码、会话密钥、复杂密钥、加密系统等。这个类部分借用了Ruby的SecureRandom库。
它支持以下安全随机数生成器: *random_bytes
* libsodium
* openssl
, libressl
* /dev/urandom
要利用上述内容,你需要确保这些生成器在你的系统中可用。例如,要使用openssl
,你的PHP安装需要支持它。
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->hex(10); // a29f470508d5ccb8e289
echo $random->base62(); // z0RkwHfh8ErDM1xw
echo $random->base64(16); // SvdhPcIHDZFad838Bb0Swg==
echo $random->base64Safe(); // PcV6jGbJ6vfVw7hfKIFDGA
echo $random->uuid(); // db082997-2572-4e2c-a046-5eefe97b1235
echo $random->number(256); // 84
echo $random->base58(); // 4kUgL2pdQMSCQtjE
base58()
生成一个随机base58
字符串。如果未指定$len
参数,16
被假定。将来可能会更大。结果可能包含除了0
(零),O
(大写o
),I
(大写i
), 和l
(小写L
)。
它类似于base64()
但已修改以避免非字母数字字符和打印时可能看起来模糊的字母。
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->base58(); // 4kUgL2pdQMSCQtjE
base62()
生成一个随机base62
字符串。如果未指定$len
参数,16
被假定。将来可能会更大。它类似于base58()
但已修改以提供可以在URL中安全使用的最大值,而不需要考虑额外字符,因为它是[A-Za-z0-9]
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->base62(); // z0RkwHfh8ErDM1xw
base64()
生成一个随机base64
字符串。如果未指定$len
参数,16
被假定。将来可能会更大。结果字符串的长度通常大于$len
。大小公式为:
4 * ($len / 3)
四舍五入为4的倍数。
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->base64(12); // 3rcq39QzGK9fUqh8
base64Safe()
生成一个URL安全的随机base64
字符串。如果未指定$len
参数,16
被假定。将来可能会更大。结果字符串的长度通常大于$len
.
默认情况下,不生成填充,因为=
可能用作URL分隔符。结果可能包含A-Z
, a-z
, 0-9
, -
和_
. =
如果$padding
是true
。见RFC 3548关于URL安全的定义base64
.
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->base64Safe(); // GD8JojhzSTrqX7Q8J6uug
bytes()
生成一个随机二进制字符串,并接受一个表示要返回的字节长度的整数作为输入。如果未指定$len
,则假定为16
。将来可能会更大。结果可能包含任何字节:x00
- xFF
.
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
$bytes = $random->bytes();
var_dump(bin2hex($bytes));
// Possible output: string(32) "00f6c04b144b41fad6a59111c126e1ee"
hex()
生成一个随机十六进制字符串。如果未指定$len
,则假定为16。将来可能会更大。结果字符串的长度通常大于$len
.
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->hex(10); // a29f470508d5ccb8e289
number()
生成一个介于0
和$len
的随机数。返回一个整数:0 <= result <= $len
.
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->number(16); // 8
uuid()
生成一个v4随机UUID(通用唯一标识符)。版本4的UUID完全是随机的(除了版本号)。它不包含有意义的信息,如MAC地址、时间等。详见RFC 4122UUID的详细信息。
该算法设置了版本号(4位)以及两个保留位。其余所有位(剩余122位)使用随机或伪随机数据源设置。版本4 UUID的形式为xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
其中x是任何十六进制数字,而y
是其中之一8
, 9
, A
或B
(例如,f47ac10b-58cc-4372-a567-0e02b2c3d479
)。
<?php
use Phalcon\Encryption\Security\Random;
$random = new Random();
echo $random->uuid(); // 1378c906-64bb-4f81-a8d6-4ae1bfcdec22
依赖注入¶
如果您使用Phalcon\Di\FactoryDefault容器,Phalcon\Encryption\Security已经为你注册好了。然而,你可能需要覆盖默认注册以便设置你自己的workFactor()
。或者,如果你没有使用Phalcon\Di\FactoryDefault并且改为使用了Phalcon\Di\Di注册方法是一样的。这样做之后,你将能够从控制器、模型、视图以及任何实现了该接口的组件中访问你的配置对象。Injectable
.
注册。下面是一个注册服务以及访问它的示例:
<?php
use Phalcon\Di\FactoryDefault;
use Phalcon\Encryption\Security;
// Create a container
$container = new FactoryDefault();
$container->set(
'security',
function () {
$security = new Security();
$security->setWorkFactor(12);
return $security;
},
true
);
setWorkFactor()
设置密码哈希因子为12轮。 该组件现在可以通过security
键名在你的控制器中使用
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Encryption\Security;
/**
* @property Security $security
*/
class MyController extends Controller
{
private function getHash(string $password): string
{
return $this->security->hash($password);
}
}
同样在您的视图中(Volt语法)