md5
md5 ($string, $binary = false)
binary = false时返回的结果是32位的二进制数据
binary = true时返回的结果是16位的二进制数据
sha1
sha1 ($string, $binary = false)
binary = false时返回的结果是32位的二进制数据
binary = true时返回的结果是16位的二进制数据
password_hash
这个函数是PHP 5.5.0新特性。用于密码加密。我们在开发中经常使用md5进行加密,通常是原密码加密码盐的方式。
password_hash()用于加密更加安全。
string password_hash ( string $password , integer $algo [, array $options ]);
前两个参数为必填,后一个参数可选。例如:
$encrypted = password_hash('password123', PASSWORD_BCRYPT, ['cost'=>12]);
//$encrypted = $2y$12$ADGJRSCJGn3it43JAopjp.ewtYO4RtIBvCpSAtY8sGBks9zYj2dHW
//$2y 为PASSWORD_BCRYPT的值 $12 为 cost的值 后面是加密后的值
$verify = password_verify('password123', $encrypted); //true
$verify2 = password_verify('password1236', $encrypted); //false
其中第三个参数是PASSWORD_BCRYPT算法的选项值,cost是指算法使用的递归层数,默认是10.层数越高越复杂,但对硬件的要求越高,默认是10已经够用。该模式算法还有salt选项,即密码盐,使用它也可以增大密码的强度,但是需要注意的是该选项在php7.0版本中已经废除。
password_verify()用于对password_hash()加密后的密码进行校验
password_verify ( string $password , string $hash )
该函数有两个参数,第一个是加密前的值,第二个是加密后的值,返回bool类型。
使用password_hash()进行加密需要注意项目使用的php版本。还有一点就是多种编程语言用同一密码时最好不要用这种加密方法,因为password_verify()加密方法是php特有的,其他语言未必有相同的解密函数。
字符串编码
urlencode
客户端在进行网页请求的时候,网址中可能会包含非ASCII码形式的内容,比如中文。而直接把中文放到网址中请求是不允许的,所以需要用URLEncoder编码地址,将网址中的非ASCII码内容转换成可以传输的字符。
原理
将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。内容中的空格‘ ’ ,全部用+代替。
$encrypted = urlencode('http://baidu.com?a=a&b=bb&c=ccc'); //http%3A%2F%2Fbaidu.com%3Fa%3Da%26b%3Dbb%26c%3Dccc
$decrypted = urldecode($encrypted); //http://baidu.com?a=a&b=bb&c=ccc
json_encode
json_encode() 用于对变量进行 JSON 编码,该函数如果执行成功返回 JSON 数据,否则返回 FALSE
string json_encode ( $value [, $options = 0 ] )
参数
value: 要编码的值。该函数只对 UTF-8 编码的数据有效。
options:由以下常量组成的二进制掩码 JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR。
要注意的是 JSON_UNESCAPED_UNICODE 选项,如果我们不希望中文被编码,可以添加该选项。
$arr = array('cn' => '中文', 'en' => 'english');
$str1 = json_encode($arr);
var_dump($str1); //string(36) "{"cn":"\u4e2d\u6587","en":"english"}"
var_dump(json_decode($str1));
//object(stdClass)#3816 (2) {
// ["cn"]=>
// string(6) "中文"
// ["en"]=>
// string(7) "english"
//}
$str2 = json_encode($arr, JSON_UNESCAPED_UNICODE);
var_dump($str2); //string(30) "{"cn":"中文","en":"english"}"
var_dump(json_decode($str2, true));
//array(2) {
// ["cn"]=>
// string(6) "中文"
// ["en"]=>
// string(7) "english"
//}
serialize
serialize函数用于序列化对象或数组,并返回一个字符串
$sites = array('Google', 'Runoob', 'Facebook');
$serialized_data = serialize($sites); //a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
$sites = unserialize($serialized_data); //array('Google', 'Runoob', 'Facebook')
<?php
class MyClass1 {
public $obj1prop;
}
class MyClass2 {
public $obj2prop;
}
$obj1 = new MyClass1();
$obj1->obj1prop = 1;
$obj2 = new MyClass2();
$obj2->obj2prop = 2;
$serializedObj1 = serialize($obj1);
$serializedObj2 = serialize($obj2);
// 默认行为是接收所有类
// 第二个参数可以忽略
// 如果 allowed_classes 设置为 true, unserialize 会将所有对象转换为 __PHP_Incomplete_Class 对象
$data = unserialize($serializedObj1 , ["allowed_classes" => true]);
ini_set('unserialize_callback_func', 'mycallback'); // set your callback_function
function mycallback($classname)
{
// just include a file containing your class definition
// you get $classname to figure out which class definition is required
}
// 转换所有对象到 __PHP_Incomplete_Class 对象,只允许 MyClass1 和 MyClass2 转换到 __PHP_Incomplete_Class
$data2 = unserialize($serializedObj2 , ["allowed_classes" => ["MyClass1", "MyClass2"]]);
print($data->obj1prop); //1
print(PHP_EOL);
print($data2->obj2prop);//2
?>
base64
原理
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个为一个单元,对应某个可打印字符。
三个bites有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。它可用来作为电子邮件的传输编码。
在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。
如在mime(多用途邮件扩展)中,Base64的使用的64个可打印字符
A-Za-z:大小写字母各26个
0-9:加上10个数字
+:加号
/:斜杠
一共64个字符,等号“=”用来作为后缀用途
对应的转换关系为
0-63:A-Za-z0-9+/
转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲器中剩下的bit用0补足。然后,每次取出6(因为26=64)个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
当原数据长度不是3的整数倍时, 如果最后剩下一个输入数据,在编码结果后加2个“=”;如果最后剩下两个输入数据,编码结果后加1个“=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证数据还原的正确性。
PHP应用
$encrypted = base64_encode('This is test'); //VGhpcyBpcyB0ZXN0
$decrypted = base64_decode($encrypted); //This is test
AES
加密流程
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥,具体的加密流程如下图:
加密原理
按加密方式分为:AES-128、AES-192、AES-256;
按加密模式分为:ECB、CBC、CFB、OFB、CTR、PCBC等。
AES的基本结构
AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:
加密方式 |
密钥长度(字节) |
分组长度(字节) |
加密轮数 |
AES-128 |
16 |
16 |
10 |
AES-192 |
24 |
16 |
12 |
AES-256 |
32 |
16 |
14 |
备注:php中不同的编码格式下字符占用的字节是不同的。ANSI编码中文字符占2个字节、英文字符占1字节;UTF-8编码中文字符占3个字节、英文字符占1字节;Unicode编码中文字符占2个字节、英文字符占2字节。
加密模式
-
ECB
ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
优点:有利于并行计算;误差不会累计(互不干扰)。
缺点:可能对明文进行主动攻击。
-
CBC
CBC模式对于每个待加密的密码块,在加密前会先与前一个密码块的密文异或然后再用加密器加密(图中的圆圈十字符号表示异或操作,下同)。第一个明文块与一个叫初始化向量的数据块异或。加、解密双方共同知晓密钥和初始化向量才能实现加解密。
优点:安全性比ECB模式高;是SSL的标准。
缺点:数据块之间的加密有依赖关系,因此不能并行计算。
-
CFB
CFB 模式是用分组算法实现流算法,明文数据不需要按分组大小对齐。
优点:明文数据不需要按分组大小对其,即无需填充。
缺点:同CBC模式,无法并行计算。
-
OFB
OFB 模式的过程和CBC模式有点像,但明文数据不需要按分组大小对齐。
优点:明文数据不需要按分组大小对其,即无需填充。
缺点:同CBC模式,无法并行计算。
-
CTR
CTR模式是在ECB模式的基础上,引入了Nonce随机数和Counter计数器,Nounce随机数和Counter计数器整体可看作计数器,每加密一段明文,计数器向上加一,并且这个计数器都会和初始IV进行连接、加加、异或等运算,然后使用加密器进行加密,最后在和明文异或得到分段密文。
优点:明文数据不需要按分组大小对其,即无需填充。
缺点:加密方和解密方需要同时维护初始IV、Nonce、Counter。
-
PCBC
PCBC模式是CBC模式的改进版,与CBC模式的不同点在于,CBC模式后段明文加密的所需向量是前一段的密文,而PCBC模式后段明文加密所需的向量是前一段明文和密文的异或值。
优点:同CBC模式。
缺点:同CBC模式。
PHP应用
PHP函数 |
开始版本 >= |
弃用版本 < |
依赖扩展 |
mcrypt_encrypt / mcrypt_decrypt |
4.0.2 |
7.2.0 |
mcrypt |
openssl_encrypt / openssl_decrypt |
5.3.0 |
- |
openssl |
<?php
// openssl_encrypt 函数实现示例
class AES
{
const METHOD = 'AES-256-CBC'; //加密模式
//加密
public static function encrypt($data, $key, $iv){
return base64_encode(openssl_encrypt($data, self::METHOD, $key, OPENSSL_RAW_DATA, md5($iv, true)));
}
//iv不是16个字节会报Warning
//解密
public static function decrypt($data, $key, $iv){
return openssl_decrypt(base64_decode($data), self::METHOD, $key, OPENSSL_RAW_DATA, md5($iv, true));
}
}
$text = 'This is test';
$key = 'bnZpc2libGU9JmZp13credivh4tQhAES'; //AES-256最多取前32个字节
$iv = 'pc2lib13credh4tQ';
$encrypted = AES::encrypt($text, $key, $iv);
var_dump($encrypted); //0YjQ+Fa2Ebuobx8YOGOiLQ==
$decrypted = AES::decrypt($encrypted, $key, $iv);
var_dump($decrypted); //This is test
die;
RSA
RSA算法属于非对称加密算法,非对称加密算法需要两个秘钥:公开密钥(publickey)和私有秘钥(privatekey).公开密钥和私有秘钥是一对,
如果公开密钥对数据进行加密,只有用对应的私有秘钥才能解密;
如果私有秘钥对数据进行加密那么只有用对应的公开密钥才能解密.
需要注意的地方
1.RSA 加密或签名后的结果是不可读的二进制,使用时经常会转为 BASE64 码再传输
2.RSA 加密时,对要加密数据的大小有限制,最大不大于密钥长度。例如在使用 1024 bit 的密钥时(秘钥生成可以自行百度),最大可以加密 1024/8=128 Bytes 的数据。数据大于 128 Bytes 时,需要对数据进行分组加密(如果数据超限,加解密时会失败,openssl 函数会返回 false),分组加密后的加密串拼接成一个字符串后发送给客户端。
为了保证每次加密的结果都不同,RSA 加密时会在待加密数据后拼接一个随机字符串,再进行加密。不同的填充方式 Padding 表示这个字符串的不同长度,在对超限数据进行分组后,会按照这个 Padding 指定的长度填入随机字符串。例如如果 Padding 填充方式使用默认的 OPENSSL_PKCS1_PADDING(需要占用 11 个字节用于填充),那么明文长度最多只能就是 128-11=117 Bytes。
接收方解密时也需要分组。将加密后的原始二进制数据(对于经过 BASE64 的数据,需要解码),每 128 Bytes 分为一组,然后再进行解密。解密后,根据 Padding 的长度丢弃随机字符串,把得到的原字符串拼接起来,就得到原始报文。
3.openssl_public_encrypt函数 php的默认填充和无填充是有区别的,如果只是php和php对接则不需要关注这个问题,如果是php跟c或java,需要选择无填充然后自行加入填充
4.需要将php的openssl模块打开或安装(win上是打开,linux上是安装,具体自行百度)
下面使用的相关函数功能介绍:
openssl_pkey_get_public() 从证书中提取公钥
openssl_pkey_get_private() 从证书中提取私钥
openssl_public_encrypt() 公钥加密
openssl_private_decrypt() 私钥解密
openssl_private_encrypt() 私钥加密
openssl_public_decrypt() 公钥解密
base64_encode() 使用base64对数据重新编码
base64_decode() 将base64的数据解码
//公钥加密
$public_key = openssl_pkey_get_public(RSA_PUBLIC);
if(!$public_key){
die('公钥不可用');
}
//第一个参数是待加密的数据只能是string,第二个参数是加密后的数据,第三个参数是openssl_pkey_get_public返回的资源类型,第四个参数是填充方式
$return_en = openssl_public_encrypt("hello world", $crypted, $public_key);
if(!$return_en){
return('加密失败,请检查RSA秘钥');
}
$eb64_cry = base64_encode($crypted);
echo "公钥加密数据:".$eb64_cry;
echo "<hr>";
//私钥解密
$private_key = openssl_pkey_get_private(RSA_PRIVATE);
if(!$private_key){
die('私钥不可用');
}
$return_de = openssl_private_decrypt(base64_decode($eb64_cry), $decrypted, $private_key);
if(!$return_de){
return('解密失败,请检查RSA秘钥');
}
echo "私钥解密数据:".$decrypted;
echo "<hr>";
//私钥加密
$private_key = openssl_pkey_get_private(RSA_PRIVATE);
if(!$private_key){
die('私钥不可用');
}
$return_en = openssl_private_encrypt("hello world222222", $crypted, $private_key);
if(!$return_en){
return('加密失败,请检查RSA秘钥');
}
$eb64_cry = base64_encode($crypted);
echo "私钥加密数据".$eb64_cry;
echo "<hr>";
//公钥解密
$public_key = openssl_pkey_get_public(RSA_PUBLIC);
if(!$public_key){
die('公钥不可用');
}
$return_de = openssl_public_decrypt(base64_decode($eb64_cry), $decrypted, $public_key);
if(!$return_de){
return('解密失败,请检查RSA秘钥');
}
echo "公钥解密数据:".$decrypted;
echo "<hr>";
DES
DES 是对称性加密里面常见一种,全称为 Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法。密钥长度是64位(bit),超过位数密钥被忽略。所谓对称性加密即加密和解密密钥相同,对称性加密一般会按照固定长度,把待加密字符串分成块,不足一整块或者刚好最后有特殊填充字符。
跨语言做 DES 加密解密经常会出现问题,往往是填充方式不对、编码不一致或者加密解密模式没有对应上造成。常见的填充模式有: pkcs5、pkcs7、iso10126、ansix923、zero。加密模式有:DES-ECB、DES-CBC、DES-CTR、DES-OFB、DES-CFB。
<?php
/**
* openssl 实现的 DES 加密类,支持各种 PHP 版本
*/
class DES
{
/**
* @var string $method 加解密方法,可通过 openssl_get_cipher_methods() 获得
*/
protected $method;
/**
* @var string $key 加解密的密钥
*/
protected $key;
/**
* @var string $output 输出格式 无、base64、hex
*/
protected $output;
/**
* @var string $iv 加解密的向量
*/
protected $iv;
/**
* @var string $options
*/
protected $options;
// output 的类型
const OUTPUT_NULL = '';
const OUTPUT_BASE64 = 'base64';
const OUTPUT_HEX = 'hex';
/**
* DES constructor.
* @param string $key
* @param string $method
* ECB DES-ECB、DES-EDE3 (为 ECB 模式时,$iv 为空即可)
* CBC DES-CBC、DES-EDE3-CBC、DESX-CBC
* CFB DES-CFB8、DES-EDE3-CFB8
* CTR
* OFB
*
* @param string $output
* base64、hex
*
* @param string $iv
* @param int $options
*/
public function __construct($key, $method = 'DES-ECB', $output = '', $iv = '', $options = OPENSSL_RAW_DATA | OPENSSL_NO_PADDING)
{
$this->key = $key;
$this->method = $method;
$this->output = $output;
$this->iv = $iv;
$this->options = $options;
}
/**
* 加密
*
* @param $str
* @return string
*/
public function encrypt($str)
{
$str = $this->pkcsPadding($str, 8);
$sign = openssl_encrypt($str, $this->method, $this->key, $this->options, $this->iv);
if ($this->output == self::OUTPUT_BASE64) {
$sign = base64_encode($sign);
} else if ($this->output == self::OUTPUT_HEX) {
$sign = bin2hex($sign);
}
return $sign;
}
/**
* 解密
*
* @param $encrypted
* @return string
*/
public function decrypt($encrypted)
{
if ($this->output == self::OUTPUT_BASE64) {
$encrypted = base64_decode($encrypted);
} else if ($this->output == self::OUTPUT_HEX) {
$encrypted = hex2bin($encrypted);
}
$sign = @openssl_decrypt($encrypted, $this->method, $this->key, $this->options, $this->iv);
$sign = $this->unPkcsPadding($sign);
$sign = rtrim($sign);
return $sign;
}
/**
* 填充
*
* @param $str
* @param $blocksize
* @return string
*/
private function pkcsPadding($str, $blocksize)
{
$pad = $blocksize - (strlen($str) % $blocksize);
return $str . str_repeat(chr($pad), $pad);
}
/**
* 去填充
*
* @param $str
* @return string
*/
private function unPkcsPadding($str)
{
$pad = ord($str{strlen($str) - 1});
if ($pad > strlen($str)) {
return false;
}
return substr($str, 0, -1 * $pad);
}
}
$key = 'key123456';
$iv = 'iv123456';
// DES CBC 加解密
$des = new DES($key, 'DES-CBC', DES::OUTPUT_BASE64, $iv);
echo $base64Sign = $des->encrypt('Hello DES CBC');
echo "\n";
echo $des->decrypt($base64Sign);
echo "\n";
// DES ECB 加解密
$des = new DES($key, 'DES-ECB', DES::OUTPUT_HEX);
echo $base64Sign = $des->encrypt('Hello DES ECB');
echo "\n";
echo $des->decrypt($base64Sign);