PHP的实现相当简单,两行代码就行,结果就是偶尔与其他平台不一致,原因就是屏蔽了很多细节。这只是一篇很无聊的笔记,略过。
如下面两行代码,虽然实现了目的,但效果并不理想
秘钥长度与拓展选择
PHP支持N多的加密算法,而这些算法则来源于Openssl,如果使用函数
openssl_get_cipher_methods()
会得到一份列表,过滤后AES相关的如下:
aes-128-cbc
aes-128-cbc-hmac-sha1
aes-128-cbc-hmac-sha256
aes-128-ccm
aes-128-cfb
aes-128-cfb1
aes-128-cfb8
aes-128-ctr
aes-128-ecb
aes-128-gcm
aes-128-ofb
aes-128-xts
aes-192-cbc
aes-192-ccm
aes-192-cfb
aes-192-cfb1
aes-192-cfb8
aes-192-ctr
aes-192-ecb
aes-192-gcm
aes-192-ofb
aes-256-cbc
aes-256-cbc-hmac-sha1
aes-256-cbc-hmac-sha256
aes-256-ccm
aes-256-cfb
aes-256-cfb1
aes-256-cfb8
aes-256-ctr
aes-256-ecb
aes-256-gcm
aes-256-ofb
aes-256-xts
id-aes128-CCM
id-aes128-GCM
id-aes128-wrap
id-aes192-CCM
id-aes192-GCM
id-aes192-wrap
id-aes256-CCM
id-aes256-GCM
id-aes256-wrap
也就是说,它该有的都有了,问题来了这里使用的是Openssl拓展,而不是mcrypt拓展,原因只有一点,官方对mcrypt_encrypt函数做了如下标记:
Warning: This function has been DEPRECATED as of PHP 7.1.0. Relying on this function is highly discouraged.
@see
向量的作用
看看具体的例子:
mcrypt_create_iv
为什么要做这样的事情,简单点说就是
安全考虑与保证加密结果的一致性
,复杂点说详见Wiki
初始向量
,那么这个向量需要保密么,当然是可选的,毕竟不是秘钥。一个更通俗的解释就是,在加密之前对区块进行初始化(如随机数算法给你一个种子每次就一样了)
那么如果向量不一致能解密么?
可以的,只是会导致解密的数据结果顺序异常,重新组合下就好了,所以保密的作用并不是特别大,实际呢,你试试就知道了。
秘钥长度的理解
AES 128指什么 AES的区块长度固定为128比特(16字节)
AES192 和 AES256类似,区块大了24字节和32字节
怎么加密的呢,一块一块加密,如果一段文本很长很长,就截断为对应长度再加密
单个区块如何加密?这个问题可以看看这里:
Link
这样就很好理解秘钥长度是多少了,对应的
AES 128 => 16个字符
AES 192 => 24个字符
AES 256 => 32个字符
如果要说,还有没有其他长度呢,当然有,不细说,没啥作用。
加密模式有哪些呢
CBC、ECB、CTR、OCF、CFB 安全性不一样,工作模式不一样,所需要的参数不一样,具体场景具体选择
CBC 优点:
不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准
CBC缺点:
不利于并行计算
需要初始化向量IV
具体内容可以看看:
http://www.cnblogs.com/happyhippy/archive/2006/12/23/601353.html
为什么会有填充算法的存在呢?假设我们执行如下AES加密,得出结果:
1) aes("loveyu") = C1955B/EdtfgKc0dWAwGFA==
2) aes("loveyu123456") = q+VvUfq79k0He8Rf9dEOrA==
解密的结果是怎么样的呢?如下
string(16) "loveyu " >>>>>> HEX 6c6f76657975000000000000000000
string(16) "loveyu123456 "
结果不算太出人意料,但得到了Mcrypt的默认填充算法,零填充,也能发现这样的算法有很大的局限性。
填充算法的种类
ANSI X.923
… | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 04 |
ISO 10126 ????
… | DD DD DD DD DD DD DD DD | DD DD DD DD 81 A6 23 04 |
PKCS7 与 PKCS5(两者一致除非有64的填充)
… | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
Zeropadding
… | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |
大多数时候,各个不同的平台使用的默认填充算法略有差异,使用起来差别不大,了解了细节就没啥难度了。对于PKCS7和PKCS5,主要在于长度不一样,而AES貌似没有64位的填充。
差异来自于哪里
使用了随机向量,PHP官方例子就这样写的
使用了AES128的算法
使用了32个长度的秘钥
使用了默认填充算法
会有哪些问题?
你怎么告诉别人你的向量是什么,MCRYPT_RAND 生成的么,还是Base64一下
你怎么解释你的AES128使用了32位的秘钥,大家都是PHP无所谓,如果对方用openssl解密怎么办
加密解密前后的结果不一致,非必现,偶尔有一样
日常使用会有哪些参数是必要?
AES 256
aes-cbc-256
PKCS7_TEXT
MD5(“loveyu”)
1234567890123456
为什么这里的向量长度是16而不是32?
echo
openssl_cipher_iv_length
(
"aes-256-cbc"
)
;
echo
@
mcrypt_get_iv_size
(
MCRYPT_RIJNDAEL_256
,
MCRYPT_MODE_CBC
)
;
结果为:16和32,差异来自这里
由于AES-CBC 总会输出16的整数倍数据,所以需要的向量大小也是16个字节
算法实现的不同会导致所需的条件不一致,而大量客户端均采用openssl的实现,所以使用16长度的向量
Aes-128-cbc zeroPadding
$mode = "aes-128-cbc";
$key = "1234567890123456";
$iv = "1234567890123456";
$padding = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
$data = "1234567890\0\0\0\0\0\0";
echo base64_encode(openssl_encrypt($data, $mode, $key, $padding, $iv)), "\n";
echo @base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv));
输出结果:
yOW03u6FmhY2rHTmiy7Ttg==
yOW03u6FmhY2rHTmiy7Ttg==
要通过openssl_encrypt实现zeroPadding并不容易,而且还不好转换
算法实现 nodejs
const
crypto
=
require
(
'crypto'
)
;
let mode
=
"aes-128-cbc"
;
let key
=
"1234567890123456"
;
let iv
=
"1234567890123456"
;
let data
=
"1234567890
\0
\0
\0
\0
\0
\0
"
;
let cipher
=
crypto.
createCipheriv
(
mode
,
key
,
iv
)
;
cipher.
setAutoPadding
(
false
)
;
let crypt
=
cipher.
update
(
data
,
'utf8'
,
'base64'
)
;
crypt
+=
cipher.
final
(
"base64"
)
;
console.
log
(
crypt
)
;
Output:yOW03u6FmhY2rHTmiy7Ttg==
当我们使用相同的参数配置时,得到的结果总是可靠的
Nodejs和PHP的mcrypt一样,会默认对数据进行填充,只是算法略有不同,为
PKCS
Aes-256-cbc加密的实现
AES 256
aes-256-cbc
PKCS7_TEXT
a4055e3e9879951930961af352066068
1234567890123456
ABCDEF0123456789
size:16
ABCDEF012345678
size:15
PHP openSSL加密结果:
8xpgNuUhQNdQLsuq5DqjAvws3S9hWNKV24kZij3+aSA= size:32
L5zn/8HqNmengnanZ9qQ4w== size:16
const
crypto
=
require
(
'crypto'
)
;
let mode
=
"aes-256-cbc"
;
let key
=
"a4055e3e9879951930961af352066068"
;
let iv
=
"1234567890123456"
;
// let data = "ABCDEF0123456789";
let data
=
"ABCDEF012345678"
;
let cipher
=
crypto.
createCipheriv
(
mode
,
key
,
iv
)
;
let crypt
=
cipher.
update
(
data
,
'utf8'
,
'base64'
)
;
crypt
+=
cipher.
final
(
"base64"
)
;
console.
log
(
crypt
)
;
NodeJS 加密结果:
8xpgNuUhQNdQLsuq5DqjAvws3S9hWNKV24kZij3+aSA= size:32
L5zn/8HqNmengnanZ9qQ4w== size:16
Mcrypt此时无结果,并扔给你一个警告:
mcrypt_encrypt(): Received initialization vector of size 16, but size 32 is required for this encryption mode.
Aes-256-cbc解密的实现
PHP版本
$mode
=
"aes-256-cbc"
;
$key
=
"a4055e3e9879951930961af352066068"
;
$iv
=
"1234567890123456"
;
$padding
=
PKCS7_TEXT
;
$data
=
"8xpgNuUhQNdQLsuq5DqjAvws3S9hWNKV24kZij3+aSA="
;
var_dump
(
openssl_decrypt
(
base64_decode
(
$data
)
,
$mode
,
$key
,
$padding
,
$iv
)
)
;
输出结果:string(16) “ABCDEF0123456789”
NodeJS版本
const
crypto
=
require
(
'crypto'
)
;
let mode
=
"aes-256-cbc"
;
let key
=
"a4055e3e9879951930961af352066068"
;
let iv
=
"1234567890123456"
;
let data
=
"L5zn/8HqNmengnanZ9qQ4w=="
;
let decipher
=
crypto.
createDecipheriv
(
mode
,
key
,
iv
)
;
let decode
=
decipher.
update
(
data
,
'base64'
,
'utf8'
)
;
decode
+=
decipher.
final
(
"utf8"
)
;
console.
log
(
decode
,
"size:"
+
decode.
length
)
;
输出结果:ABCDEF012345678 size:15
到这里,大部分实现都处理了,有很多细枝末节不兼容也正常,如果使用相同的实现和相同的算法能解决很多问题,而大部分时候标准的Openssl兼容性是极好的。
上一篇:
朋友圈还是关了吧
下一篇:
两天反穿武功山 Day1