PHP回顾系列目录

PHP加解密

经过CSDN的密码泄露、各大平台和论坛被拖库等安全事件,但凡有一点安全意识的开发人员都知道不能存放明文密码(当然单纯md5同样会被鄙视);信用卡及CVV等非常敏感的客户数据,需要经过多层加密后才存放到数据库中。

数据的安全处理包含加密和解密两个过程。加密后结果有两种:无法还原明文(不包括穷举和暴力破解)和可还原成明文。第一种是单向加密模式,一般用于校验;第二种加密有与之对应的解密过程,根据算法和密钥能得到加密前的结果。两种加密模式在实践中都有广泛应用场景。例如,用户的密码,只需要进行安全的hash即可,即使数据泄露,获取明文密码的可能性也大大降低;用户的手机号则需要加密存储,发短信通知用户时,将密文还原成手机号再使用,避免被拖库后泄露用户信息。

加密算法

PHP和加解密相关的拓展主要有: Hash,Mcrypt、OpenSSL以及在PHP 5.5中引入的密码hash API。以下分别做介绍。

Hash

Hash拓展如其名,提供了各类常见的hash算法,如md5,、SHA系列、hmac、pbkdf2等。计算字符串或者文件的hash值,该拓展内的函数基本能满足要求。拓展内常用的函数包括:

  • hash_algos: 列出拓展支持的hash算法
  • hash/hash_file: 根据给定的算法和数据/文件,计算hash值
  • hash_hmac/hash_hmac_file/hash_pbkdf2: 计算给定数据/文件的hash值,需要提供密钥或者盐等额外数据

常用md5和sha1函数,可用拓展内的函数替代:md5(‘xxxxx’)等同于hash(‘md5’, ‘xxxxx’),sha1同理。

Mcrypt

Mcrypt主要提供双向操作的各类算法,例如DES、3DES、AES等算法。Mcrypt依赖于libmcrypt项目,但是该项目自2007起不再维护,这导致Mcrypt拓展在PHP 7.1中被废弃,在PHP 7.2中被移除(项目迁移到PECL中)。在新项目中不建议使用Mcrypt拓展 ,官方推荐使用OpenSSL拓展替代。

和hash类函数相比,mcrypt中的加解密函数使用门槛更高。要熟练使用mcrypt,需要理清楚几个概念:加密算法(cipher)、分组模式(encrypt mode)、密钥(key)和初始向量(initialization vector, IV)。常用的加密算法包括DES/3DES/AES-128/AES-192等,分组模式有CBC/CFB/ECF/OFB等,密钥是用户用来加解密的字符串,某些算法和分组模式组合需要初始向量来驱动。

根据算法和分组模式,加密时操作的数据块大小(block size)会有差异。不是数据块大小整数倍的数据,需要进行填充后才可使用。遗憾的是,mcrypt对数据填充没有支持。同理,密钥和iv的长度必须与算法和模式要求的相符。

所以,要使用mcrypt加解密,需要上述四个要素:算法、分组模式、密钥和初始向量(非必须,某些算法和分组模式组合有要求)。mcrypt中的函数,除了真正执行加解密的函数(mcrypt_encrypt/mcrypt_decrypt、mcrypt_generic/mdecrypt_generic),其余函数可以归类为辅助获取上述四个要素中的一项。例如,使用mcrypt_list_algorithms函数获取支持的加密算法;mcrypt_get_iv_size和mcrypt_create_iv用来获取初始向量。四项要素准备齐全后,输入数据即可进行加解密(注意:数据可能需要填充,填充长度依赖于算法和分组模式)。

OpenSSL

OpenSSL拓展既包含hash类的算法,也提供双向操作的DES/AES等算法,还支持非对称的RSA算法、安全的随机数生成等功能。由于OpenSSL拓展的能力依赖于openssl库,其功能将会不断完善,建议熟悉和使用。

openssl_get_md_methods函数可获取所有支持的hash算法,使用openssl_digest函数可计算hash值。对于pbkdf2算法,可直接使用openssl_pbkdf2函数。

openssl_get_cipher_methods函数获取所有的加密算法,然后可使用openssl_encrypt/openssl_decrypt函数进行加解密。

openssl库提供了openssl_random_pseudo_bytes函数来生成随机的字符串。如果程序中需要随机数,建议使用该函数而非自行写算法生成。

此外,openssl提供了对证书的支持。可以从证书中读取证书、使用公钥和私钥对数据进行签名和校验。相关的函数包括:openssl_get_publickey/openssl_get_privatekey、openssl_private/public_encrypt/decrypt、openssl_sign/openssl_verify等函数。详情可参考官方的openssl拓展文档

OpenSSL依赖于openssl库,建议系统中安装新版本的openssl库以保障安全性。

密码hash API

(猜测)密码的加密和验证对许多人是个难题(或者用弱鸡的行为来实现),无奈之下PHP官方在5.5版本中内置了密码加密和验证的原生支持。密码hash API主要是针对密码加密和验证:passwod_hash加密,password_verify验证密码,就是这么简单!

如果翻阅文档,发现此拓展总共四个函数。除去加密和验证,其余两个分别是password_get_info和password_neets_rehash。第一个函数返回加密数据的信息,第二个函数则校验加密数据是否与指定的算法相匹配。可见官方为了人民群众科学的使用密码而专门搞这么一套API,也是用心良苦了。

password_hash使用了号称后现代主义的加密算法:bcrypt(默认,目前也只有该算法,后续可能会加入其它算法)。使用该函数,能极大的提高密码的安全性,强烈建议使用。

具体实践

具体业务开发中,约定好加密方式,调用已有的库函数即可解决绝大部分数据加解密问题。如非必要,千万不要自己实现一套加密算法,除非你是密码学专家。例如hash类算法的一致性和雪崩性,加密类算法的安全性依赖于密钥而非算法原则,非算法专业的很难设计出,即使实现也不一定能经得住生产环境的考验。

如果约定的协议不是某个具体的加密算法,还包含了一系列前处理和后处理,就要视情况而定。PHP没有字节流的概念(使用字节流需掌握pack/unpack函数),string或者char才是最直观的,md5或者hash_hmac中的row_output参数都很少用(不一定懂含义),二进制或者字节流就更难为人了。如果对方先获取数据的字节流,做了一系列流处理和加密操作,最后base64了一下,PHP这边可能除了base64能看懂,其他的就懵逼了。这种情况下,建议找有经验的老司机写好算法后直接调用,课后自己多补补密码学的东西加深理解。

密码知识是业务中用到的一个工具,相信大部分人可以完全无视之(因为有现成API可调用)。如果能理解,就是最吼的,毕竟提高安全意识是值得称赞的。