この仮想化が進むインターネットの世界では、データを慎重に保護する必要があります。この記事では、パスワード、クレジット カード番号、さらにはメッセージ全体などの重要な情報のエンコードと暗号化の基本について説明します。また、PHP の組み込み機能を使用することで、情報の暗号化と復号化の意味を学び、パスワードやその他のデータに関するいくつかの実践的な例を確認できます。
今日の現実世界が 20 年前の現実世界とどのように異なっているかを理解します。 1980 年代、暗号化はシークレット サービスのものであり、トム クランシーの推理小説で読んだようなものでした。少量の情報を秘密にしておきたい場合は、パスワード、パスフレーズ、またはその他の基本的な方法を使用してデータを暗号化する必要があります。
現在、暗号化はあらゆるところで使用されています。パスワードも暗号化されてデータベースに保存されます。サイバースペースの暗号化チャネルは、仮想プライベート ネットワークはもちろんのこと、SSL、SSH、その他のテクノロジーを介して暗号化される場合があります。ユーザーは、Pretty Good Privacy (PGP) を使用して機密ファイルや電子メールを保護できますし、使用することになります。
PHP 開発者は、強力なセキュリティ対策が独自のアプリケーションに適用されるだけでなく、現在取り組んでいるプロジェクトにも適用できることを知っておく必要があります。ユーザーは、一般的な暗号化方式 (ログイン ページのパスワード フィールドにクリア テキストを表示しないなど) からさまざまな高度な暗号化方式 (DES、MD5、SHA1、Blowfish など) への移行に対する意識を高める必要があります。
時間とスペースの制約のため、ここで暗号化のすべての側面について説明することはできませんが、ここで、あなたに当てはまるほとんどの状況をカバーする基本を学びます。まず、PHP の組み込み機能を使用して情報を暗号化および復号化することが何を意味するのかを見てから、パスワードやその他のデータを含むいくつかの実際的な例に進みます。この記事では、より大きなセキュリティの観点から暗号化について説明します。最後に、他の PHP 拡張機能とプラグインを紹介します。
暗号化入門
ギリシャ語の語源である資産として、暗号化は「暗号化」の芸術です。シーザー暗号は最も古い暗号であり、最も単純な形式です。平文メッセージを受け取り、文字を n 位置に移動して暗号文を生成します。例:
平文: Veni Vidi Vici
ダークテキスト: Xgpk 。シーザー暗号は解読するのが簡単です。例: 上記の情報を確認すると、X が複数回繰り返され、k も同様であることがわかります。 4 文字の単語にその母音の数がいくつあるかを判断する推測ゲームになります。これらの母音付きの単語を理解すれば、残りの単語の移動方法がわかります。また、プレーン テキストがラテン語であるかどうかを判断し、その概要を把握するのにも役立ちます。
<form action="verify.php" method="post"> <p><label for='username'>Username</label> <input type='text' name='username' id='username'/> </p> <p><label for='password'>Password</label> <input type='text' name='password' id='password'/> </p> <p><input type="submit" name="submit" value="log in"/> </p> </form>この HTML マークアップのどこが間違っていますか?パスワード フィールドに選択された入力タイプはテキストです。つまり、ユーザーがフィールドに入力した内容はすべてクリア テキストとして画面に表示されます。 タイプをパスワードに変更し、フィールド内のユーザー入力をアスタリスクの文字列に置き換えることができます。信頼できるものですか?絶対に信頼できます。ただし、このステップは多くのアプリケーションでは無視されます。小さなことでも、安全性に関しては人々を不安にさせることがあります。ラウンジの窓がひどく破損していた銀行にお金を預けたいですか?もしかしたらそうなるかもしれません。しかし、銀行が無傷であることを望むかもしれません。アプリケーションについても同様です。 フォームの送信を処理する verify.php ファイルに進みましょう。 リスト 2. 単純な PHP ログイン検証
<?php $user = $_POST['user']; $pw = $_POST['password']; $sql = "select user,password from users where user='$user' and password='$pw' limit 1'; $result = mysql_query($sql); if (mysql_num_rows($result)){ //we have a match! }else{ //no match } ?>
读阅到此,您会露出满意的微笑。等待阅读本文加密部分的部分读者可能变得不耐烦了,但是加密仅为安全问题的一部分。您还必须聪明地处理引入的用户数据。developerWorks 教程 “锁定您的 PHP 应用程序”(请参阅参考资料)讨论了 SQL 注射:将不正常的数据发送到数据库可导致有害或无根据的访问。无论您使用多少个加密,公开弱点没有一点好处。
您应遵循下面的传统安全原则:“不信任用户提供的数据” 和 “深层防御”;清除传入数据并通过转义传入的字符串保护数据库(参见清单 3)。深层防御是将多余的安全方法都妥善保管 —— 不仅包括加密,还包括用户所提供数据的智能处理。
清单 3. 保护 PHP 形式解析免受用户数据操作的影响
<?php $user = strip_tags(substr($_POST['user'],0,32)); $pw = strip_tags(substr($_POST['password'],0,32)); $sql = "select user,password from users where user='". mysql_real_escape_string($user)."' and password='". mysql_real_escape_string($pw)."' limit 1'; $result = mysql_query($sql); if (mysql_num_rows($result)){ //we have a match! }else{ //no match } ?>
通过合理使用 strip_tags()、substr() 和 mysql_real_escape_string(),可以删除任何潜在的有害命令,将字符串减少到 32 个字符,并去掉所有特殊字符,数据库可能将这些字符解释为非预期命令字符串的一部分。
在这一过程结束时,数据库中仍有一个明文密码。您不能显示它。最容易的修复方法是使用 PHP 的内置 crypt() 功能。
使用 crypt()
PHP 的内置 crypt() 功能可实现单向加密 或单向散列。它只所以是单向的,是因为在对某内容进行加密后,您永远不能将其反转为明文。乍一看,此想法似乎很荒谬。使用加密主要是保护信息,随后能够使用该信息,后者通常意味着能够对它进行解密。
不要绝望。单向加密方案和 crypt() 特别受欢迎。可以使保护信息的方法更安全。如果您的用户密码列表落入不法之徒之手,他们实际上没有将密码解密为明文的方法。
让我们返回到密码示例。注释 (notational) PHP 应用程序可能包括让系统管理员创建、编辑和删除用户的模块。例如,在将用户记录存储到用户表之前,PHP 脚本可以使用 crypt() 对密码加密。
清单 4. 使用 crypt() 加密密码
<?php $user = strip_tags(substr($_POST['user'],0,32)); $pw = strip_tags(substr($_POST['password'],0,32)); $cleanpw = crypt($pw); $sql = "insert into users (username,password) values('".mysql_real_escape_string($user)."', '".mysql_real_escape_string($cleanpw)."')"; //.....etc.... ?>
crypt() 将一串明文作为它的第一个参数字,对它应用 salt 会影响加密算法的随机性,并生成输入明文的单向暗文。如果不提供 salt,则 PHP 通常默认其系统 salt,它可以是以下值和长度之一:
算法 Salt
CRYPT_STD_DES 2 个字符(默认)
CRYPT_EXT_DES 9 个字符
CRYPT_MD5 12 个字符,以 $1$开头
CRYPT_BLOWFISH 16 个字符,以 $2$开头
许多现代 PHP 安装使用 MD5 或更高的 salt,它们使用强大的 12 个字符的 salt,但是,不要对任何事情想当然。您最好知道系统正在使用哪一个值。您可以使用以下 PHP 代码片段检查服务器的设置:
<?php echo "System salt size: ". CRYPT_SALT_LENGTH; ?>
返回的答案将是 2、9、12 或 16,它告诉您系统正在使用的值。要使用 MD5 或更高版本的 salt,您可以显式调用明文和 salt 参数中的 crypt() 函数以及 md5() 函数,以获取随机暗文(参见清单 5)。md5() 函数可以散列反馈的任何字符串,并将其转变为固定长度为 32 个字符的字符串。您可能更喜欢其他方法,具体的使用取决于安全需求和个人爱好。
清单 5. 使用 crypt() 和 md5() 加密密码
<?php $user = strip_tags(substr($_POST['user'],0,32)); $pw = strip_tags(substr($_POST['password'],0,32)); $cleanpw = crypt(md5($pw),md5($user)); $sql = "insert into users (username,password) values('".mysql_real_escape_string($user)."', '".mysql_real_escape_string($cleanpw)."')"; //.....etc.... ?>
现在数据库中已经拥有一个已加密的密码,但是没有对其进行解密的方法。如何使之有用?一个比较容易的方法是:对用户提供的任何传入密码都使用相同的加密方法,并将结果与您存储的密码比较。
清单 6. 重访 verify.php
<?php $user = strip_tags(substr($_POST['user'],0,32)); $pw = strip_tags(substr($_POST['password'],0,32)); $cleanpw = crypt(md5($pw),md5($user)); $sql = "select user,password from users where user='". mysql_real_escape_string($user)."' and password='". mysql_real_escape_string($cleanpw)."' limit 1'; $result = mysql_query($sql); if (mysql_num_rows($result)){ //we have a match! }else{ //no match } ?>
例如,如果存储的加密密码是 i83Uw28jKzBrZF,则加密存储传入的密码,并将它与存储的密码进行比较。攻击者破坏加密的惟一方法是将一个非常长的字符串列表与您的加密密码进行比较,每次比较一个,直到找到匹配项。这也称为字典攻击,因此您的密码最好不应该是密码 或 Star Trek 字符名,甚至您的呢称。因为在加密 Fido 后,它会变成一堆乱语,但这并不表明它对于此种攻击是安全的。确保您的密码具有某一长度(八个或更多字符),并包含大写字母、数字和特定的字符,如 ! 和 $,这样猜测您的数据会更加困难。在短语中,f1D0! 是一个较好的密码,它胜于 GandalftheGray 之类的长密码,由于后者使用小写字母,并且是 “Lord of the Rings” 的字符名称。
使用 crypt() 的一种不太好的方法
还有使用 crypt() 的另一种方法,这种方法不太好:将明文的前 n 个字符用作 salt。
清单 7. 将明文字符用于 salt
<?php $user = strip_tags(substr($_POST['user'],0,32)); $pw = strip_tags(substr($_POST['password'],0,32)); $cleanpw =crypt($pw, substr($user,0,2)); $sql = "select user,password from users where user='". mysql_real_escape_string($user)."' and password='". mysql_real_escape_string($cleanpw)."' limit 1'; $result = mysql_query($sql); if (mysql_num_rows($result)){ //we have a match! }else{ //no match } ?>
如果您的用户名是 tmyer,则 salt 预置为 tm,它会使某人很容易推断 salt 的内容。这不是一个好方法。
使用 PHP 进行加密和解密
本文的大部分篇幅讨论了使用 crypt() 的单向加密。但是,如果您要将消息发送给某人,并提供对该消息解密的方法,又该如何办呢?请使用 PHP 支持的公钥加密技术。
使用公钥加密的用户拥有一个私钥和一个公钥,并且他们与其他用户共享公钥。如果您要将一封私有短信发送给您的朋友 John Doe,您可以使用 John Doe 的公钥(您已经将其存储在自已的 keyring 中)加密该消息。John Doe 收到该消息后,只有他可以使用他的私钥对其解密。任何给定用户的公钥和私钥在数学上是不能相关的。对于 PGP 和其他公钥加密方法,不存在从公钥推断某人私钥的方法。
PGP 的附加特性是:私钥的密码实际上不是密码,它是一个密码短语。它可以是整句话,包括标点符号、空格和所有字符样式。
使用基于 PGP 的公钥加密的一种方法是使用 GNU Privacy Guard (GPG)。使用 GPG 加密的任何消息都可以使用 GPG、PGP 或支持任一程序的任何数量的电子邮件客户机插件来解密。在示例中,联机表接受用户输入(包括消息);使用 GPG 为特定的接收方加密消息;然后发送消息。
清单 8. 使用 GPG
<?php //set up users $from = "webforms@example.com"; $to = "you@example.com"; //cut the message down to size, remove HTML tags $messagebody = strip_tags(substr($_POST['msg'],0,5000)); $message_body = escapeshellarg($messagebody); $gpg_path = '/usr/local/bin/gpg'; $home_dir = '/htdocs/www'; $user_env = 'web'; $cmd = "echo $message_body HOME=$home_dir USER=$user_env $gpg_path" . "--quiet --no-secmem-warning --encrypt --sign --armor " . "--recipient $to --local-user $from"; $message_body = `$cmd`; mail($to,'Message from Web Form', $message_body,"From:$from\r\n"); ?>
在此示例中,PHP 调用 /usr/local/bin/gpg(此位置因服务器而异),以便使用发送方的私钥和接收方的公钥加密消息。结果,只有接收方可以解密该消息,并且知道来自发送方的消息。此外,还可以设置 HOME 和 USER 环境变量,以通知 GPG 在何处查找存储这些密钥的 keyring。其他标志的功能如下:
--quiet 和 --no-secmem-warning 抑制来自 GPG 的警告。
--encrypt 执行加密。
--sign 添加签名,以验证发送方的身份。
--armor 产生非二进制的 ASCII 输出,这样,易于通过电子邮件将其发送。
正常情况下,正如前面提到的,机密密钥受密码短语的保护。本特定实例没有使用密码短语,因为在每次表单提交时它都需要手工输入。当然,在下列情况下您还可以选择其他选项:在单独文件中提供短语,或使用它自已的身份验证方案防止表单公用(例如,如果它是一个只能由公司销售代表访问的表单)。
另请注意,除非您正在对允许用户输入电子邮件消息的表使用 SSL,否则键入的任何内容都是明文形式的。换句话说,客户机和服务器之间的任何人都可以看见它。不过,这是另一个主题。
结束语
我们对安全性、加密技术,甚至公钥加密技术介绍了很多,目的是帮助您成功开发下一个 PHP 项目。使用加密和其他加密方法的要点不是创建 100% 可靠的无缝系统。关闭的计算机才是不可攻击的系统,但是也不能完全保证,因为某人可能会走上前走,打开它,然后攻击它。加密的要点是使获取敏感数据变得非常困难,以致黑客不再尝试攻击,或尝试攻击失败后离去。
所有安全性考虑必须兼顾方便和保护。使用强大的算法密钥将所有数据都进行单向加密意味着您的数据非常安全,但是使用时很不方便。这带来的相应缺陷也很严重,如同使用非加密的内容一样,为您带来的任何方便也为其他人获取数据带来了可怕的方便。通过加密重要的机密数据(如密码、信用卡号和秘密消息)和添加好的安全措施(如深层防御、过滤用户提供的数据和传统的一般常识)可以达到最佳平衡。
【相关教程推荐】
2. php从入门到精通
3. bootstrap教程