虽然HTTPS的成本已经大幅度下降了,但是仍然会有一些需要再加密一层的传输需求。
本文将实现在客户端和服务端之间,使用CryptoJS和mcrypt进行加密传输的方法。
其实个人也没有搞明白所有细节,只不过是调通了 PHP MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC 与 JS AES ZeroPadding 之间的加解密通信而已。
用途,大概就是在一些环境下,在无法依赖 https 的情况下,需要传输一些需要加密的数据时使用。之前看了腾讯网页登录的方法,账号+密码+验证码 做多次哈希后传输,也可以保证无法破解和回放攻击。不过既然有AES这种高级加密方法,何尝不使用一下呢。
准备环境
- 客户端:支持JS并可以发起Ajax请求即可,CryptoJS库
- 服务端:HTTP服务,PHP,必须安装 php-mcrypt
建议
网络中传输的数据,使用base64编码之后传输
实现 PHP加密 —- JS解密
PHP代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php $content = 'this is the text here'; $key = 'encryptionkey'; $iv = 'fXyFiQCfgiKcyuVNCGoILQ=='; while (strlen($key) < 16) { $key = $key."\0"; } if (strlen($iv) != strlen(base64_encode(mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND)))) { exit(); } $iv_base64_decode = base64_decode($iv); $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $content, MCRYPT_MODE_CBC, $iv_base64_decode); $rst = base64_encode($ciphertext); echo $rst; |
得到:F0QOnmU79TFx6IhFLsUnfSvwJvmKP5uzU2h5jJH+MPY=
JS代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
ciphertext = 'F0QOnmU79TFx6IhFLsUnfSvwJvmKP5uzU2h5jJH+MPY='; function js_decrpyt(ciphertext) { var result; var key = 'encryptionkey'; key = format_key(key); var iv = 'fXyFiQCfgiKcyuVNCGoILQ=='; key = CryptoJS.enc.Utf8.parse(key); iv = CryptoJS.enc.Base64.parse(iv); plaintext = CryptoJS.AES.decrypt(ciphertext, key, {iv: iv, padding: CryptoJS.pad.ZeroPadding}); result = CryptoJS.enc.Utf8.stringify(plaintext); console.log(result); return result; } function format_key(key) { while (key.length < 16) { key = key + '\u0000'; } return key; } |
至此完成 PHP端加密 —- JS端解密 的过程。
实现 JS加密 —- PHP解密
JS代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function js_encrpyt(content) { var result; var key = 'encryptionkey'; key = format_key(key); var iv = 'fXyFiQCfgiKcyuVNCGoILQ=='; key = CryptoJS.enc.Utf8.parse(key); iv = CryptoJS.enc.Base64.parse(iv); var ciphertext = CryptoJS.AES.encrypt(content, key, {iv: iv, padding: CryptoJS.pad.ZeroPadding}); result = ciphertext.toString(); console.log(result); return result; } function format_key(key) { while (key.length < 16) { key = key + '\u0000'; } return key; } |
得到:F0QOnmU79TFx6IhFLsUnfSvwJvmKP5uzU2h5jJH+MPY=
PHP代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php $ciphertext = 'F0QOnmU79TFx6IhFLsUnfSvwJvmKP5uzU2h5jJH+MPY=';; $key = 'encryptionkey'; $iv = 'fXyFiQCfgiKcyuVNCGoILQ=='; while (strlen($key) < 16) { $key = $key."\0"; } if (strlen($iv) != strlen(base64_encode(mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND)))) { exit(); } $iv_base64_decode = base64_decode($iv); $plaintext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($ciphertext), MCRYPT_MODE_CBC, $iv_base64_decode); echo $plaintext; |
至此完成 JS端加密 —- PHP端解密 的过程。
注意事项
1. JS与PHP的加密算法
严格地说,AES和Rijndael加密法并不完全一样,所以CryptoJS支持的AES与PHP中MCRYPT的RIJNDAEL也不是完全相同的。
PHP的Rijndael可以手动强制key长度,并且在php 5.4之前还会自动补齐。
JS的CryptoJS中,key的长度是根据传入的key自动判断的。
2. 关于’\0′
PHP 5.6 之后,不再接受无效长度的 key 和 iv 参数。 如果参数长度无效,则 mcrypt_decrypt() 函数会产生警告并且返回 FALSE。
对于PHP 5.6 之前版本中,对于长度不足的密钥和初始向量会在其后自动补齐 ‘\0’ 使其达到有效长度。
所以对于已经使用PHP7版本的我们,现在要手动补齐’\0’了。因为MCRYPT_RIJNDAEL_128的key长度和iv长度都是16,所以例子里偷懒了,没有根据加密方法做判断,写死了16。
而在JS的CryptoJS中,完全不会自动补齐,我们每次都需要检查 key 和 iv 的长度,并手动补齐。
代码和示例
暂无
参考资料
CryptoJS: https://code.google.com/archive/p/crypto-js/
RIJNDAEL: 维基百科Rijndael加密法
3 comments
萃香西瓜
2017 年 1 月 18 日 在 下午 1:15 (UTC 8) Link to this comment
其实我对于基于js的加解密用途,感觉比较困惑
毕竟js都是明文的,如果使用对称加密(比如文中AES),秘钥和算法都是明文公开的甚至直接写到js文件里的
如果使用非对称加密(比如RSA),只要监听http也可以做到还原整个场景
这种情况下只要外部想攻击,基本上没有门槛可言。不过对于扫描类型的撒网攻击,或许可以保护自己不会在第一轮就立马被人随便攻破。
所以比较好奇CryptoJS的使用场景
石樱灯笼
2017 年 1 月 18 日 在 下午 2:35 (UTC 8) Link to this comment
加密并不能确保安全,加密永远只是在一定程度上增加破解难度而已。对于像https这种情况,只不过是引入了一个第三方认证机构,来确保没有中间人攻击。在没有第三方认证机构或者第三方认证机构无赖的情况下(cnnic和wosign),https也只不过是装饰品。
CryptoJS在网站网页上,因为双端秘钥和算法都是可见的,所以无法抵抗劫持级别的攻击,但对于网络上的简单连接劫持是非常有效的。网络上很多流量劫持都是没有动作分析功能的,无法自动分析你的算法文件和秘钥文件,而人工分析一般只有针对性的攻击才会做,需要看代码,确认秘钥和算法,有人力成本。如果进一步做JS代码混淆,想要破解就不是很方便了。
虽然我这次研究这个东西只是为了利用iv防止回放攻击。
另外对于客户端应用,CryptoJS将不再是从网络传输,而是与安装程序一同分发,代码将不透明,这是目前客户端加密的主要用途。
姜辰
2017 年 1 月 23 日 在 下午 9:25 (UTC 8) Link to this comment
这样的加密解密的过程,对服务器的消耗?
门外汉,还是不做评论仅仅膜拜比较好。