<table width="100%" border="0" cellspacing="0" cellpadding="5" bgcolor="#649CCC"><tr valign="middle"><td align="left"> <p class="p_Heading1"><span class="f_Heading1">附录C. 加密</span></p> </td> <td align="right"> <span style="font-size: 9pt"> <a href="introduction.htm">Top</a> <a href="new_item54.htm">Previous</a> </span> </td> </tr></table>
<table width="100%" border="0" cellspacing="0" cellpadding="5"><tr valign="top"><td align="left"><p style="line-height: 1.50;">附录C. 加密</p><p style="line-height: 1.50;"> 作为一本相关安全方面的书,通常加密是需要提及的话题。我之所以在本书的主体部分忽略了加密问题,是因为它的用途是狭窄的,而开发者应从大处着眼来考虑安全问题。过分依赖于加密常常会混淆问题的根源。尽管加密本身是有效的,但是进行加密并不会神奇地提高一个应用的安全性。</p><p style="line-height: 1.50;"> 一个PHP开发人员应主要熟悉以下的加密方式:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;">l 对称加密</p><p style="line-height: 1.50;">l 非对称加密(公钥)</p><p style="line-height: 1.50;">l Hash函数(信息摘要)</p><p style="line-height: 1.50;">l 信息验证码</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 本附录主要关注于使用mcrypt扩展的对称加密算法。你需要参考的资料如下:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 实用加密技术(Applied Cryptography), by Bruce Schneier (Wiley)</p><p style="line-height: 1.50;">http://www.schneier.com/blog/</p><p style="line-height: 1.50;">http://wikipedia.org/wiki/Cryptography</p><p style="line-height: 1.50;">http://phpsec.org/articles/2005/password-hashing.html</p><p style="line-height: 1.50;">http://pear.php.net/package/Crypt_HMAC</p><p style="line-height: 1.50;">http://pear.php.net/package/Crypt_RSA</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;">C.1. 密码的存储</p><p style="line-height: 1.50;"> 当你在数据库内存储的密码时,永远不要以明码方式存入,而是应该存储密码的hash值并同时使用附加字串:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> /* $password contains the password. */</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $salt = 'SHIFLETT';</p><p style="line-height: 1.50;"> $password_hash = md5($salt . md5($password . $salt));</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> /* Store password hash. */</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 当你需要确认一个密码是否正确时,以同样的方式计算出hash值并比较异同:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $salt = 'SHIFLETT';</p><p style="line-height: 1.50;"> $password_hash = md5($salt . md5($_POST['password'] . $salt));</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> /* Compare password hashes. */</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 如果hash值完全相同,你就有理由认为密码也是相同的。</p><p style="line-height: 1.50;"> 如果使用了这个技巧,是不可能告诉用户他们的密码是什么的。当用户忘记密码时,你只能让他录入一个新密码并重新计算hash值存入数据库。当然,你需要非常小心地对用户进行身份确认——密码提醒机制是易受频繁攻击的目标,同时也是经常出现安全漏洞的源头。</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;">C.2. 使用mcrypt</p><p style="line-height: 1.50;"> PHP的标准加密扩展是mcrypt,它支持很多不同的加密算法。你可以通过mcrypt_list_algorithms( )函数来查看你的平台上支持的算法列表:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> echo '<pre>' . print_r(mcrypt_list_algorithms(), TRUE) . '</pre>';</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 加密和解密分别由mcrypt_encrypt( ) 及 mcrypt_decrypt( )函数来实现。这两个函数都有5个参数,第一个参数是用于指定使用的算法:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> mcrypt_encrypt($algorithm,</p><p style="line-height: 1.50;"> $key,</p><p style="line-height: 1.50;"> $cleartext,</p><p style="line-height: 1.50;"> $mode,</p><p style="line-height: 1.50;"> $iv);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> mcrypt_decrypt($algorithm,</p><p style="line-height: 1.50;"> $key,</p><p style="line-height: 1.50;"> $ciphertext,</p><p style="line-height: 1.50;"> $mode,</p><p style="line-height: 1.50;"> $iv);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 其中的加密键(第二个参数)是非常敏感的数据,因此你要确保把它存放在安全的地方。可以用第八章中保护数据库权限的方法来保护加密键。如果经济条件允许的话,硬件加密键是最好的选择,它提供了超级强大的安全性。</p><p style="line-height: 1.50;"> 函数有多种模式可供选择,你可以使用mcrypt_list_modes( )来列出所有支持的模式:</p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> echo '<pre>' . print_r(mcrypt_list_modes(), TRUE) . '</pre>';</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 第五个参数($iv)为初始化向量,可以使用mcrypt_create_iv( )函数建立。</p><p style="line-height: 1.50;"> 下面的示例类提供了基本的加密解密方法:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> class crypt</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> private $algorithm;</p><p style="line-height: 1.50;"> private $mode;</p><p style="line-height: 1.50;"> private $random_source;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> public $cleartext;</p><p style="line-height: 1.50;"> public $ciphertext;</p><p style="line-height: 1.50;"> public $iv;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> public function __construct($algorithm = MCRYPT_BLOWFISH,</p><p style="line-height: 1.50;"> $mode = MCRYPT_MODE_CBC,</p><p style="line-height: 1.50;"> $random_source = MCRYPT_DEV_URANDOM)</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> $this->algorithm = $algorithm;</p><p style="line-height: 1.50;"> $this->mode = $mode;</p><p style="line-height: 1.50;"> $this->random_source = $random_source;</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> public function generate_iv()</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> $this->iv = mcrypt_create_iv(mcrypt_get_iv_size($this->algorithm,</p><p style="line-height: 1.50;"> $this->mode), $this->random_source);</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> public function encrypt()</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> $this->ciphertext = mcrypt_encrypt($this->algorithm,</p><p style="line-height: 1.50;"> $_SERVER['CRYPT_KEY'], $this->cleartext, $this->mode, $this->iv);</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> public function decrypt()</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> $this->cleartext = mcrypt_decrypt($this->algorithm,</p><p style="line-height: 1.50;"> $_SERVER['CRYPT_KEY'], $this->ciphertext, $this->mode, $this->iv);</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 上面的类会在其它示例中使用,下面是它的使用方法示例:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt = new crypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt->cleartext = 'This is a string';</p><p style="line-height: 1.50;"> $crypt->generate_iv();</p><p style="line-height: 1.50;"> $crypt->encrypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $ciphertext = base64_encode($crypt->ciphertext);</p><p style="line-height: 1.50;"> $iv = base64_encode($crypt->iv);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> unset($crypt);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> /* Store $ciphertext and $iv (initialization vector). */</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $ciphertext = base64_decode($ciphertext);</p><p style="line-height: 1.50;"> $iv = base64_decode($iv);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt = new crypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt->iv = $iv;</p><p style="line-height: 1.50;"> $crypt->ciphertext = $ciphertext;</p><p style="line-height: 1.50;"> $crypt->decrypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $cleartext = $crypt->cleartext;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;">小提示</p><p style="line-height: 1.50;"> 本扩展要求你在编译PHP时使用-mcrypt标识。安装指南及要求详见http://php.net/mcrypt。</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;">C.3. 信用卡号的保存</p><p style="line-height: 1.50;"> 我常常被问到如何安全地保存信用卡号。我的总是会首先询问他们是否确实有必要保存信用卡号。毕竟不管具体是如何操作的,引入不必要的风险是不明智的。同时国家法律还有关于信用卡信息处理方面的规定,我还时刻小心地提醒我并不是一个法律专家。</p><p style="line-height: 1.50;"> 本书中我并不会专门讨论信用卡处理的方法,而是会说明如何保存加密信息到数据库及在读取时解密。该流程会导致系统性能的下降,但是确实提供了一层保护措施。其主要优点是如果数据库内容泄密暴露出的只是加密信息,但是前提是加密键是安全的。因此,加密键与加密的实现方法本身同样重要。</p><p style="line-height: 1.50;"> 保存加密数据到数据的过程是,首先加密数据,然后通过初始向量与明文建立密文来保存到数据库。由于密文是二进制字符串,还需要通过base64_encode( )转换成普通文本字符串以保证二进制编码的安全存储。</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt = new crypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt->cleartext = '1234567890123456';</p><p style="line-height: 1.50;"> $crypt->generate_iv();</p><p style="line-height: 1.50;"> $crypt->encrypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $ciphertext = $crypt->ciphertext;</p><p style="line-height: 1.50;"> $iv = $crypt->iv;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $string = base64_encode($iv . $ciphertext);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 保存该字串至数据库。在读取时,则是上面流程的逆处理:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $string = base64_decode($string);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $iv_size = mcrypt_get_iv_size($algorithm, $mode);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $ciphertext = substr($string, $iv_size);</p><p style="line-height: 1.50;"> $iv = substr($string, 0, $iv_size);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt = new crypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt->iv = $iv;</p><p style="line-height: 1.50;"> $crypt->ciphertext = $ciphertext;</p><p style="line-height: 1.50;"> $crypt->decrypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $cleartext = $crypt->cleartext;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> ?></p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> 本实现方法假定加密算法与模式不变。如果它们是不定的话,你还要保存它们以用于解密数据。加密键是唯一需要保密的数据。</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;">C.4. 加密会话数据</p><p style="line-height: 1.50;"> 如果你的数据库存在安全问题,或者部分保存在会话中的数据是敏感的,你可能希望加密会话数据。除非很有必要,一般我不推荐这样做,但是如果你觉得在你的情形下需要这样做的话,本节提供了一个实现方法的示例。</p><p style="line-height: 1.50;"> 这个方案十分简单。实际上,在第八章中,已经说明了如何通过调用session_set_save_handler( )来执行你自己的会话机制。通过对保存和读取数据的函数的少量调整,你就能加密存入数据库的数据及在读取时解密数据:</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> <?php</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> function _read($id)</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> global $_sess_db;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $algorithm = MCRYPT_BLOWFISH;</p><p style="line-height: 1.50;"> $mode = MCRYPT_MODE_CBC;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $id = mysql_real_escape_string($id);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $sql = "SELECT data</p><p style="line-height: 1.50;"> FROM sessions</p><p style="line-height: 1.50;"> WHERE id = '$id'";</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> if ($result = mysql_query($sql, $_sess_db))</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> $record = mysql_fetch_assoc($result);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $data = base64_decode($record['data']);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $iv_size = mcrypt_get_iv_size($algorithm, $mode);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $ciphertext = substr($data, $iv_size);</p><p style="line-height: 1.50;"> $iv = substr($data, 0, $iv_size);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt = new crypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt->iv = $iv;</p><p style="line-height: 1.50;"> $crypt->ciphertext = $ciphertext;</p><p style="line-height: 1.50;"> $crypt->decrypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> return $crypt->cleartext;</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> return '';</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> function _write($id, $data)</p><p style="line-height: 1.50;"> {</p><p style="line-height: 1.50;"> global $_sess_db;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $access = time();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt = new crypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $crypt->cleartext = $data;</p><p style="line-height: 1.50;"> $crypt->generate_iv();</p><p style="line-height: 1.50;"> $crypt->encrypt();</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $ciphertext = $crypt->ciphertext;</p><p style="line-height: 1.50;"> $iv = $crypt->iv;</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $data = base64_encode($iv . $ciphertext);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $id = mysql_real_escape_string($id);</p><p style="line-height: 1.50;"> $access = mysql_real_escape_string($access);</p><p style="line-height: 1.50;"> $data = mysql_real_escape_string($data);</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> $sql = "REPLACE</p><p style="line-height: 1.50;"> INTO sessions</p><p style="line-height: 1.50;"> VALUES ('$id', '$access', '$data')";</p><p style="line-height: 1.50;"> </p><p style="line-height: 1.50;"> return mysql_query($sql, $_sess_db);</p><p style="line-height: 1.50;"> }</p><p style="line-height: 1.50;"> </p><hr noshade="noshade" size="1"/><p style="line-height: 1.50;"> </p></td></tr></table>
- 第一章 简介
- 1.1.PHP功能
- 1.1.1. 全局变量注册
- 1.1.2. 错误报告
- 1.2.原则
- 1.2.1. 深度防范
- 1.2.2. 最小权限
- 1.2.3. 简单就是美
- 1.2.4. 暴露最小化
- 1.3. 方法
- 1.3.1. 平衡风险与可用性
- 1.3.2. 跟踪数据
- 1.3.3. 过滤输入
- 1.3.4. 输出转义
- 第二章 表单及URL
- 2.1. 表单与数据
- 2.2. 语义URL攻击
- 2.3. 文件上传攻击
- 2.4. 跨站脚本攻击
- 2.5. 跨站请求伪造
- 2.6. 欺骗表单提交
- 2.7. HTTP请求欺骗
- 第三章 数据库及SQL
- 3.1. 访问权限暴露
- 3.2. SQL 注入
- 3.3. 数据的暴露
- 第四章 会话与 Cookies
- 4.1. Cookie 盗窃
- 4.2. 会话数据暴露
- 4.3. 会话固定
- 4.4. 会话劫持
- 第五章 包含
- 5.1. 源码暴露
- 5.2. 后门URL
- 5.3. 文件名操纵
- 5.4. 代码注入
- 第六章 文件与命令
- 6.1. 文件系统跨越
- 6.2. 远程文件风险
- 6.3. 命令注入
- 第七章 验证与授权
- 7.1. 暴力攻击
- 7.2. 密码嗅探
- 7.3. 重播攻击
- 7.4. 永久登录
- 第八章 共享主机
- 8.1. 源码暴露
- 8.2. 会话数据暴露
- 8.3. 会话注入
- 8.4. 文件系统浏览
- 8.5. 安全模式
- 附录 A. 配置选项
- 附录B. 函数
- 附录C. 加密