喜欢PHP迷。先介绍一下问题的背景:本人是一名PHPer。有一天公司的Java找到我,让我帮忙写一个界面的demo。我想:‘我最喜欢写界面了,来来来!',于是Java自带了Java版的demo,粗略看了下,具体涉及到以下几点:(不想理解的可以看最后一部分,就是这样,就是中心思想)md5加密:在java中定义hashMap,存储userid,然后使用toJSONString将其转换为Json字符串,然后将Json用md5Hex加密,然后放入hashMap中。map参数格式转换:将map中的数据转换成String,将key和value数据拼接起来,拼接成一个string,这个string的具体要求如下:'按照的ASCII码从小到大排序参数名,并使用URL键值对格式(即key1=value1&key2=value2...)构造字符串说说遇到的坑:这个PHPer比较渣,我不知道Java中的hashMap是干什么用的,但是我知道它最后做了一件事:JSONObject.toJSONString(body),没错,已经转换成字符串,所以我有如下代码:(我不负责贴图,不知道对不对)md5加密Java版Mapbody=newHashMap<>();body.put("用户名",用户名);//post请求体为json格式,对json格式进行md5加密StringpostBody=JSONObject.toJSONString(body);StringbodyMd=DigestUtils.md5Hex(postBody);PHP版本$body=["userId"=>$userId];$postbody=json_encode($body);$bodyMd=md5($postbody);以上应该是我们之间做的一件事吧?应该是吧,反正转成Json,转成Md5,接下来就是map转string,再贴两段代码:(本人不负责发帖,不知道对不对正确与否)(根据参数名ASCII码从小到大排序,使用URL键值对格式(即key1=value1&key2=value2...)构造字符串signPlainText)map参数格式转换:Java版本MapresultMap=newTreeMap<>();for(Map.Entryentry:map.entrySet()){Stringkey=(String)entry.getKey();对象值=entry.getValue();如果(StringUtils.isEmpty(key)||value==null){继续;}resultMap.put(key,value);}StringBuilderbuff=newStringBuilder();for(Map.Entryentry:resultMap.entrySet()){buff.append(entry.getKey()).append("=").append(entry.getValue()).append("&");}StringsignPlanText=buff.toString();如果(StringUtils.isNotEmpty(signPlanText)){signPlanText=signPlanText.substring(0,signPlanText.length()-1);}返回signPlanText;PHP版本$signPlanText='';ksort($参数);foreach($paramsas$key=>$param){if(empty($key)||$param==null){继续;$signPlanText.=$key.'='.$param.'&';}if($signPlanText){$signPlanText=substr($signPlanText,0,strlen($signPlanText)-1);}返回$signPlanText;遇到的问题:因为Java中的Treemap是按照自然值排序排序的,但是PHP中好像没有,所以决定用Ksort来排序,不知道对不对!应该是正确的吧?各位大神拍砖~HmacSHA256签名验证:Java版Stringsignature="";尝试{Macsha256HMAC=Mac.getInstance(HMACSHA256);SecretKeySpecsecretkey=newSecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8),HMACSHA256);sha256HMAC.init(密钥);byte[]bytes=sha256HMAC.doFinal(plainText.getBytes(StandardCharsets.UTF_8));签名=Base64.encodeBase64URLSafeString(字节);}catch(Exceptione){e.printStackTrace();}返回签名;PHP版本(是吗?看起来不是问题吗?)$hmac_sha256_str=base64_encode(hash_hmac("sha256",$signPlanText,$appSecret));$signature=urlencode($hmac_sha256_str);返回$签名;还在想,PHP几句代码就能解决?果然是世界上最好的语言',(大佬,我加了引号)终于在23:35翻译了上面的Java代码,放了一首歌,美美的睡了一觉,心想明天可以美美的交作业了~(滴滴滴滴等~等地理地理~~)坑来了,运行上面的代码,你会发现界面提示签名错误!!错误永远是错误!!为什么???明明是一样的!困惑!!继续阅读...看PHP文档中的hash_hmac语句:stringhash_hmac(string$algo,string$data,string$key[,bool$raw_output=FALSE])algo要使用的哈希算法的名称,例如:“md5”、“sha256”、“haval160,4”等。如何获取支持的算法列表,请参阅hash_algos()。data要散列的消息。key是使用HMAC生成消息摘要时使用的密钥。raw_output设置为TRUE以输出原始二进制数据,设置为FALSE以输出小写十六进制字符串。Java中sha256HMAC后得到的值是二进制的,所以,PHP也需要转为二进制,所以改进为如下代码:hash_hmac("sha256",$signPlanText,$appSecret,true);//结果为binary格式还没有结束,最重要的出现了:java中的Base64.encodeBase64URLSafeString(bytes)会将特殊字符'+/'替换为'-_',并且会去掉'='但是!!PHP不会!!!PHP没有提供url安全的base64编码功能,所以需要自己动手!我还是不够好所以,出现了下面的代码,也是最终的解决方案:("sha256",$signPlanText,$appSecret,true));返回$签名;}/***替换特殊符号*/publicstaticfunctionbase64UrlEncode($str){returnrtrim(strtr(base64_encode($str),'+/','-_'),'=');}这时候你会发现签名是对的,原来生活这么美好,怪我太年轻了。同时有朋友说:1.有两套不同的标准,Ios一套,Java一套2.PHP中的url_encode函数分为两种:urlencode和rawurlencode的区别:urlencode的结果和rawurlencode在处理字母数字、特殊符号、中文时是一样的,唯一不同的是空格的处理,urlencode处理成“+”,rawurlencode处理成%20。总结就是:php的base64_encode和base64_decode是用来转换url的,不符合要求,需要自己实现base64url_dec方法odepublicstaticfunctionbase64url_decode($data){returnbase64_decode(str_pad(strtr($data,'-_','+/'),strlen($data)%4,'=',STR_PAD_RIGHT));}base64url_encodepublicstaticfunctionbase64url_encode($data){returnrtrim(strtr(base64_encode($data),'+/','-_'),'=');