記一次Java加密加簽算法到php的坑


此文為本人原創首發於 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 }
View Code

 

# 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 ?>
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM