写代码的经历中,总少不了与外部的程序对接,一旦有这样的事,往往周期会很长,很麻烦,因为你要考虑的事会多了很多,其中安全性的加密解密就是重要的一项。写代码,可以出Bug,但逼格不能弱。什么是逼格?和别人对接一下,连加密解密都没有,连验证签名都没有,别人一眼就望穿你,这就是眼界的问题了。 这次的故事是对接一个大的支付系统,对方也是第一个对接我们,然后定了接口和加解密算法,给了个Java的Demo,问了声,有没有PHP的,没有,歇菜,自己来吧。 代码说多不多,说少不少,为了先说事,代码放在最后面。 第一个坑:加密算法多多,你到底要闹咋样? 码农兄弟们可以先下去看一眼代码,然后说说它用了啥算法? 接口传输的安全性算法,首先要区分杏彩源码搭建出售企娥3266397597是加签名还是加密?区别是,签名+原文可以验证收到的信息是否被篡改,不过别指望有了签名就可以还原出原文来。加密就是不让别人看到原文是啥,然后给把钥匙,要让接收的人解密看出原文来。两者的算法基本上来说,就是完全不同的。 加密还分对称非对称。对称有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等,非对称有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法) 还有,你以为拿个公钥就够了?当然不是,还要一对。更坑的是,可能有不同的格式。对方给了我一个keystore格式的,发觉php完全不支持,想办法转成了pem格式的。 常见的密钥格式:jks,jce,p12,pfx,bks,ubr等等 常见的证书文件格式:cer,crt,rsa,p7b,p7r,p7c,pem,p10,csr等等 最后还有一点,这次碰到的算法具体的参数。 我这次遇到的是3DES加密,AlGORITHM = "DESede/ECB/PKCS5Padding";
,后面的类型和填充方式都不能差一点。 第二个坑:到底什么是密钥? 你会说这个很简单啊 Java里就是像这样:?PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());? php里就是像这样:?$privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath));? 你以为你以为的就是你以为的吗?前面说了,即使算法一样,密钥格式不一样,开发语言一样,用法也完全不一样。 上面的只是格式不同,下面的还有编码的不同: 看起来我从代码里读出的密码是这个,但其实送入算法中的可不是,还要做一个Base64转换,如果你送入了错误的,会永远困在迷惘中。 1 $this->dESCORPKey = C(‘lakala_encrypt_key‘); 2 $key = $this->$dESCORPKey; 3 $encryptData = self::encrypt($key, $signedReq); 4 ... 5 public function encrypt($key,$data){ 6 $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制) 7 $encData = openssl_encrypt($data, ‘DES-EDE3‘, $decode_key, OPENSSL_RAW_DATA); 8 $encData = base64_encode($encData); 9 return $encData; 10 } ? PS:网上有在线加密解密算法的工具,往往和代码出来的结果不一致,除了各种参数都需要写对以外,特别注意密码(密钥)的输入格式,要不要Base64编码或者解码。 第三个坑:带中文的字符串格式 看一眼下面的代码,你就会知道,php里有很多json_encode,json_decode,java代码里有很多getByte()这样转换成字节的操作,一个经典的问题就来了,你如果传入了中文,中文按什么格式进行编码?编码不同,决定了加密算法操作时二进制的不同,也决定了最终输出的不同。 在写代码的时候查阅了java的getByte()方法,默认值竟然是读取机器的字符格式!!!所以,写代码的时候一定要注意,最好加上具体格式,不要用默认值,要这么写:getByte("UTF-8")
,否则换了机器表现不一样,你会死的很难看。 第四个坑:语言的问题 虽然上帝说人人平等,但现实远远复杂的多。 虽然加密解密本质上说就是一种运算变换,什么语言都可以实现的,可你不会从底层开始搞的,那就涉及语言的问题。 最简单的例子:java里具体运算时都会把String转换成byte进行操作。PHP就不行。 加密算法这个玩意虽然复杂,不过前人已经给我们造了很多轮子了,网上随便一搜就可以找到很多资料,问题是没有成系统,特别各个语言间的轮子差异还蛮大的。如何做适配,在网上的资料非常的难找。 PHP里加密方法还有mcrypt和openssl两套,互相之间的写法还差异蛮大的,让人头疼。 举个栗子(这里的Java解析使用了fastjson): 1 java中: 2 CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField); 3 PHP中: 4 $req = json_decode(trim($jsonStr), true); 5 ksort($req); ? 看起来很像了吧?才不是呢!以下是输入的Json { "head": { "serviceSn": "B5D79F38B96040B7B992B6BE329D9975", "serviceId": "MPBF001", "channelId": "05", "inputSource": "I002", "opId": "", "requestTime": "20180628142105", "versionId": "1.0.0", "businessChannel": "LKLZFLLF" }, "request": { "userId":"40012345678", "userName": "AA", "userMobile": "18675529912", "idNo": "110101198609096078" } } ? 问题出在具体的类型定义,以及Json解析的深度 JAVA中有定义具体按哪个类型解析 1 public class CommReq implements Serializable { 2 private static final long serialVersionUID = 1L; 3 private CommReqHead head; 4 private String request; 5 } JAVA这种强类型语言,必须定义类型,将request定义成String型,导致JSON解析出来的结果: "request":"{\"userId\":\"40012345678\",\"userName\": \"AA\", \"userMobile\": \"18675529912\", \"idNo\": \"110101198609096078\" } "; 而PHP是弱类型语言,直接嵌套进去也解析出来了 "request": {"userId":"40012345678","userName": "AA", "userMobile": "18675529912", "idNo": "110101198609096078" } } 如果PHP要和JAVA真正保持一致(因为一旦不一致,加密结果就不一样了) 1 $req = json_decode(trim($jsonStr), true); 2 ksort($req); 3 req[‘request‘]=json_encode(req[‘request‘]); ? 前面也提到了密钥类型的问题,其实也和语言有关,PHP只支持PEM格式,所以,还用工具对keystore进行了转换,转换之后发现几个密码都已经不需要了。生成公钥PEM和私钥PEM加上加密解密Key就可以了。 小结 回顾来看,其实只是解决了很小的一个问题,将一段JAVA代码转换成了PHP代码,甚至中间复杂的算法细节都调用原来就有的模块,更不用怀疑这些模块写的算法的正确性,但调试这样一个东西,却的的确确花费了非常大的精力。技术没有任何中间地带,只有行或者不行,容不得半分作假。开发必须要注重细节,细节到位了才不会出Bug,这点在加密解密这件事上,尤其的明显,输入差一个字符,输出完全不同。开发没有很容易的事,只有我做过,我熟悉的事。 ? 代码在这里哦。钥匙就不提供了,这样直接copy代码跑不起来是正常的。哈哈
原文地址:http://blog.51cto.com/13883524/2149009