作为一个底层码农,其实加密解密平时是用不到的,毕竟平时写的就是cms 的curd, 但随着API的流行以及老大对代码质量的重视,还有和别的部门的交互,于是需要加解密啦。原文
hash( hamc ) 和 加解密
hash(hamc) 可以用的算法: md5, sha1, sha256, sha512
加解密:base64, aes
用途:
hash 和 hamc : 因为不能解密,所以一般用在签名过程中,怎么验证是否数据被用户修改了呢,用相同的密钥对于传递过来的数据重新加密一遍,和sign 比对,如果相等,那就没有人修改签名(注意这个密钥一定不能被知道,因为一旦被知道,劫持者可以根据密钥帮修改后的数据生成对应sign, 起不到保护的作用)
这篇文章是关于hamc ,hash 和md5 sha1 关系的讲解
md5: 经常用到,有时候我们需要固定长度的字符串,就可以用md5加密一下,md5 可以生成32位或者64位,依据不同的算法,js 好像经常出64位的,和php对接的时候要注意一下
sha1: 感觉和上面的md5类似(感觉就是两种不同的加密算法)
老李文章中对sha1 的描述:
其实跟前面的MD5同期的还有一个SHA1加密方式的,不过也是算法比较简单,所以这里就一笔带过吧。而这里即将要说到的
SHA256
和SHA512
都是来自于SHA2家族的加密函数,看名字可能你就猜的出来了。
hmac: hmac 其实就是用上面两种算法(sha256, md5),再加上一个密钥进行加密(这个密钥感觉又类似salt 的存在)
1 | php中md5, sha1 都有直接内置的方法, md5(), sha1(), 用的都是hash 方式,可以如下验证 |
hmac 比较官方的描述
散列消息身份验证码 Hashed Message Authentication Code 。它不是散列函数,而是采用了将MD5或SHA1散列函数与共享机密秘钥(与公钥/秘钥对不同)一起使用的消息身份验证机制。消息与秘钥组合并运行散列函数(md5或sha1),然后运行结果与秘钥组合并再次运行散列函数。
HMAC-SHA1简要来说,就是采用sha1算法,与HMAC机制相结合,制造出更加难以破解的加密串。
hash_hmac
在php中hash_hmac函数就能将HMAC和一部分哈希加密算法相结合起来实现HMAC-SHA1 HMAC-SHA256 HMAC-MD5等等算法。函数介绍如下:
string hash_hmac(string $algo, string $data, string $key, bool $raw_output = false)
algo:要使用的哈希算法名称,可以是上述提到的md5,sha1等
data:要进行哈希运算的消息,也就是需要加密的明文。
key:使用HMAC生成信息摘要是所使用的密钥。
raw_output:该参数为可选参数,默认为false,如果设为true,则返回原始二进制数据表示的信息摘要,否则返回16进制小写字符串格式表示的信息摘要(注意是16进制数,而非简单的字母加数字)。
php 中我们常用的还有下面的密码散列算法,四个方法
其中password_hash 默认算法是bcrypt (也不知道这个密码散列和上面的hash 有啥区别,从目前的bcrypt 上来看,就是把hash次数和salt 的内容都放在加密字符串中)
bcrypt
bcrypt : 关于bcrypt 这里有个解释很清楚, 感觉上其实就是另一种算法的hash
我之前很好奇这种hash, 怎么验证,也不能解密呀,当看到上面这张图片的时候,完全明白了,他的salt 存在了他加密后的字符串中,有了salt 就和以前的md5加解密一样了(注意上面各个字段的意思,10代表hash 10次),php现在经常用的一种加密密码的方式也很好用
1 | md5(‘123456’) e10adc3949ba59abbe56e057f20f883e |
1 | $str = password_hash(123, PASSWORD_DEFAULT); //默认 bcrypt, PASSWORD_DEFAULT 也代表了 PASSWORD_BCRYPT.二者都是1 |
urlencode
关于urlencode ,这篇文章说到的蛮好的,但是太长了 看看阮一峰老师的文章 , 我大致理解了下,首先php只有一个urlencode , 但是js 有两种,分别是 encodeUri 和 encodeUriComponent, php 的 相当于js 的后者,后者在加密url的时候会破坏url自身的完整性,他只适用于对url中的参数进行加密,因为会对:// 都进行转义,这样就不能当做url请求了
(ps: urlencode 啥作用,比如我发送了一个请求 http://www.baidu.com?url=http://chenye.com&name=11, 我希望的是url参数 中携带着后面的name ,而不是name 和 url 同级别的关系,这时候只能用urlencode 加密特定的参数啦, 记得在express 中好像默认会帮助我们decodeUriComponent 关于http url部分)
加解密:数据
base64:
感觉我平时用到最多的地方就是不能识别的二进制(乱码)转换成base64 或者 16进制 hex。
比如之前itbasic 上面图片转换成base64然后传给后端,后端php 再把base64解析成二进制,写入文件,保存成图片。
如果不用base64 上传图片我们还可以通过post 提交,http 协议是纯文本协议,我们怎么上传二进制的图片呢, http 有自己的机制,http 头部的mime 头也设定了该文件是图片,可以百度下怎么用node stream实现一个静态web 服务器。
php中很多加解密都是先生成二进制,然后转换成 base64 或者 16进制 (或者默认生成的就是 base64 )
(因为二进制可能含有不可见字符,复制粘贴都不方便)
这个评论更加深了纯文本协议的理解
base64_encode
base64_decode
base64 会让传输内容变大, https://www.zhihu.com/question/36306744, base64 出现原理和使用,这篇文章讲解的比较全面 (这个我就在图片处理的时候用到过,一些算法,比如aes, js 有 hex 或者 base64 的方式,感觉就是为了让过滤掉二进制中的特殊字符)
这种最原始的方式,因为不需要密钥,感觉不能算上加解密,确实在各个网站上也是这么感觉的
首先的区分下加解密和hash 的不同,对于md5() 这样加密之后理论上就不能解密的方式,我们一般称之为hash,在我们以往的网站编程中,对于用户的密码保存一般都是通过hash这种方式,通过把用户的密码拼接一个字符串,然后md5下生成的值保存到数据库中,下次用户登陆的时候,再次把用户输入的密码做上次同样的操作,对比和数据库中值是否一致,如果一致说明密码正确,否则密码错误。
加密我们平时生活中遇到最多的应该就是https 解析过程,因为非对称加密太消耗性能,所以我们传输过程需要对称加密(对称加密就是加密密钥和解密密钥一致,非对称反之),而对于密钥的传输我们用的是非对称(非对称因为密钥的不同又有公钥和私钥之分:公钥加密,私钥解密,数据不会被获取;私钥加密,公钥解密,数据不会被篡改)。
对称加密算法一般有AES(itbasic 请求datrix 那边用的就是这种,ecb 没有$iv 偏移量,cbc 有偏移量 ),DES,3DES,非对称有RSA
上图中,123456 就是对称加密过程中的密钥
最一开始的时候,我朝人民一般都是倾向于使用“天王盖地虎”,“宝塔镇河妖”这种加解密技术;然而,美帝用了一种叫做DES的技术进行对称加解密,这玩意一度成为业界通用的对称加解密技术,银行、五角大楼都爱用这玩意,可惜好景不长、世风日下、世态炎凉,这玩意的破解成本越来越低越来越低~~ 于是,为了续命,就又有一些白胡子老头给DES打补丁,缝缝补补搞出来一个玩意叫做3DES,继续用,又不是不能用…这个顾名思义就行了,别打我,真的:3DES就是用DES处理(注意是处理,我没说是加密)了三次的意思。就目前看来,3DES实际上用的可能也并不是十分广泛了,所以如果大家在选择对称加解密技术的时候,尽量避开DES和3DES就可以了。
呵呵,喜新厌旧的沙雕人类…虽然DES已经没人用了,但毕竟也是辉煌过,我觉得还是得动手表演一波儿。我们知道,在php7里,原来的mcrypt系列加解密已经被放弃掉了,官方建议我们使用openssl系列来进行加解密,所以确保你的PHP环境里安装了openssl标准扩展。
上面是原文中对于DES 和 3DES 的描述
1 | // 这个函数打印出来openssl支持的所有加密方法以及模式的组合 |
其中带有ede的,比如des-ede*这样的就表示是3DES
其中des 有如下几种
1 | des-cbc |
下面是如何通过des-ecb 的方式加密
1 | // 我们就选用des-ecb方法进行一次des加密 |
接下来我们说说aes 的加密方式
1 | $ava_methods = openssl_get_cipher_methods(); |
aes-128-ecb, 其中128代表密钥的长度,多余的长度会被直接街去掉(这个可以通过代码验证下,比如12345 用密钥1234567812345678和12345678123456789分别加密,看结果是否一样,最后再用不满128bit来加密123456781234567 ),128bit
128/8=16 位,aes-192-ecb, aes-256-ecb, 这其中的192 ,256,都是同样的意思。
最后一个参数是OPENSSL_RAW_DATA,如果选用这个option的话,经过加密后的数据会是奇怪的二进制数据,无法直接通过文本方式查看,所以要看的话必须先使用bin2hex函数处理一下。
上面这段话一定要注意理解,反正我第一次没有理解,导致第一次加密数据失败,网上的很多aes-128-ecb 这种加密代码的封装中都携带着自己的逻辑,所以会误导视野。上述opensssl_encrypt(),如果没有用最后一个参数,直接返回的结果是加密结果base64 的结果,如果有了,返回的是一段奇怪的二进制数据数据,这里我们可以理解成原始数据(加密数据最原始的样子),我们可以通过base64或者 bin2hex 转换城对应的base64格式或者16进制格式
1 | 明文: |
上面就是aes ecb 的处理过程
如果我们把加密的内容分组之后稍微修改下分组顺序再解密,可以得到原文分组顺序的不同
ecb太不靠谱了,竟然能修改,出现了cbc 模式
1 | $ava_methods = openssl_get_cipher_methods(); |
今天和我司员工对接cbc模式,~~不出意外,这条产品线的小伙伴也是在网上copy网上代码,结果直接没有给我iv向量,没有iv向量怎么可以解密呢,哈哈哈,其实之前我也不知道解密需要iv,我以为这个iv是包自带的,不需要我们管,但是当我在网上的在线平台使用的时候,发现cbc必须填写iv向量,最少还是16个字节,也就是16个英文字符(吐槽一下php自身生成的那个应该是二进制,需要转换一下,否则根本复制不下来,复制下来也不是16个字符长度),怎么办呢,这时候我逐渐意识到是不是加解密还需要iv,老李代码中反正有,
1 | $enc_data = openssl_encrypt( $data, $my_method, $key, OPENSSL_RAW_DATA, $iv ); // 加密 |
其实老李上面那段代码也写了注释,把iv当做一个随机的字符串,看来真的可能需要iv,这时候就开始翻他的go代码,但是他的go代码中没有传递iv变量这个值呀,看到了下面
1 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) |
~~对了,就是这个 这个key[:blocksS],这个就是iv,怪不得他不用传递,原来是约定的,
初始化向量(IV)
http://blog.csdn.net/xiaohu50/article/details/51682849
初始化向量(IV,Initialization Vector)是许多工作模式中用于随机化加密的一块数据,因此可以由相同的明文,相同的密钥产生不同的密文,而无需重新产生密钥,避免了通常相当复杂的这一过程。
初始化向量与密钥相比有不同的安全性需求,因此IV通常无须保密,然而在大多数情况中,不应当在使用同一密钥的情况下两次使用同一个IV。对于CBC和CFB,重用IV会导致泄露明文首个块的某些信息,亦包括两个不同消息中相同的前缀。对于OFB和CTR而言,重用IV会导致完全失去安全性。另外,在CBC模式中,IV在加密时必须是无法预测的;特别的,在许多实现中使用的产生IV的方法,例如SSL2.0使用的,即采用上一个消息的最后一块密文作为下一个消息的IV,是不安全的。
因为刚做了一个接口的用户信息加解密,加密至URL,然后那边后台解析,一开始IV不一致老是有一个参数解析不了,后来问同事是IV的问题,所以在这边做个记录,对这个还不是很了解。。。
如果加解密的IV不一致的话会导致数据不一致,所以解密失败,IV如果不变的话会导致不安全
上面是我在百度go cbc 加解密的时候看到的, 起初还以为go的cbc加密iv有什么特殊化,看来是我多虑了,就像很多时候aes加密本身不需要base64,但很多时候网上代码都加上了,因为默认约定这样好看点,或者字符长度确定了便于存储
这个传说中的iv向量终于他妈出现了!相对于ECB模式,CBC在加密之前多了一个XOR异或运算的环节,但是第一个明文分组和谁做异或呢?所以这个iv向量就是初始化后给第一个明文分组做XOR异或运算用的,第二个明文分组就与第一个密文分组做XOR异或运算,然后再加密得到第二个密文分组…依次重复下去。
xor 就是异或,相同的取0,不同的取1
总结:
AES和DES以及3DES这种加密方式被称为分组密码,分组密码每次只能加密固定长度的明文,所以如果明文很长的话,就需要轮流为每个分组明文进行加密,AES的分组长度是128bit,而DES的分组长度为64bit;如果一旦需要对多个分组进行轮流加密,加入明文被分成了三个明文分组,那么就需要对三个明文进行迭代加密(粗暴理解就是轮流加密),然而会有很多种不同的迭代方式,这种不同的迭代方式专业名词就叫“模式”,这些模式有:ECB、CBC、OFB、CFB、CTR… …
对明文进行分组的方式是固定的,唯一不同的就是分组长度不一样而已;模式是指对多个明文从第一个开始轮流加密到最后一个的这个过程,是怎么轮流执行的。
非对称加解密就不copy了,以后用到的时候再看吧
自己的总结:
其实上面用到的也就怎么用php 进行aes-128-ecb加密,加密出来的数据让别的语言可以解析出来
这其中会遇到很多问题,比如之前曾经遇到过但是没有记录的和java通信过程中的padding 填充问题,以后遇到了再补充吧。
顺带补充一下:
https://www.devglan.com/online-tools/aes-encryption-decryption
这个网站以及下面的php代码
1 | $data = '陈野'; |
默认的填充方式
pkcs5padding or pkcs7padding
https://blog.csdn.net/Stewart/article/details/52462273
这篇文章是关于pkcs5 和 7 padding 的比较,填充的是什么内容也有具体说。(感觉php 就是 5填充,因为没法指定blockSize的大小)
字符集(utf8)
http://tool.chacuo.net/cryptaes
上面这个网站才是真正意义上详细的加密,参数可以选择
今天着重研究了下pkcs5padding 和 pkcs7padding , 网上说的 5p 是7 p的子集, 7p的填充 1 到255 字节都可以, 5p的填充只能 1 到 8字节 ,可是我发现aes 是16字节分组的(不管是 128 还是 256 算法,都是16字节分组),按理说5p只能填充 1到8 那如果只有1个字符的话,怎么填充呢,是填充7个字节还是15字节呢,反正,当我填充15的时候php的openssl nopading 出来的数据和 openssl raw data 出来的数据是一样的,那么应该就是填充15了
网上看到7p和5p的区别大致上都是:5p是7p子集,5p只支持到8的block,7p可以支持到255,所以
这篇文章上说的aes 5p和7p是一样的, 感觉是错的,就是aes没有5p的padding
关于nopadding, 字面意思上就是不填充,对于aes,不填充的话,必须保证是16字节整数倍,否则会加密失败
关于zeropadding, php的 openssl 虽然有这个参数,但是要是不满16字节,仍然是会报错,既然这样,zeropadding 感觉和nopadding 就差不多了,实践了一下,果然zero_padding 就是base64了一下no_padding的结果
关于老版本的加密函数的转换,据说是老版本因为是zero_padding 导致数据出来和新版本不一样,zero_padding ,在末尾填充的是 chr(0), 代表0 这个ascii 对应的 字符,很多网上写的是 \0(很奇怪的是chr(\0) 能得到和 chr(0) 一样的字符,但是这个字符我们键盘是不能表示出来的, chr(\1) 和 chr(1) 却不一样) ,当我们拼凑到16字节之后,再解密,只需要过滤掉\0这个特殊字符就好了
1 | // 这是php 的7padding , |
注意一下填充时候的length, 我们计算的时候都是用的strlen, 并不是 中文字符的len
注意一下,我们没填充之前,已经是一个完整的string, 我们每次填充的内容,都是我们肉眼可以识别的字符,或者说是我们可以处理的字符,所以我们可以裁剪substr, 或者trim过滤掉我们添加的字符
总结:hash 和 加解密大致就是上面那些东西了,后面jwt 这些认证方式,其实就是 hash 和 加解密的实际运用,本质上并不是一个新的东西
现在重看, 阮一峰 老师的这篇jwt,讲解的真的好,jwt首先就可以看做一个token,自带token 相比较传统的cookie session,更便于扩展,由客户端自己维持登陆态。缺点token 签发了没法主动让他失效,除非到达失效时间,或者像我们一样添加额外逻辑,token 必须在redis中有对应内容,才能生效。
jwt token ,是由三个字符串base64加密而成, 因为base64解密不需要密钥,所以我们很容易根据base64解密出来
第一段是 alg + type. 算法 + 类型(JWT)
第二段是我们自定义的一个数组,当然可能有些属性是必须的,比如签发时间和过期时间
第三段是sign,为了防止第二段被别人修改,利用的加密算法是前面的alg (可能更确切的说是hash)
综上:所以事先起来感觉很简单,前面两段都是base64, 就是最后一个用hash,之后再base64, 有个问题就是这块的base64不是普通的base64, base64之后再替换三个字符,我的感觉是怕用户把这个字段连接在url后面当做get请求发送,base64中的参数影响到这个url的正确性
当我们明白过程之后,我们就很容易自己实现一个jwt,就不需要用网上的加密包了,我们可以看看php的 firebase/php-jwt 包,蛮简单的,默认hsh256, php 的hash_hmac 很容易实现。
到此为止 ,我们就剩rs256 公钥和私钥没有去研究,以后遇到了再去研究这个firebase的包(从下面也可以看出,hash_hmac 用来hash的函数,openssl 一般是用来加密的函数)