共计 4668 个字符,预计需要花费 12 分钟才能阅读完成。
永强被吓坏了!因为永强看到了某个微信群有人指出公众号里上篇打酱油附送的那篇文章《震惊!北京一男子竟然用 swoole 做了这种事!》的内容实在是太 low 了,这种 low 文章就不要拿出来发了。
但是给永强留下了面积巨大的心理阴影。他尚未见识过社交网络的恶毒嘴脸。
所以永强本来昨天要发的文章拖到了今天,但是永强实在是怕了,他怕被人喷了被人骂了。虽然我百般鼓励,但他还是心有余悸。尽管我都已经直接告诉他“你那玩意根本就没人看”了,他还是依然不敢发了。然后我不得不摆出 PS 大法给他做了一张图,他看了看那张图后又收了我 6.66 元的微信红包,决定继续鼓起勇气发了。
是时候表现一下我的 PS 精湛技术了!
“我们历经千辛万苦,摸打滚爬过数不清的错误,发射了不知道多少枚长征系列,耗费了一代航天人的心血,终于看到了地球与月亮通信的曙光,然后就在五分钟,我们惊讶地发现,原来老王的 smartmesh 技术早就实现了,甚至连地球文明与外形文明的通信都给出了完美的解决方案…”——— 尼古拉斯 * 赵永强
众所周知,作为精通各种技术表演的我早就已经不屑于采用 ppt 的方式吹牛了,一般我都是直接上机操作表演,当然了,程序都是提前写好了的,全是 mock 的假数据,脚本实现自动化,无论谁来操作都是流畅的,一切都是完美的!
作为一个追求完美的人,我还得继续接着吹上次聊到结尾,好像是遗留了两个问题:
- ecb、cfb、cbc 等这些后缀是什么意思
- iv 向量又是什么意思
鉴于 DES 和 3DES 已经属于不建议使用的方法了,所以这次我们直接用 AES 加密进行装逼表演,比如下面这坨代码,你们复制粘贴走运行一下:
<?php
$ava_methods = openssl_get_cipher_methods();
// 选用 aes-128-ecb
$my_method = 'aes-128-ecb';
if (!in_array( $my_method, $ava_methods) ) {exit( '错误的加密方法'.PHP_EOL);
}
// 加密用的密码
$key = "1234567812345678";
// 加密的内容
$data = "12345678abcdxxoo12345678abcdxxoo";
$enc_data = openssl_encrypt($data, $my_method, $key, OPENSSL_RAW_DATA);
$hex = bin2hex($enc_data);
echo $hex.':'.strlen($hex).PHP_EOL;
我这里运行结果是:
c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf : 96
分析一下上面代码:这次我们选用的加密方法是 AES-128-ECB,这个 128 是什么意思?128 就是密钥长度的意思:128bit;如果你留心的话,还会注意到有 aes-192-ecb 和 aes-256-ecb,其实就是指加密密钥长度为 192bit、256bit,然后是值得注意的一个地方是:
$enc_data = openssl_encrypt($data, $my_method, $key, OPENSSL_RAW_DATA);
最后一个参数是 OPENSSL_RAW_DATA,如果选用这个 option 的话,经过加密后的数据会是奇怪的二进制数据,无法直接通过文本方式查看,所以要看的话必须先使用 bin2hex 函数处理一下。
注意了哈,我选的这个密钥 1234567812345678 是有特殊用意的,这个密钥的长度是 16 字节也就是 128bit,而我们选用的 aes 加密方法中要求的密钥长度就是 128bit,那么我们尝试将密钥增加几位变成:1234567812345678abc,然后其他代码不做任何改动,再次执行加密,结果如下:
c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf : 96
就是说用“1234567812345678”和“1234567812345678abc”加密后的数据都是一样的。看起来如果我们选用 128bit 密钥长度的话,一旦密钥长度超过 128bit 后面多余的部分会被直接无视掉~~~
然后我们再尝试将密钥“1234567812345678”缩短一个字节,改成“123456781234567”,其他地方代码不做任何改动,运行一波儿,结果如下:
c202e5b1dc36c3147e50d02df7ab700cc202e5b1dc36c3147e50d02df7ab700cda89b056d926d3fea2e59ffc552b1d98 : 96
这次不行了,已经不一样了~
然后,我们将注意力放到明文和密文上来:
明文:12345678abcdxxoo12345678abcdxxoo
密文:c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf
仔细观察有一个比较屌的地方,我们将密文每隔 32 个字符长度就分割一下,你们感受一下:
c1391e34caf38f8c2a477cbda3772533
c1391e34caf38f8c2a477cbda3772533
d96aa42b59151a9e9b5925fc9d95adaf
卧槽,竟然有前两段是一样的!???卧槽。。。。。。
仔细看了一把明文 12345678abcdxxoo12345678abcdxxoo,分析一下,卧槽!:
12345678abcdxxoo
12345678abcdxxoo
难道说明文“12345678abcdxxoo”被密钥“1234567812345678”加密后后的密文就是“c1391e34caf38f8c2a477cbda3772533”?
时机已然成熟了!是时候继续深入装逼了!为什么会出现这个结果?现在我们开始说“ecb、cfb、cbc 等这些后缀是什么意思”。
你若有所思的猜测到:“难道说对称加密的时候,都是将明文先分块,然后再分别对分块加密?”,我欣慰地看着你说:“嗯,是的,肯定是,不然老子往下没法写了,我特么都快编不下去了…”
- DES 和 3DES 会将明文以 64bit(8 字节)作为一个单元进行分组;
- AES 则会将明文以 128bit(16 字节)作为一个单元进行分组;
- 无论是 AES 还是 DES,当最后一个分组的数据长度不满足分组标准长度的时候,会用某种填充方式进行填充;
- AES 对一个 16 字节分组加密完毕后,分组大小依然为 16 字节;
比如说这段明文“12345678abcdxxoo12345678abcdxxoo”,一共是 32 字节,理论上说就会被先按照 16 字节分组:“12345678abcdxxoo”是一组,剩下的“12345678abcdxxoo”是另外一组,我们用程序验证一下:
<?php
$ava_methods = openssl_get_cipher_methods();
$my_method = 'aes-128-ecb';
if (!in_array( $my_method, $ava_methods) ) {exit( '错误的加密方法'.PHP_EOL);
}
$key = "123456781234567";
// 注意!这段明文长度刚好为 32 字节!$data = "12345678abcdxxoo12345678abcdxxoo";
echo '明文长度:'.strlen($data).PHP_EOL;
$enc_data = openssl_encrypt($data, $my_method, $key, OPENSSL_RAW_DATA);
echo '密文长度:'.strlen($enc_data).PHP_EOL;
注意明文长度我选择刚好为 32 字节。保存运行一下,至于你们那里是什么结果我不知道,反正我这里是这样的:
我日,感觉被打脸了,为毛加密后多出了 16 字节?
我们将明文从 32 字节的“12345678abcdxxoo12345678abcdxxoo”修改成 33 字节的“12345678abcdxxoo12345678abcdxxooa”,这样的话,明文会被分成三个 16 字节的分组,由于最后一个分组只有一个字节,所以剩余 15 字节会被填充:
似乎印证了我们一个猜测:当最后一个明文分组小于要求分组标准大小时,不会产生新的分组;当最后一个明文分组大于等于要求分组标准大小时,会产生一个新的分组。
我不想填充怎么办?修改一下加密和解密函数的最后那个 OPENSSL_NO_PADDING 选项即可,你们感受一下:
<?php
$ava_methods = openssl_get_cipher_methods();
// 选用 aes-128-ecb
$my_method = 'aes-128-ecb';
if (!in_array( $my_method, $ava_methods) ) {exit( '错误的加密方法'.PHP_EOL);
}
// 加密用的密码
$key = "1234567812345678";
// 加密的内容
$data = "12345678abcdxxoo12345678abcdxxoo";
echo '明文:'.$data.',长度为'.strlen($data).PHP_EOL;
$enc_data = openssl_encrypt($data, $my_method, $key, OPENSSL_NO_PADDING);
echo '密文:'.$enc_data.',长度为'.strlen($enc_data).PHP_EOL;
$hex = bin2hex($enc_data);
echo "密文十六进制:".$hex.',长度为'.strlen($hex).PHP_EOL;
$dec_data = openssl_decrypt($data, $my_method, $key, OPENSSL_NO_PADDING);
$dec_data = openssl_decrypt($enc_data, 'aes-128-ecb', $key, OPENSSL_NO_PADDING);
echo "解密:".$dec_data.PHP_EOL;
上面代码运行一下,结果如下图:
有时候一些同学在做跨语言加解密的时候,基本上都是栽在了填充上。具体表现就是 PHP 加密后让 Java 解密,然后发现解密失败;或者 Java 加密 PHP 解密结果也是挂了。这个时候首先检查一下 PADDING 这里,基本上都是这样的问题。
那么说了这么多,总结一下:
AES 和 DES 以及 3DES 这种加密方式被称为分组密码,分组密码每次只能加密固定长度的明文,所以如果明文很长的话,就需要轮流为每个分组明文进行加密,AES 的分组长度是 128bit,而 DES 的分组长度为 64bit;如果一旦需要对多个分组进行轮流加密,加入明文被分成了三个明文分组,那么就需要对三个明文进行迭代加密(粗暴理解就是轮流加密),然而会有很多种不同的迭代方式,这种不同的迭代方式专业名词就叫“模式”,这些模式有:ECB、CBC、OFB、CFB、CTR… …
PS:⚠️对明文进行分组的方式是固定的,唯一不同的就是分组长度不一样而已;模式是指对多个明文从第一个开始轮流加密到最后一个的这个过程,是怎么轮流执行的。