此文為本人原創首發於 http://www.35coder.com/convert_encryption_codes_to_php/。
寫代碼的經歷中,總少不了與外部的程序對接,一旦有這樣的事,往往周期會很長,很麻煩,因為你要考慮的事會多了很多,其中安全性的加密解密就是重要的一項。寫代碼,可以出Bug,但逼格不能弱。什么是逼格?和別人對接一下,連加密解密都沒有,連驗證簽名都沒有,別人一眼就望穿你,這就是眼界的問題了。
這次的故事是對接一個大的支付系統,對方也是第一個對接我們,然后定了接口和加解密算法,給了個Java的Demo,問了聲,有沒有PHP的,沒有,歇菜,自己來吧。
代碼說多不多,說少不少,為了先說事,代碼放在最后面。
第一個坑:加密算法多多,你到底要鬧咋樣?
碼農兄弟們可以先下去看一眼代碼,然后說說它用了啥算法?
接口傳輸的安全性算法,首先要區分是加簽名還是加密?區別是,簽名+原文可以驗證收到的信息是否被篡改,不過別指望有了簽名就可以還原出原文來。加密就是不讓別人看到原文是啥,然后給把鑰匙,要讓接收的人解密看出原文來。兩者的算法基本上來說,就是完全不同的。
加密還分對稱非對稱。對稱有: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代碼跑不起來是正常的。哈哈
# JAVA

1 /** 2 * 3 */ 4 package com.chuangmi.foundation.lakala.service.impl; 5 6 import java.io.BufferedReader; 7 import java.io.FileInputStream; 8 import java.io.IOException; 9 import java.io.InputStream; 10 import java.io.UnsupportedEncodingException; 11 import java.security.KeyStore; 12 import java.security.PrivateKey; 13 import java.security.PublicKey; 14 import java.security.Security; 15 import java.security.Signature; 16 17 import javax.annotation.PostConstruct; 18 import javax.crypto.Cipher; 19 import javax.crypto.SecretKey; 20 import javax.crypto.spec.SecretKeySpec; 21 import javax.security.cert.X509Certificate; 22 import javax.servlet.http.HttpServletRequest; 23 24 import org.apache.commons.codec.binary.Base64; 25 import org.bouncycastle.jce.provider.BouncyCastleProvider; 26 import org.slf4j.Logger; 27 import org.slf4j.LoggerFactory; 28 import org.springframework.beans.factory.annotation.Value; 29 import org.springframework.stereotype.Service; 30 31 import com.alibaba.fastjson.JSON; 32 import com.alibaba.fastjson.JSONException; 33 import com.alibaba.fastjson.JSONObject; 34 import com.alibaba.fastjson.parser.Feature; 35 import com.chuangmi.foundation.lakala.service.SignService; 36 import com.chuangmi.foundation.lakala.service.models.CommReq; 37 import com.chuangmi.foundation.lakala.service.models.CommRequest; 38 import com.chuangmi.foundation.lakala.service.models.CommRes; 39 import com.chuangmi.foundation.lakala.service.models.CommResponse; 40 41 42 @Service 43 public class SignServiceImpl implements SignService { 44 45 private static final Logger logger = LoggerFactory.getLogger(SignService.class); 46 47 @Value("${cer.filePath}") 48 private String cerFilePath; 49 50 @Value("${key.filePath}") 51 private String keyFilePath; 52 53 @Value("${key.passWord}") 54 private String keyPassWord; 55 56 @Value("${key.alias}") 57 private String alias; 58 59 @Value("${encrypt.key}") 60 private String dESCORPKey; 61 62 /** 63 * 加密算法與填充方式 64 */ 65 public static final String AlGORITHM = "DESede/ECB/PKCS5Padding"; // 定義加密算法,可用 66 67 @PostConstruct 68 public void init(){ 69 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){ 70 71 System.out.println("security provider BC not found, will add provider"); 72 73 Security.addProvider(new BouncyCastleProvider()); 74 75 } 76 } 77 78 /** 79 * 加簽並加密需要發送的請求報文 80 * @param 接口報文 81 * @return 加簽后可以發送的json密文 82 * @throws JSONException 83 */ 84 @Override 85 public String signRequestJsonToSend(String jsonStr) throws JSONException { 86 JSONObject resultObj = null; 87 if(jsonStr.indexOf("\"head\"") >= 0) 88 { 89 CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField); 90 // 對報文體簽名 91 String signData = signData(req.getRequest()); 92 93 logger.info("加簽成功,原始報文:" + jsonStr + ",報文體簽名:" + signData); 94 95 req.getHead().setSignData(signData); 96 resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField); 97 } 98 else if(jsonStr.indexOf("\"comm\"") >= 0) 99 { 100 CommRequest req = JSON.parseObject(jsonStr, CommRequest.class, Feature.OrderedField); 101 // 對報文體簽名 102 String signData = signData(req.getData()); 103 104 logger.info("加簽成功,原始報文:" + jsonStr + ",報文體簽名:" + signData); 105 106 req.getComm().setSigntx(signData); 107 resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField); 108 } 109 110 String signedReq = String.valueOf(JSONObject.toJSON(resultObj)); 111 logger.info("加簽后的報文:" + signedReq); 112 113 //加密 114 byte[] key = Base64.decodeBase64(dESCORPKey); 115 byte[] encryptData = encrypt(key, signedReq.getBytes()); 116 117 String encryptStr = Base64.encodeBase64String(encryptData); 118 119 logger.info("加密成功,密文:" + encryptStr); 120 121 return encryptStr; 122 } 123 124 /** 125 * 加簽並加密需要發送的響應報文 126 * @param 接口報文 127 * @return 加簽后可以發送的json密文 128 * @throws JSONException 129 */ 130 @Override 131 public String signResponseJsonToSend(String jsonStr) throws JSONException { 132 JSONObject resultObj = null; 133 if(jsonStr.indexOf("\"head\"") >= 0) 134 { 135 CommRes response = JSON.parseObject(jsonStr, CommRes.class, Feature.OrderedField); 136 resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField); 137 } 138 else if(jsonStr.indexOf("\"comm\"") >= 0) 139 { 140 CommResponse response = JSON.parseObject(jsonStr, CommResponse.class, Feature.OrderedField); 141 // 對報文體簽名 142 String signData = signData(response.getData()); 143 144 logger.info("加簽成功,原始報文:" + jsonStr + ",報文體簽名:" + signData); 145 146 response.getComm().setSigntx(signData); 147 resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField); 148 } 149 150 String signedReq = String.valueOf(JSONObject.toJSON(resultObj)); 151 logger.info("加簽后的響應報文:" + signedReq); 152 153 //加密 154 byte[] key = Base64.decodeBase64(dESCORPKey); 155 byte[] encryptData = encrypt(key, signedReq.getBytes()); 156 157 String encryptStr = Base64.encodeBase64String(encryptData); 158 159 logger.info("加密成功的響應報文,密文:" + encryptStr); 160 161 return encryptStr; 162 } 163 164 /** 165 * 從request提取json data,並解密,驗簽, 驗簽成功則返回data,否則返回null 166 * @param request 167 * @return 168 * @throws IOException 169 * @throws JSONException 170 */ 171 @Override 172 public String verifyReceivedRequest(HttpServletRequest request) throws IOException, JSONException { 173 String json = extractJson(request); 174 logger.info("接收報文密文:" + json); 175 176 return verifyRequestJson(json); 177 } 178 179 /** 180 * 對收到的請求json解密,驗簽, 驗簽成功則返回data,否則返回null 181 * @param json 182 * @return 183 * @throws UnsupportedEncodingException 184 * @throws JSONException 185 */ 186 @Override 187 public String verifyRequestJson(String json) throws UnsupportedEncodingException, JSONException { 188 //解密 189 byte[] key = Base64.decodeBase64(dESCORPKey); 190 byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json)); 191 String orig = new String(decryptBytes, "UTF-8"); 192 logger.info("【收到的報文請求】接收報文:" + orig); 193 194 // 驗簽 195 JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField); 196 String reqStr = String.valueOf(JSONObject.toJSON(obj)); 197 if(reqStr.indexOf("\"comm\"") >= 0) 198 { 199 CommRequest req = JSON.parseObject(reqStr, CommRequest.class, Feature.OrderedField); 200 String signtx = req.getComm().getSigntx(); 201 202 logger.info("報文中的簽名:" + signtx); 203 boolean nRet = verifyData(req.getData(), signtx); 204 if(nRet) 205 { 206 req.getComm().setSigntx(""); 207 return JSON.toJSONString(req); 208 } 209 else 210 { 211 return null; 212 } 213 } 214 else if(reqStr.indexOf("\"head\"") >= 0) 215 { 216 CommReq req = JSON.parseObject(reqStr, CommReq.class, Feature.OrderedField); 217 String signData = req.getHead().getSignData(); 218 219 logger.info("報文中的簽名:" + signData); 220 boolean nRet = verifyData(req.getRequest(), signData); 221 if(nRet) 222 { 223 req.getHead().setSignData(""); 224 return JSON.toJSONString(req); 225 } 226 else 227 { 228 return null; 229 } 230 } 231 else 232 { 233 return null; 234 } 235 } 236 237 /** 238 * 對響應的報文json解密,驗簽, 驗簽成功則返回data,否則返回null 239 * @param json 240 * @return 241 * @throws UnsupportedEncodingException 242 * @throws JSONException 243 */ 244 @Override 245 public String verifyResponseJson(String json) throws UnsupportedEncodingException, JSONException { 246 //解密 247 byte[] key = Base64.decodeBase64(dESCORPKey); 248 byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json)); 249 String orig = new String(decryptBytes, "UTF-8"); 250 logger.info("【收到的響應報文】報文:" + orig); 251 252 // 驗簽 253 JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField); 254 String reqStr = String.valueOf(JSONObject.toJSON(obj)); 255 if(reqStr.indexOf("\"comm\"") >= 0) 256 { 257 CommResponse response = JSON.parseObject(reqStr, CommResponse.class, Feature.OrderedField); 258 String signtx = response.getComm().getSigntx(); 259 260 logger.info("報文中的簽名:" + signtx); 261 boolean nRet = verifyData(response.getData(), signtx); 262 if(nRet) 263 { 264 response.getComm().setSigntx(""); 265 return JSON.toJSONString(response); 266 } 267 else 268 { 269 return null; 270 } 271 } 272 else if(reqStr.indexOf("\"head\"") >= 0) 273 { 274 CommRes response = JSON.parseObject(reqStr, CommRes.class, Feature.OrderedField); 275 return JSON.toJSONString(response); 276 } 277 else 278 { 279 return null; 280 } 281 } 282 283 public String extractJson(HttpServletRequest request) throws IOException { 284 //用於接收對方的jsonString 285 StringBuilder jsonString = new StringBuilder(); 286 BufferedReader reader = request.getReader(); 287 try { 288 String line; 289 while ((line = reader.readLine()) != null) { 290 jsonString.append(line); 291 } 292 } finally { 293 reader.close(); 294 } 295 String data = jsonString.toString(); 296 return data; 297 } 298 299 /* 使用私鑰簽名,並返回密文 300 * @param param 需要進行簽名的數據 301 * @return 簽名 302 */ 303 private String signData(String param) 304 { 305 InputStream inputStream = null; 306 try { 307 308 String store_password = keyPassWord;// 密鑰庫密碼 309 String password = keyPassWord;// 私鑰密碼 310 String keyAlias = alias;// 別名 311 // a. 創建針對jks文件的輸入流 312 313 inputStream = new FileInputStream(keyFilePath);// CA 文件名 如: D://tmp/encrypt.jks 314 // input = getClass().getClassLoader().getResourceAsStream(keyFile); 315 // 如果制定classpath下面的證書文件 316 317 // b. 創建KeyStore實例 (store_password密鑰庫密碼) 318 KeyStore keyStore = KeyStore.getInstance("JKS"); 319 keyStore.load(inputStream, store_password.toCharArray()); 320 321 // c. 獲取私鑰 (keyAlias 為私鑰別名,password為私鑰密碼) 322 PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray()); 323 324 // 實例化一個用SHA算法進行散列,用RSA算法進行加密的Signature. 325 Signature dsa = Signature.getInstance("SHA1withRSA"); 326 // 加載加密散列碼用的私鑰 327 dsa.initSign(privateKey); 328 // 進行散列,對產生的散列碼進行加密並返回 329 byte[] p = param.getBytes(); 330 dsa.update(p); 331 332 return Base64.encodeBase64String(dsa.sign());// 進行簽名, 加密后的也是二進制的,但是返回給調用方是字符串,將byte[]轉為base64編碼 333 334 } catch (Exception gse) { 335 336 gse.printStackTrace(); 337 return null; 338 339 } finally { 340 341 try { 342 if (inputStream != null) 343 inputStream.close();// 判斷 344 } catch (Exception e) { 345 e.printStackTrace(); 346 } 347 } 348 349 } 350 351 /* 用公鑰證書對字符串進行簽名驗證 352 * @param urlParam 需要進行簽名驗證的數據 353 * @param signParam 編碼后的簽名 354 * @return 校驗簽名,true為正確 false為錯誤 355 */ 356 private boolean verifyData(String urlParam, String signParam) 357 { 358 boolean verifies = false; 359 360 InputStream in = null; 361 362 try { 363 364 // a. 創建針對cer文件的輸入流 365 InputStream inputStream = new FileInputStream(cerFilePath);// CA 文件名 如: D://tmp/cerfile.p7b 366 // input = getClass().getClassLoader().getResourceAsStream(keyFile); 367 // 如果制定classpath下面的證書文件 368 369 // b. 創建KeyStore實例 (store_password密鑰庫密碼) 370 X509Certificate cert = X509Certificate.getInstance(inputStream); 371 372 // c. 獲取公鑰 (keyAlias 為公鑰別名) 373 PublicKey pubKey = cert.getPublicKey(); 374 375 if (pubKey != null) { 376 // d. 公鑰進行驗簽 377 // 獲取Signature實例,指定簽名算法(與之前一致) 378 Signature dsa = Signature.getInstance("SHA1withRSA"); 379 // 加載公鑰 380 dsa.initVerify(pubKey); 381 // 更新原數據 382 dsa.update(urlParam.getBytes()); 383 384 // 公鑰驗簽(true-驗簽通過;false-驗簽失敗) 385 verifies = dsa.verify(Base64.decodeBase64(signParam));// 將簽名數據從base64編碼字符串轉回字節數組 386 } 387 388 } catch (Exception gse) { 389 gse.printStackTrace(); 390 } finally { 391 392 try { 393 if (in != null) 394 in.close();// 判斷 395 } catch (Exception e) { 396 e.printStackTrace(); 397 } 398 } 399 return verifies; 400 401 } 402 403 404 // DES,DESede,Blowfish 405 /** 406 * 使用3des加密明文 407 * 408 * @param byte[] key: 密鑰 409 * @param byte[] src: 明文 410 * 411 */ 412 private byte[] encrypt(byte[] key, byte[] src) { 413 try { 414 415 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){ 416 417 System.out.println("security provider BC not found, will add provider"); 418 419 Security.addProvider(new BouncyCastleProvider()); 420 421 } 422 423 // 生成密鑰 424 SecretKey deskey = new SecretKeySpec(key, AlGORITHM); 425 // 加密 426 Cipher c1 = Cipher.getInstance(AlGORITHM); 427 c1.init(Cipher.ENCRYPT_MODE, deskey); 428 return c1.doFinal(src);// 在單一方面的加密或解密 429 } catch (java.security.NoSuchAlgorithmException e1) { 430 e1.printStackTrace(); 431 } catch (javax.crypto.NoSuchPaddingException e2) { 432 e2.printStackTrace(); 433 } catch (java.lang.Exception e3) { 434 e3.printStackTrace(); 435 } 436 return null; 437 } 438 439 /** 440 * 使用3des解密密文 441 * 442 * @param byte[] key: 密鑰 443 * @param byte[] src: 密文 444 * 445 */ 446 private byte[] decrypt(byte[] keybyte, byte[] src) { 447 try { 448 449 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){ 450 451 System.out.println("security provider BC not found, will add provider"); 452 453 Security.addProvider(new BouncyCastleProvider()); 454 455 } 456 457 // 生成密鑰 458 SecretKey deskey = new SecretKeySpec(keybyte, AlGORITHM); 459 // 解密 460 Cipher c1 = Cipher.getInstance(AlGORITHM); 461 c1.init(Cipher.DECRYPT_MODE, deskey); 462 return c1.doFinal(src); 463 } catch (java.security.NoSuchAlgorithmException e1) { 464 e1.printStackTrace(); 465 } catch (javax.crypto.NoSuchPaddingException e2) { 466 e2.printStackTrace(); 467 } catch (java.lang.Exception e3) { 468 e3.printStackTrace(); 469 } 470 471 return null; 472 } 473 474 public static void main(String[] args) throws JSONException { 475 476 InputStream inputStream = null; 477 try { 478 479 String store_password = "123456";// 密鑰庫密碼 480 String password = "123456";// 私鑰密碼 481 String keyAlias = "www.lakala.com";// 別名 482 // a. 創建針對jks文件的輸入流 483 484 inputStream = new FileInputStream("/Users/rinson/eclipse-workspace/lakala/lakala-server/asdc.keystore");// CA 文件名 如: D://tmp/encrypt.jks 485 // input = getClass().getClassLoader().getResourceAsStream(keyFile); 486 // 如果制定classpath下面的證書文件 487 488 // b. 創建KeyStore實例 (store_password密鑰庫密碼) 489 KeyStore keyStore = KeyStore.getInstance("JKS"); 490 keyStore.load(inputStream, store_password.toCharArray()); 491 492 // c. 獲取私鑰 (keyAlias 為私鑰別名,password為私鑰密碼) 493 PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray()); 494 495 // 實例化一個用SHA算法進行散列,用RSA算法進行加密的Signature. 496 Signature dsa = Signature.getInstance("SHA1withRSA"); 497 // 加載加密散列碼用的私鑰 498 dsa.initSign(privateKey); 499 String param = "XXXXX"; 500 // 進行散列,對產生的散列碼進行加密並返回 501 dsa.update(param .getBytes()); 502 503 System.out.println(Base64.encodeBase64String(dsa.sign()));// 進行簽名, 加密后的也是二進制的,但是返回給調用方是字符串,將byte[]轉為base64編碼 504 505 } catch (Exception gse) { 506 507 gse.printStackTrace(); 508 509 } finally { 510 511 try { 512 if (inputStream != null) 513 inputStream.close();// 判斷 514 } catch (Exception e) { 515 e.printStackTrace(); 516 } 517 } 518 } 519 }
# PHP

1 <?php 2 3 namespace Common\Lib\Lakala; 4 5 class SignService 6 { 7 private $publicPemFilePath=''; 8 private $privatePemFilePath=''; 9 private $dESCORPKey = ''; 10 11 //初始化 12 public function __construct() 13 { 14 $this->publicPemFilePath = C('lakala_cer_filePath'); 15 $this->privatePemFilePath=C('lakala_key_filePath'); 16 $this->dESCORPKey = C('lakala_encrypt_key'); 17 } 18 19 /** 20 * 加簽並加密需要發送的請求報文 21 * @param $head 是java類CommReqHead,需在調用時傳入 22 * @param $data 是具體的請求參數數組 23 * 24 */ 25 public function ToLakala($head,$data,$url){ 26 //CommReq 27 $ret = [ 28 'head'=>$head, 29 'request'=>$data, 30 ]; 31 $ret = json_encode($ret); 32 //會對整個請求body加簽加密,返回字符串body體 33 $params = $this->signRequestJsonToSend($ret); 34 35 //http request 36 $ch = curl_init($url); 37 curl_setopt($ch, CURLOPT_POST, 1); 38 curl_setopt($ch, CURLOPT_HEADER, 0); 39 curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); 40 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 41 curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); 42 curl_setopt($ch, CURLOPT_TIMEOUT, 30); 43 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($data))); 44 curl_setopt($ch, CURLOPT_POSTFIELDS, $params); 45 $result = curl_exec($ch); 46 curl_close($ch); 47 $result = $params; 48 //驗證返回結果 49 $response = $this->verifyResponseJson($result); 50 //結果返回 51 return $response; 52 } 53 54 55 public function FromLakala(){ 56 $lakalaSign = new SignService(); 57 $params = I(''); 58 $params = $this->verifyRequestJson($params); 59 return $params; 60 } 61 62 public function FromLakalaResponse($head,$response){ 63 $ret = [ 64 'head'=>$head, 65 'response'=>$response, 66 ]; 67 $res = $this->signResponseJsonToSend($ret); 68 return $res; 69 } 70 71 /** 72 * 加簽並加密需要發送的請求報文 73 * @param $jsonStr 接口報文(String類型) 74 * @return 加簽后可以發送的json密文 75 */ 76 public function signRequestJsonToSend($jsonStr) 77 { 78 if(strpos($jsonStr,"\"head\"")!= false) 79 { 80 $req = json_decode(trim($jsonStr), true); 81 ksort($req); 82 // 對報文體簽名 83 $signData = $this->signData($req['request']); 84 $req['head']['signData']= $signData; 85 $req['request']=json_encode($req['request'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 86 ksort($req['head']); 87 $resultObj = $req; 88 89 } 90 else if(strpos($jsonStr,"\"comm\"")!= false) 91 { 92 $req = json_decode(trim($jsonStr), true); 93 ksort($req); 94 // 對報文體簽名 95 $signData = $this->signData($req['data']); 96 $req['comm']['signtx']=$signData; 97 $req['data']=json_encode($req['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 98 ksort($req['head']); 99 $resultObj = $req; 100 } 101 102 $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 103 104 //logger.info("加簽后的報文:" + signedReq); 105 //此處直接放入要加密的數據 106 $key = $this->dESCORPKey; 107 $encryptData = self::encrypt($key,$signedReq); 108 //logger.info("加密成功,密文:" + encryptStr); 109 110 return $encryptData; 111 } 112 113 /** 114 * 加簽並加密需要發送的響應報文 115 * @param $jsonStr 接口報文(String類型) 116 * @return 加簽后可以發送的json密文 117 */ 118 public function signResponseJsonToSend($jsonStr) { 119 if(strpos($jsonStr,"\"head\"")!= false) 120 { 121 $response = json_decode(trim($jsonStr), true); 122 $resultObj = json_decode(json_encode($response)); 123 } 124 else if(strpos($jsonStr,"\"comm\"")!= false) 125 { 126 $response = json_decode(trim($jsonStr), true); 127 ksort($response); 128 // 對報文體簽名 129 $signData = $this->signData($response['data']); 130 131 //logger.info("加簽成功,原始報文:" + jsonStr + ",報文體簽名:" + signData); 132 $response['comm']['signTx']=$signData; 133 $response['data']=json_encode($response['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 134 ksort($response['comm']); 135 $resultObj = $response; 136 } 137 138 $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 139 //logger.info("加簽后的響應報文:" + signedReq); 140 141 $key = $this->$dESCORPKey; 142 $encryptData = self::encrypt($key, $signedReq); 143 144 //logger.info("加密成功的響應報文,密文:" + encryptStr); 145 return $encryptData; 146 } 147 148 /** 149 * 對響應的報文json解密,驗簽, 驗簽成功則返回data,否則返回null 150 * @param json (String) 151 * @return (String) 152 */ 153 public function verifyResponseJson($json) { 154 //解密 155 $key = $this->dESCORPKey; 156 $decryptBytes = self::decrypt($key, $json); 157 158 //logger.info("【收到的響應報文】報文:" + orig); 159 160 // 驗簽 161 $obj = json_decode($decryptBytes); 162 $reqStr = json_encode($obj); 163 if(strpos($reqStr,"\"comm\"")!= false) 164 { 165 $response = json_decode($reqStr,true); 166 $signtx = $response['comm']['signtx']; 167 168 //logger.info("報文中的簽名:" + signtx); 169 $nRet = $this->verifyData($response['data'], $signtx); 170 if($nRet) 171 { 172 $response['comm']['signtx']=""; 173 return json_encode($response); 174 } 175 else 176 { 177 return null; 178 } 179 } 180 else if(strpos($reqStr,"\"head\"")!= false) 181 { 182 return $reqStr; 183 } 184 else 185 { 186 return null; 187 } 188 } 189 190 /** 191 * 對收到的請求json解密,驗簽, 驗簽成功則返回data,否則返回null 192 * @param json (String) 193 * @return (String) 194 */ 195 public function verifyRequestJson($json) { 196 //解密 197 $key = $this->dESCORPKey; 198 $decryptBytes = self::decrypt($key, $json); 199 200 // 驗簽 201 $obj = json_decode($decryptBytes); 202 $reqStr = json_encode($obj); 203 if(strpos($reqStr,"\"comm\"")!= false) 204 { 205 $req = json_decode($reqStr,true); 206 ksort($req); 207 $signtx = $req['comm']['signtx']; 208 209 //logger.info("報文中的簽名:" + signtx); 210 $nRet = $this->verifyData($req['data'], $signtx); 211 if($nRet) 212 { 213 $req['comm']['signtx']=""; 214 return json_encode($req); 215 } 216 else 217 { 218 return null; 219 } 220 } 221 else if(strpos($reqStr,"\"head\"")!= false) 222 { 223 $req = json_decode($reqStr,true); 224 ksort($req); 225 $signData = $req['head']['signData']; 226 //logger.info("報文中的簽名:" + signData); 227 $nRet = $this->verifyData($req['request'], $signData); 228 return $nRet; 229 if($nRet) 230 { 231 $req['head']['signData']=""; 232 return json_encode($req); 233 } 234 else 235 { 236 return null; 237 } 238 } 239 else 240 { 241 return null; 242 } 243 } 244 /* 使用私鑰簽名,並返回密文 245 * @param param 需要進行簽名的數據(Array) 246 * @return 簽名(加簽字符串String) 247 */ 248 private function signData($param) 249 { 250 $content = json_encode($param,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); 251 $privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath)); 252 openssl_sign($content, $signature, $privateKey); 253 openssl_free_key($privateKey); 254 return base64_encode($signature); 255 256 } 257 258 /* 用公鑰證書對字符串進行簽名驗證 259 * @param urlParam 需要進行簽名驗證的數據(直接從解密報文中取出的String) 260 * @param signParam 編碼后的簽名(直接從解密報文中取出的String) 261 * @return 校驗簽名,true為正確 false為錯誤 262 */ 263 private function verifyData($urlParam,$signParam) 264 { 265 $signature = base64_decode($signParam); 266 $pubkeyid = openssl_pkey_get_public(file_get_contents($publicPemFilePath)); 267 // state whether signature is okay or not 268 $verifies = openssl_verify($urlParam, $signature, $pubkeyid); 269 return $verifies; 270 } 271 272 /** 273 * @param $key獲取的密鑰字符串(直接從配置文件中取出) 274 * @param $data (字符串) 275 * @return string 276 */ 277 public function encrypt($key,$data){ 278 $decode_key = base64_decode($key);//此處需要BASE64解碼(變為2進制) 279 $encData = openssl_encrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA); 280 $encData = base64_encode($encData); 281 return $encData; 282 } 283 284 /** 285 * @param $key獲取的密鑰字符串(直接從配置文件中取出) 286 * @param $data (字符串) 287 * @return string 288 */ 289 public function decrypt($key,$data){ 290 $decode_key = base64_decode($key);//此處需要BASE64解碼(變為2進制) 291 $data = base64_decode($data);//此處需要BASE64解碼(變為2進制) 292 $decData = openssl_decrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA); 293 return $decData; 294 } 295 296 } 297 298 ?>