### PHP 中的加密功能(1)

数据加密在我们生活中的地位已经越来越重要了，尤其是考虑到在网络上发生的大量交易和传输的大量数据。如果对于采用安全措施有兴趣的话，也一定会有兴趣了解 PHP 提供的一系列安全功能。在本篇文章中，我们将介绍这些功能，提供一些基本的用法，以便你能够为自己的应用软件中增加安全功能。
预备知识
在详细介绍 PHP 的安全功能之前，我们需要花点时间来向没有接触过这方面内容的读者介绍一些有关密码学的基本知识，如果对密码学的基本概念已经非常熟悉，就可以跳过去这一部分。
密码学可以通俗地被描述为对加/解密的研究和实验，加密是将易懂的资料转换为不易懂资料的过程，解密则是将不易懂的资料转换为原来易懂资料的过程。不易懂的资料被称作密码，易懂的资料被称作明码。
数据的加/解密都需要一定的算法，这些算法可以非常地简单，如著名的凯撒码，但当前的加密算法要相对复杂得多，其中一些利用现有的方法甚至是无法破译的。
PHP 的加密功能
只要有一点使用非 Windows 平台经验的人可能对 crypt()也相当熟悉，这一函数完成被称作单向加密的功能，它可以加密一些明码，但不能够将密码转换为原来的明码。尽管从表面上来看这似乎是一个没有什么用处的功能，但它的确被广泛用来保证系统密码的完整性。因为，单向加密的口令一旦落入第三方人的手里，由于不能被还原为明文，因此也没有什么大用处。在验证用户输入的口令时，用户的输入采用的也是单向算法，如果输入与存储的经加密后的口令相匹配，则输入的口信一定是正确的。
PHP 同样提供了使用其 crypt()函数完成单向加密功能的可能性。我将在这里简要地介绍该函数：
string crypt (string input_string [, string salt])
其中的 input_string 参数是需要加密的字符串，第二个可选的 salt 是一个位字串，它能够影响加密的暗码，进一步地排除被称作预计算攻击的可能性。缺省情况下，PHP 使用一个 2 个字符的 DES 干扰串，如果你的系统使用的是 MD5（我将在以后介绍 MD5 算法），它会使用一个 12 个字符的干扰串。顺便说一下，可以通过执行下面的命令发现系统将要使用的干扰串的长度：
print "My system salt size is: ". CRYPT_SALT_LENGTH;
系统也可能支持其他的加密算法。crypt()支持四种算法，下面是它支持的算法和相应的 salt 参数的长度：
算法 Salt 长度
CRYPT_STD_DES 2-character (Default)
CRYPT_EXT_DES 9-character
CRYPT_MD5 12-character beginning with $1$
CRYPT_BLOWFISH 16-character beginning with $2$
用 crypt()实现用户身份验证
作为 crypt()函数的一个例子，考虑这样一种情况，你希望创建一段 PHP 脚本程序限制对一个目录的访问，只允许能够提供正确的用户名和口令的用户访问这一目录。我将把资料存储在我喜欢的数据库 MySQL 的一个表中。下面我们以创建这个被称作 members 的表开始我们的例子：
mysql>CREATE TABLE members (
->username CHAR(14) NOT NULL,
->password CHAR(32) NOT NULL,
->PRIMARY KEY(username)
->);
然后，我们假定下面的数据已经存储在该表中：
用户名 密码
clark keloD1C377lKE
bruce ba1T7vnz9AWgk
peter paLUvRWsRLZ4U
这些加密的口令对应的明码分别是 kent、banner 和 parker。注意一下每个口令的前二个字母，这是因为我使用了下面的代码，根据口令的前二个字母创建干扰串的：
$enteredPassword.
$salt = substr($enteredPassword, 0, 2);
$userPswd = crypt($enteredPassword, $salt);
// $userPswd 然后就和用户名一起存储在 MySQL 中
我将使用 Apache 的口令-应答认证配置提示用户输入用户名和口令，一个鲜为人知的有关 PHP 的信息是，它可以把 Apache 的口令-应答系统输入的用户名和口令识别为$PHP_AUTH_USER 和$PHP_AUTH_PW，我将在身份验证脚本中用到这二个变量。花一些时间仔细阅读下面的脚本，多注意一下其中的解释，以便更好地理解下面的代码：
crypt()和 Apache 的口令-应答验证系统的应用
<?php
$host = "localhost";
$user = "zorro";
$pswd = "hellodolly";
$db = "users";
// Set authorization to False
$authorization = 0;
// Verify that user has entered username and password
if (isset($PHP_AUTH_USER) && isset($PHP_AUTH_PW)) :
mysql_pconnect($host, $user, $pswd) or die("Can't connect to MySQL
server!");
mysql_select_db($db) or die("Can't select database!");
// Perform the encryption
$salt = substr($PHP_AUTH_PW, 0, 2);
$encrypted_pswd = crypt($PHP_AUTH_PW, $salt);
// Build the query
$query = "SELECT username FROM members WHERE
username = '$PHP_AUTH_USER' AND
password = '$encrypted_pswd'";
// Execute the query
if (mysql_numrows(mysql_query($query)) == 1) :
$authorization = 1;
endif;
endif;
// confirm authorization
if (! $authorization) :
header('WWW-Authenticate: Basic realm="Private"');
header('HTTP/1.0 401 Unauthorized');
print "You are unauthorized to enter this area.";
exit;
else :
print "This is the secret data!";
endif;
?>
上面就是一个核实用户访问权限的简单身份验证系统。在使用 crypt()保护重要的机密资料时，记住在缺省状态下使用的 crypt()并不是最安全的，只能用在对安全性要求较低的系统中，如果需要较高的安全性能，就需要我在本篇文章的后面介绍的算法。
下面我将介绍另一个 PHP 支持的函数━━md5()，这一函数使用 MD5 散列算法，它有几种很有趣的用法值得一提：
混编
一个混编函数可以将一个可变长度的信息变换为具有固定长度被混编过的输出，也被称作“信息文摘”。这是十分有用的，因为一个固定长度的字符串可以用来检查文件的完整性和验证数字签名以及用户身份验证。由于它适合于 PHP，PHP 内置的 md5()混编函数将把一个可变长度的信息转换为 128 位（32 个字符）的信息文摘。混编的一个有趣的特点是不能通过分析混编后的信息得到原来的明码，因为混编后的结果与原来的明码内容没有依赖关系。 即便只改变一个字符串中的一个字符，也将使得 MD5 混编算法计算出二个截然不同的结果。我们首先来看下表的内容及其相应的结果：
使用 md5()混编字符串
<?php
$msg = "This is some message that I just wrote";
$enc_msg = md5($msg);
print "hash: $enc_msg ";
?>
结果：
hash: 81ea092649ca32b5ba375e81d8f4972c
注意，结果的长度为 32 个字符。再来看一下下面的表，其中的$msg 的值有了一点微小的变化：
使用 md5()对一个稍微变化的字符串进行混编
<?php
//注意，message 中少了一个 s
$msg = "This is some mesage that I just wrote";
$enc_msg = md5($msg);
print "hash2: $enc_msg <br /><br />";
?>