摘要:工商銀行中國(guó)工商銀行提現(xiàn)支付域名項(xiàng)目名接口我們第一次使用支付請(qǐng)求對(duì)象,是為了將其生成簽名原串。第一次加密是將不包含屬性值的支付請(qǐng)求對(duì)象封裝的簽名原串和我們生成的私鑰共同加密成簽名字符串,放進(jìn)支付請(qǐng)求對(duì)象中的屬性中。
引題
【備注】簽名原串的源碼放在git上了,請(qǐng)大家參看:項(xiàng)目源碼
筆者最近在做支付、調(diào)用天貓優(yōu)惠券、綁定銀行卡相關(guān)的業(yè)務(wù),在這些業(yè)務(wù)中,我們都需要將數(shù)據(jù)加密。然而,數(shù)據(jù)的加密方式不同,綁定銀行卡用md5加密,這不涉及金錢上的往來,使用MD5加密沒問題。然而,一旦涉及了金錢,比如支付業(yè)務(wù),那么,這種方式并不好。因?yàn)楹诳秃苡锌赡芙厝?bào)文,修改密碼后盜取金額,因而,我們采用RSA的加密方式。這里以連連支付為講解示例。
講解連連支付之前,需要介紹非對(duì)稱加密算法。
非對(duì)稱加密我們?cè)谕ㄟ^ip傳輸數(shù)據(jù)時(shí),如果采用對(duì)稱加密,即一個(gè)主站和用戶之間可以使用相同的密鑰對(duì)傳輸內(nèi)容進(jìn)行加密,主站和用戶之間是知道彼此的密鑰。然而,ip報(bào)文就好比在官道上運(yùn)輸糧草、黃金、物資,雖然相對(duì)來說比較安全,但很容易被人盯上。密鑰本身如果被盜,那么,再復(fù)雜的密鑰也無濟(jì)于事。自然的想法是在密鑰上再加密,這就是遞歸的窮舉問題了。
這并不是最好的辦法,有沒有一種方式,即報(bào)文被截取之后,黑客依然無計(jì)可施。這就出現(xiàn)了一種全新的算法,即RSA加密算法。它把密碼革命性地分成公鑰和私鑰,由于兩個(gè)密鑰并不相同。
首先通過openssl genrsa -out rsa_private_key.pem 1028 生成pkcs1格式的1028個(gè)字節(jié)的私鑰(適合PHP等前端),即:
MIICXgIBAAKBgQsyeT57L81ie1Lm1hEb7RVa9JszkhmuNAu7garMbmHInXRJBkqj
GWMqRFp0KQWYGGRYRqG59XVXYub3KuTE/9FamifG+d+EyUNFbwcG9H1g+kSnm868
MhBp1wr2zec/s47Bbx0fbtRYPXeQrkdzz6oAxVLoNDp+7eRixvlTe6c0LwIDAQAB
AoGBCx+1vBD9yHlSM2YIvS6VNmYKJDXzq3eZVR6PD3PRJWv8oQ37JiMqkY3oIkTM
jDYx5V6drQXliRGru/FJt8TOsNM7nmu1sGQH2Ae6WPHnqWHDJpSlEQ/rSzAv4XYx
WZtYWq/6ToT25foJ7e+BL2uMKKAq/64deiLt+K7hQWUi6nTBAkEDlqt/j/cYEGnT
eY2GBRTbLLLJGZ+c3hSHSS84n82l0U2qnNA3zrxshZc7hU6NTPrrQzmjIl0MGimP
VbDNwC59qQJBAx7IQx6ec1OoNA+chz1Xh/ipklcximKdPNW6QByEZ8B6lp74l2SJ
aISeqe+WCHvnk6FVpOTqC3rWmQWsVje42hcCQQGOZL9EKq8X5xzbuOEm8P1/q+UE
JLD9qj9lIIJY4vEHDLxxluas1A/n+0bHr+IdQS+njqZNb7ag3ecYDT2dG0xJAkB6
Fv/zUSKtebsjW7hsDtHwlvKQMzlEo2XmAQbFlRNKnzIgcDyrmDkKdDnjLdp0Hcw5
z55ZgtBoYR6YeGPhNnbXAkEC/hvl31bulAqTGdZsVYY6FEVn9TXbsF9mTFSyFbGH
XjjILiDu9dQasPVBP5vLNt+ClGJJJ36ffVaX7FSbHVs7iA==
然而,我們后臺(tái)使用的是Java,需要將其轉(zhuǎn)為pkcs8格式的私鑰,即:
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBCzJ5PnsvzWJ7UubW
ERvtFVr0mzOSGa40C7uBqsxuYciddEkGSqMZYypEWnQpBZgYZFhGobn1dVdi5vcq
5MT/0VqaJ8b534TJQ0VvBwb0fWD6RKebzrwyEGnXCvbN5z+zjsFvHR9u1Fg9d5Cu
R3PPqgDFUug0On7t5GLG+VN7pzQvAgMBAAECgYELH7W8EP3IeVIzZgi9LpU2Zgok
NfOrd5lVHo8Pc9Ela/yhDfsmIyqRjegiRMyMNjHlXp2tBeWJEau78Um3xM6w0zue
a7WwZAfYB7pY8eepYcMmlKURD+tLMC/hdjFZm1har/pOhPbl+gnt74Eva4wooCr/
rh16Iu34ruFBZSLqdMECQQOWq3+P9xgQadN5jYYFFNsssskZn5zeFIdJLzifzaXR
Taqc0DfOvGyFlzuFTo1M+utDOaMiXQwaKY9VsM3ALn2pAkEDHshDHp5zU6g0D5yH
PVeH+KmSVzGKYp081bpAHIRnwHqWnviXZIlohJ6p75YIe+eToVWk5OoLetaZBaxW
N7jaFwJBAY5kv0QqrxfnHNu44Sbw/X+r5QQksP2qP2Ugglji8QcMvHGW5qzUD+f7
Rsev4h1BL6eOpk1vtqDd5xgNPZ0bTEkCQHoW//NRIq15uyNbuGwO0fCW8pAzOUSj
ZeYBBsWVE0qfMiBwPKuYOQp0OeMt2nQdzDnPnlmC0GhhHph4Y+E2dtcCQQL+G+Xf
Vu6UCpMZ1mxVhjoURWf1NduwX2ZMVLIVsYdeOMguIO711Bqw9UE/m8s234KUYkkn
fp99VpfsVJsdWzuI
我們將pkcs8格式的私鑰轉(zhuǎn)化為公鑰,即
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQsyeT57L81ie1Lm1hEb7RVa9Jsz
khmuNAu7garMbmHInXRJBkqjGWMqRFp0KQWYGGRYRqG59XVXYub3KuTE/9FamifG
+d+EyUNFbwcG9H1g+kSnm868MhBp1wr2zec/s47Bbx0fbtRYPXeQrkdzz6oAxVLo
NDp+7eRixvlTe6c0LwIDAQAB
你會(huì)發(fā)現(xiàn),不論是pkcs1的私鑰,還是pkcs8格式的私鑰,其與公鑰并不相等。因?yàn)椋?這就是所謂的非對(duì)稱加密。私鑰是用來對(duì)公鑰加密信息解密的,需要保密。而公鑰是對(duì)信息進(jìn)行加密,任何人都可以知道,包括hack。我們?cè)趥鬏數(shù)臅r(shí)候,雙方都遵守這個(gè)契約:
甲該訴乙,使用RSA算法進(jìn)行加密,乙說,好的。
甲和乙分別根據(jù)RSA生成一對(duì)密鑰,互相發(fā)送公鑰。
甲使用乙的公鑰給乙加密報(bào)文信息。
乙收到信息,并用自己的密鑰進(jìn)行解密。
乙使用同樣的方式給甲發(fā)送消息,甲使用相同方式進(jìn)行解密。
其實(shí),我們?cè)谑褂眠B連支付時(shí),也遵守這個(gè)規(guī)則。我們首先生成一對(duì)公私鑰。將生成的公鑰上傳到連連商戶站的后臺(tái),連連那邊就接收到了我們的公鑰。我們?cè)購倪B連商戶站的后臺(tái)下載連連公鑰,我們將私鑰和簽名原串共同加密生成簽名,這就是加簽。加簽后的數(shù)據(jù)和連連公鑰再次加密,通過HttpClient調(diào)用連連支付的接口,將加簽后的信息傳遞給連連。連連驗(yàn)簽通過后,給我們回傳他們加簽后的簽名信息,我們這邊進(jìn)行驗(yàn)簽。這樣的加密方式是比較安全的。
上面提到了兩次加密和簽名原串,那么,簽名原串到底是什么?
簽名原串、加簽我們調(diào)用連連支付時(shí),肯定涉及到金額,商戶號(hào),簽名方式,銀行卡名稱的。這些就是支付請(qǐng)求對(duì)象,假設(shè),我們現(xiàn)在有一個(gè)請(qǐng)求支付的javabean類:
/** * 這是支付父類的bean */ public class BaseRequestBean { private String oid_partner; private String sign; private String sign_type; } @Data @AllArgsConstructor @NoArgsConstructor public class PaymentRequestBean extends BaseRequestBean { private String api_version; private String card_no; private String flag_card; private String notify_url; private String no_order; private String dt_order; public String money_order; private String acct_name; private String bank_name; private String info_order; private String memo; private String brabank_name; }
在上面的父類中有一個(gè)sign屬性,這里存儲(chǔ)的是簽名原串加密后的數(shù)據(jù)。
什么是簽名原串?
即上面各個(gè)屬性(但不包含sign屬性)的值,按照一定格式,拼接而成的字符串。
為什么除去sign屬性?
sign屬性存儲(chǔ)的將簽名原串加密后的字符串。
我們首先要講支付請(qǐng)求對(duì)象賦值,如圖所示:
我們通過一系列的操作,將其轉(zhuǎn)變?yōu)槿缦赂袷降淖址?,按照首字母由低到高的方式排名,如果首字母相同,再比較第二個(gè),以此類推。。。具體怎么生成的,下面會(huì)提到。
acct_name=jack&api_version=1.2&bank_name=工商銀行&brabank_name=中國(guó)工商銀行&card_no=123456677756&dt_order=20190302023423&flag_card=1212121&info_order=提現(xiàn)支付&memo=ceshi&money_order=12.00¬ify_url=https://域名/項(xiàng)目名/接口&no_o...
我們第一次使用支付請(qǐng)求對(duì)象,是為了將其生成簽名原串。簽名原串和我們生成的pkcs8格式的私鑰加簽,第一次加密(加簽)涉及到我們自己生成的私鑰,如代碼所示:
/** * 簽名處理 * * @param prikeyvalue:私鑰 * @param sign_str:簽名原串 * @return */ public static String sign(String prikeyvalue, String sign_str) { try { //【1】獲取私鑰 KeyFactory keyFactory = KeyFactory.getInstance(PaymentConstant.SIGN_TYPE); //將BASE64編碼的私鑰字符串進(jìn)行解碼 BASE64Decoder decoder = new BASE64Decoder(); byte[] encodeByte = decoder.decodeBuffer(prikeyvalue); //生成私鑰對(duì)象 PrivateKey privatekey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodeByte)); //【2】使用私鑰 // 獲取Signature實(shí)例,指定簽名算法(本例使用SHA1WithRSA) Signature signature = Signature.getInstance(PaymentConstant.MD5_WITH_RSA); //加載私鑰 signature.initSign(privatekey); //更新待簽名的數(shù)據(jù) signature.update(sign_str.getBytes(BaseConstant.CHARSET)); //進(jìn)行簽名 byte[] signed = signature.sign(); //將加密后的字節(jié)數(shù)組,轉(zhuǎn)換成BASE64編碼的字符串,作為最終的簽名數(shù)據(jù) return new String(org.apache.commons.codec.binary.Base64.encodeBase64(signed)); } catch (Exception e) { e.printStackTrace(); } return null; }
我們將加簽后的數(shù)據(jù)放置在請(qǐng)求對(duì)象的sign中,如圖所示
我們第二次使用支付請(qǐng)求對(duì)象,這次對(duì)象中的sign已經(jīng)存值。我們此時(shí)可以將加簽后的請(qǐng)求對(duì)象和連連公鑰共同加密。這次涉及到的是我們從商戶站下載下來的連連公鑰。調(diào)用連連的支付接口,如圖所示:
書寫簽名原串我們上面一直在提簽名原串,其實(shí)怎么生成的呢,我采用的是選擇排序算法,如代碼所示:
public static void main(String[] args) { JSONObject jsonObject = new JSONObject(); jsonObject.put("oid_partner", "12121212121"); jsonObject.put("api_version", "1.2"); jsonObject.put("sign_type", "rsa"); jsonObject.put("flag_card", "1212121"); jsonObject.put("notify_url", "https://域名/項(xiàng)目名/接口"); jsonObject.put("no_order", "20190302023423zby"); jsonObject.put("dt_order", "20190302023423"); jsonObject.put("money_order", "12.00"); jsonObject.put("card_no", "123456677756"); jsonObject.put("acct_name", "jack"); jsonObject.put("bank_name", "工商銀行"); jsonObject.put("info_order", "提現(xiàn)支付"); jsonObject.put("memo", "ceshi"); jsonObject.put("brabank_name", "中國(guó)工商銀行"); System.out.println(concatString(jsonObject,null)); } /** * Created By zby on 15:07 2019/3/6 * 拼接字符串 */ public static String concatString(JSONObject jsonObject, String type) { Listkeys = keysSort(jsonObject); if (null == keys && keys.size() <= 0) { return null; } if (StringUtils.isBlank(type)) { type = "&"; } StringBuilder concatBuilder = new StringBuilder(); for (String key : keys) { concatBuilder.append(key + "=" + jsonObject.getString(key) + type); } return StringUtils.substring(concatBuilder.toString(), 0, concatBuilder.length() - 1); } /** * Created By zby on 14:55 2019/3/6 * 獲取排序后的值 */ public static List keysSort(JSONObject jsonObject) { if (null == jsonObject && jsonObject.size() <= 0) { return null; } List keyList = new ArrayList<>(jsonObject.keySet()); if (null != keyList && keyList.size() > 0) { for (int i = 0; i < keyList.size() - 1; i++) { for (int j = 0; j < keyList.size() - (i + 1); j++) { String currKey = keyList.get(j); String afterKey = keyList.get(j + 1); if (StringUtils.isBlank(currKey) && StringUtils.isBlank(afterKey)) { throw new RuntimeException("當(dāng)前值為空currKey=" + currKey + ",或者下一個(gè)值afterKey=" + afterKey); } char[] currKeyChars = currKey.toCharArray(); for (int k = 0; k < currKeyChars.length; k++) { //保證當(dāng)前字符是有效字符,即在26個(gè)字母之中,不在,直接放到后面 if (validateLetter(currKeyChars[k])) { // 小于,不用排序,直接跳出 if (currKeyChars[k] < afterKey.charAt(k)) { break; // 等于,跳過此循環(huán) } else if (currKeyChars[k] == afterKey.charAt(k)) { continue; // 大于,看清而定 } else { if (validateLetter(afterKey.charAt(k))) { keyList.set(j, afterKey); keyList.set(j + 1, currKey); } break; } } else { keyList.set(j, afterKey); keyList.set(j + 1, currKey); break; } } } } } return keyList; } /** * Created By zby on 14:52 2019/3/6 * 驗(yàn)證字符 */ public static boolean validateLetter(Character c) { if (c == null) { return false; } return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z"); }
生成結(jié)果為:
acct_name=jack&api_version=1.2&bank_name=工商銀行&brabank_name=中國(guó)工商銀行&card_no=123456677756&dt_order=20190302023423&flag_card=1212121&info_order=提現(xiàn)支付&memo=ceshi&money_order=12.00¬ify_url=https://域名/項(xiàng)目名/接口&no_o...
支付并不復(fù)雜,說白了,無非是兩次加密。
第一次加密是將不包含sign屬性值的支付請(qǐng)求對(duì)象封裝的簽名原串和我們生成的私鑰共同加密成簽名字符串,放進(jìn)支付請(qǐng)求對(duì)象中的sign屬性中。
第二次加密是我們使用連連支付的加密算法,將第一次加密的后支付請(qǐng)求對(duì)象和連連公鑰共同加密,封裝為pay_load,調(diào)用連連支付的的接口請(qǐng)求支付。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/73589.html
摘要:錢可以存儲(chǔ)在自己的余額中,這就相當(dāng)于微信錢包,余額可以提現(xiàn)到銀行卡的中。我們的第三方支付平臺(tái)是連連支付,杭州的一家公司。私鑰怎么加簽每個(gè)公司的加簽方式是不一樣的,支付寶有支付寶的加簽方式,微信有微信的加簽方式。 導(dǎo)讀 筆者在校期間,通過自學(xué)java。學(xué)校里也開過這門課,但是,講的都是一些基礎(chǔ),比如java的表達(dá)式、基本類型、自定義類型等等。也都是很基礎(chǔ)的東西,就連lambda表達(dá)式都沒...
摘要:防止在傳輸過程中被截獲破解。將上面參數(shù)組成的字符串加上安全校驗(yàn)碼組成待簽名的數(shù)據(jù),安全校驗(yàn)碼通過平臺(tái)分配,假設(shè)安全校驗(yàn)碼為,那計(jì)算的原串為偽代碼只有兩方內(nèi)部保存,確保傳遞的參數(shù)沒有被第三方篡改。 轉(zhuǎn)載請(qǐng)注明出處 http://www.paraller.com 原文排版地址 點(diǎn)擊跳轉(zhuǎn) 第三方支付 1、簡(jiǎn)單加密 目的是為了保證上傳的參數(shù)信息沒有被篡改,主要分成三部分 接口參數(shù) : 需...
摘要:?jiǎn)栴}定義最長(zhǎng)回文子串問題給定一個(gè)字符串,求它的最長(zhǎng)回文子串長(zhǎng)度。可以采用動(dòng)態(tài)規(guī)劃,列舉回文串的起點(diǎn)或者終點(diǎn)來解最長(zhǎng)回文串問題,無需討論串長(zhǎng)度的奇偶性。 0. 問題定義 最長(zhǎng)回文子串問題:給定一個(gè)字符串,求它的最長(zhǎng)回文子串長(zhǎng)度。 如果一個(gè)字符串正著讀和反著讀是一樣的,那它就是回文串。下面是一些回文串的實(shí)例: 12321 a aba abba aaaa tatt...
摘要:字符串方法還是比較強(qiáng)大的,做個(gè)筆記總結(jié)。美元符號(hào)連字符與正則表達(dá)式相匹配的子字符串。美元符號(hào)單引號(hào)位于匹配子字符串右側(cè)的文本。否則,第至個(gè)參數(shù)對(duì)應(yīng)為捕獲組匹配項(xiàng),倒數(shù)的兩個(gè)參數(shù)為匹配下標(biāo),原串。函數(shù)的匹配返回值,作為每次的匹配替換值。 javascript字符串方法replace還是比較強(qiáng)大的,做個(gè)筆記總結(jié)。 第一個(gè)參數(shù) replace的第一個(gè)參數(shù)為字符串或者正則表達(dá)式。第二個(gè)參數(shù)...
閱讀 1530·2021-09-22 15:52
閱讀 1622·2019-08-30 15:44
閱讀 956·2019-08-30 14:24
閱讀 2761·2019-08-30 13:06
閱讀 2770·2019-08-26 13:45
閱讀 2838·2019-08-26 13:43
閱讀 1083·2019-08-26 12:01
閱讀 1576·2019-08-26 11:56