本文轉自:http://blog.csdn.net/niemeiquan/article/details/7937280
網銀的接口不難,但是開通網銀接口需要不小的費用。 相關資源匯編下載:
最近關注項目中在線支付,所以看一下文檔,在線支付應用開發:
基本所有的在線支付均采用以下方式:
客戶點擊結帳時將關於訂單的信息和貨幣信息,相應的信息URL,經過md5或其他方式發送(可能Socket和Http或Https)支付平台(塊錢,paypal或支付寶等),支付平臺處理完畢時根據相應URL,返回相關的信息(付款信息,訂單信息,驗證信息).
在實際操作Money的問題人們一向關注他的安全性等問題,同時本人習慣在通過http方式訪問非外網時采用Commons-httpclient的post發送實現,簡單方便,所以采用此種實現:具體看以下API和原代碼:
網銀在線支付API接口:
商戶>>>>>>網銀在線支付:
<form method=post action="https://pay.chinaebank.cn/select_bank">
<input type=hidden name=v_mid value="1001"> 商戶編號
<input type=hidden name=v_oid value="19990720-1001-000001234"> 訂單編號
<input type=hidden name=v_amount value="13.45"> 訂單總金額
<input type=hidden name=v_moneytype value="0"> 幣種
<input type=hidden name=v_url value="http://domain/program">
支付動作完成后返回到該url,支付結果以POST方式發送
<input type=hidden name=v_md5info value="1630DC083D70A1E8AF60F49C143A7B95"> 訂單MD5校驗碼
<input type="hidden" name="remark1 " value="">備注字段1
<input type="hidden" name="remark2" value="">備注字段2
<input type=hidden name=v_rcvname value="張三"> 收貨人姓名
<input type=hidden name=v_rcvaddr value="北京海淀"> 收貨人地址
<input type=hidden name=v_rcvtel value="68475566"> 收貨人電話
<input type=hidden name=v_rcvpost value="100036"> 收貨人郵編
<input type=hidden name=v_orderstatus value="0"> 商品信息
<input type=hidden name=v_ordername value="李四"> 訂貨人姓名
<input type=hidden name= v_orderemail value="test@test.com"> 訂貨人郵件
<input type=submit value="網銀在線支付">
</form>
MD5校驗串生成方法:當消費者在商戶端生成最終訂單的時候,將訂單中的v_amount v_moneytype v_oid v_mid v_url key六個參數的value值拼成一個無間隔的字符串(順序不要改變)。參數key是商戶的MD5密鑰(該密匙可在登陸商戶管理界面后自行更改。)
網銀在線支付>>>商戶
支付完成后頁面轉到商戶,從網銀在線支付返回的消息格式為:
<form method=get action="v_url" target=_self> <input type="hidden" name="v_oid" value=""> <input type="hidden" name="v_pstatus" value=""> <input type="hidden" name="v_pstring" value=""> <input type="hidden" name="v_pmode" value=""> <input type="hidden" name="v_md5str" value=""> <input type="hidden" name="v_amount" value=""> <input type="hidden" name="v_moneytype" value=""> <input type="hidden" name="remark1 " value=""> <input type="hidden" name="remark2" value=""> </form> |
該消息格式詳細解釋如下:v_url是該筆訂單提交時參數v_url 的值,即網銀返回到商戶的接口地址。
變量名稱 |
變量命名 |
返回值說明 |
|
訂單編號 |
v_oid |
商戶發送的v_oid定單編號。 |
|
支付狀態 |
v_pstatus |
20(表示支付成功) 30(表示支付失敗) |
|
支付結果信息 |
v_pstring |
支付完成 支付完成 |
|
支付方式 |
v_pmode |
支付銀行,例如工商銀行 |
|
訂單MD5校驗碼 |
v_md5str |
該參數的MD5字符串的順序為:v_oid,v_pstatus,v_amount,v_moneytype,key MD5字符串示例: 20050320-1001-0000012342012.340key 用MD5函數加密上述字符串后得到的值如果和v_md5str值相等即表明返回的信息沒有被纂改 |
|
訂單總金額 |
v_amount |
訂單實際支付金額 |
|
幣種 |
v_moneytype |
訂單實際支付幣種 |
|
備注字段1 |
remark1 |
|
|
備注字段2 |
remark2 |
|
表3
package cn.com.vnvtrip.china.pay.proxy;
import static cn.com.vnvtrip.china.pay.commons.ChinaPayConstants.CHINABANK_NOTIFY_URL_HTTP; import static cn.com.vnvtrip.china.pay.commons.ChinaPayConstants.CHINABANK_PAY_HTTPS; import static cn.com.vnvtrip.china.pay.commons.ChinaPayConstants.CHINABANK_PAY_MD5_KEY;
import java.util.HashMap; import java.util.Map; import java.util.Properties;
import org.apache.commons.codec.digest.DigestUtils;
import cn.com.vnvtrip.china.pay.commons.Env; import cn.com.vnvtrip.china.pay.commons.HTTPClient;
/** * * 網銀接口服務的代理 * * @author longgangbai * */ public class ChinaPayProxy { /** * 在下訂單時采用的的Md5加密的信息: MD5校驗串生成方法:當消費者在商戶端生成最終訂單的時候, 將訂單中的v_amount * v_moneytype v_oid v_mid v_url key六個參數的value值拼成一個無間隔的字符串(順序不要改變)。 * 參數key是商戶的MD5密鑰(該密匙可在登陸商戶管理界面后自行更改。) * * @param v_amount * @param v_moneytype * @param v_oid * @param v_mid * @param v_url * @param key * @return */ private static String getMd5Sign(String v_amount, String v_moneytype, String v_oid, String v_mid, String v_url, String key) { StringBuffer sb = new StringBuffer(); sb.append(v_amount); sb.append(v_moneytype); sb.append(v_oid); sb.append(v_mid); sb.append(v_url); sb.append(key); byte[] bytes = DigestUtils.md5(sb.toString()); String md5info = new String(bytes).toUpperCase(); return md5info; }
/** * 調用支付網關接口網址 銀行結帳的接口代理 (本人習慣采用Commons-httpclient實現) * 用途:用來接受商戶發給網銀在線服務支付的訂單信息 * * @param v_mid * 商戶編號(非空) * @param v_oid * 訂單編號(非空)(格式:訂單生成日期(yyyymmdd)-商戶編號-商戶流水號)字段不可超過64位 * @param v_amount * 訂單總金額 (非空) * @param v_moneytype * 貨幣類型 (非空) 0:RMB 1美元 * @param v_url * (非空) 支付的動作完成時返回的該url,支付結果以post方式發送 * @param v_md5info * 訂單md5校驗碼 * @param remark1 * 備注字段1(可選字段) * @param remark2 * 備注字段2 (可選字段) * @param v_vmd * yyyymmdd 備注字段2 (不可為空字段) * @param v_rcvname * 收貨人姓名 (自定義非網銀必須字段) * @param v_rcvaddr * 收貨人地址(自定義非網銀必須字段) * @param v_rcvtel * 收貨人電話(自定義非網銀必須字段) * @param v_rcpost * 收貨人郵編(自定義非網銀必須字段) * @param v_orderstatus * 商品信息(自定義非網銀必須字段) * @param v_ordername * 訂貨人姓名(自定義非網銀必須字段) * @param v_orderemail * 訂貨人郵件(自定義非網銀必須字段) * @return */ public static boolean chinaBankPayCheck(String v_mid, String v_oid, String v_amount, String v_moneytype, String v_url, String remark1, String remark2, String v_rcvname, String v_rcvaddr, String v_rcvtel, String v_rcpost, String v_orderstatus, String v_ordername, String v_orderemail) { Properties p = Env.getEnv().getProperties(); String md5key = p.getProperty(CHINABANK_PAY_MD5_KEY); String v_md5info = getMd5Sign(v_amount, v_moneytype, v_oid, v_mid, v_url, md5key); Map<String, String> paramMaps = new HashMap<String, String>(); paramMaps.put("v_mid", v_mid); paramMaps.put("v_oid", v_oid); paramMaps.put("v_amount", v_amount); paramMaps.put("v_moneytype", v_moneytype); paramMaps.put("v_url", p.getProperty(CHINABANK_NOTIFY_URL_HTTP)); paramMaps.put("v_md5info", v_md5info); paramMaps.put("remark1", remark1); paramMaps.put("remark2", remark2); paramMaps.put("v_rcvname", v_rcvname); paramMaps.put("v_rcvaddr", v_rcvaddr); paramMaps.put("v_rcvtel", v_rcvtel); paramMaps.put("v_rcpost", v_rcpost); paramMaps.put("v_orderstatus", v_orderstatus); paramMaps.put("v_ordername", v_ordername); paramMaps.put("v_orderemail", v_orderemail); return HTTPClient.executeHttp(CHINABANK_PAY_HTTPS, paramMaps, null); }
/** * result為支付完畢接受的結果的map 校驗檢測在網銀支付數據是否被攔截的 * * @param v_oid * 獲取結果中的訂單編號 * @param v_pstatus * 獲取訂單的支付狀態 * @param v_pstring * 支付的結果 * @param v_amount * 實際支付的金額 * @param v_moneytype * 實際支付的幣種 * @param v_md5str * 獲取訂單校驗的MD5驗證 * @return */ public static boolean checkPayOff(String v_oid, String v_pstatus, String v_pstring, String v_amount, String v_moneytype, String v_md5str) { Properties p = Env.getEnv().getProperties(); String md5key = p.getProperty(CHINABANK_PAY_MD5_KEY); String checkmd5 = getCheckMd5(v_oid, v_pstatus, v_amount, v_moneytype, md5key); if (checkmd5.equals(v_md5str)) { return true; } return false; }
/** * 得到網銀訂單付款后Md5加密檢查 * * @param v_oid * @param v_pstatus * @param v_amount * @param v_moneytype * @param key * @return */ private static String getCheckMd5(String v_oid, String v_pstatus, String v_amount, String v_moneytype, String key) { StringBuffer sb = new StringBuffer(); sb.append(v_oid); sb.append(v_pstatus); sb.append(v_amount); sb.append(v_moneytype); sb.append(key); byte[] bytes = DigestUtils.md5(sb.toString()); String md5info = new String(bytes).toUpperCase(); return md5info; } }
來自http://topmanopensource.javaeye.com/blog/497872
2010-02-09
文章分類:Java編程
這幾天 項目需要對接建設銀行的支付和查詢功能,在支付和查詢的時候將系統鏈接到建行指定的頁面上,由於這些頁面是基於互聯網的,開放的,所以需要對數據加密和數字簽名。 我來實現這個數據加密解密模塊,功能已經完成了,唉,不過讓我暈死的是,建行其實一並提供了 jar 包,已經實現了數據加密解密,校驗數字簽名的功能,只不過同事沒注意到,只發接口文檔給我,沒發 jar 包給我,害我白着急了幾天,不過工作也沒算浪費,自己實現的還是比較放心些吧。這些頁面的跳轉沒什么技術,主要在於數據加密和數字簽名,在鏈接到建行頁面之前,先將參數加密,在收到建行跳轉過來的鏈接參數后,取出參數里的簽名,將簽名和原始參數進行校驗,以確認目前跳轉過來的的確是建行。頁面跳轉沒什么好說的,我所感興趣的在於加密這些地方,以及對建行文檔的理解。
按照建行的規定,我們發送的數據需要進行 MD5 加密,建行對返回的數據進行了數字簽名,我們需要校驗簽名的有效性。以下是建行的兩項約定:
建行附錄 1 : MAC 算法說明
- 建設銀行家居銀行項目組決定對商戶向網上銀行系統提交的交易內容進行MAC校驗,校驗算法采用標准MD5算法,不帶密鑰。該算法的詳細說明請參見RFC 1321文檔。
- 商戶實行標准的MD5算法對向網上銀行系統提交的交易內容進行MAC校驗,產生128位(bit)的MAC結果。輸入為字符串,輸出為16進制字符表示的字符串。
- 下面是對MAC結果的顯示方式的描述:
- 對128位的交易結果按4位為一個單位進行划分,共獲得32段
- 將每段看成一個16進制數,如0011為0X3,1101為0Xd。
- 將這個數映射到ASCII碼表,形成相應的字符,如0X2為“2”,0Xd為“d”。
- 將這些字符連成一個字符串,長度為32。
- 下面是一些字符串進行MAC並按上述方法進行轉換后獲得的結果:
- MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
- MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
- MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
校驗,校驗算法采用標准MD5算法,不帶密鑰。該算法的詳細說明請參見RFC 1321文檔。
算法對向網上銀行系統提交的交易內容進行MAC校驗,產生128位(bit)的MAC結果。輸入為字符串,輸出為16進制字符表示的字符串。
結果的顯示方式的描述:
位的交易結果按4位為一個單位進行划分,共獲得32段
進制數,如0011為0X3,1101為0Xd。
碼表,形成相應的字符,如0X2為“2”,0Xd為“d”。
。
並按上述方法進行轉換后獲得的結果:
MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
建行附錄 2 :數字簽名算法說明
Html代碼
- 銀行將客戶支付信息實時通知給商戶時,使用的數字簽名算法是MD5withRSA算法。商戶驗證簽名的公鑰在商戶在網銀系統開戶,獲取數字證書后,登錄到網銀系統中,通過下載公鑰交易獲取。(下載后需妥善管理並及時更新商戶公鑰,以防公私鑰不匹配造成驗簽不通過)。(目前家居銀行項目組采用靜態密鑰對,上線前生成一對,發給合作商戶。)
- 商戶獲取的公鑰用X.509格式表示,並且將其按照每4位(bit)轉換為一個16進制數的方式表示,產生16進制的字符串。家居銀行使用標准MD5withRSA算法對給商戶的響應進行簽名,產生1024位(bit)的簽名結果,並且將其按照每4位(bit)轉換為一個16進制數的方式表示,形成16進制的字符串,長度為256。
- 下面是對簽名結果的表示方式的描述:
- 對1024位的交易結果按4位為一個單位進行划分,共獲得256段
- 將每段看成一個16進制數,如0011為0X3,1101為0Xd。
- 將這個數映射到ASCII碼表,形成相應的字符,如0X2為“2”,0Xd為“d”。
- 將這些字符連成一個字符串,長度為256。
- 例如:
- 待簽名的字符串為:
- POSID=000000000&BRANCHID=110000000&ORDERID=19991101234&PAYMENT=500.00&CURCODE=01&REMARK1=19991101&REMARK2=merchantname&SUCCESS=Y
- 簽名結果為:
- 4b3ef029516193b7d969ac1840083635a3e0901b8cd526caa44c1a072f496d7f0d4bca3942c0d9030bede37c7809b835cec787eb39e18b7596a724fba9805b24714dfbb0f4a3fb430b32e075254a114d4c38a0ac52ef46a0ad33dec3fbfc15417402a1399e65e46996c0cf49fc7ffca9222f8cd693c8376b6f928828967bec42
- 當商戶收到銀行傳來的CGI串后,從中獲取簽名(格式如上)和需簽名的原文。商戶端程序(商戶自行開發MD5withRSA簽名校驗程序)將簽名和商戶端的公鑰轉換成二進制格式,與簽名的原文一起對簽名的正確性進行校驗,校驗步驟如下:
- 使用公鑰進行簽名的逆運算
- 使用標准MD5算法運算原文
- 比較1)、2)結果。
算法。商戶驗證簽名的公鑰在商戶在網銀系統開戶,獲取數字證書后,登錄到網銀系統中,通過下載公鑰交易獲取。(下載后需妥善管理並及時更新商戶公鑰,以防公私鑰不匹配造成驗簽不通過)。(目前家居銀行項目組采用靜態密鑰對,上線前生成一對,發給合作商戶。)
格式表示,並且將其按照每4位(bit)轉換為一個16進制數的方式表示,產生16進制的字符串。家居銀行使用標准MD5withRSA算法對給商戶的響應進行簽名,產生1024位(bit)的簽名結果,並且將其按照每4位(bit)轉換為一個16進制數的方式表示,形成16進制的字符串,長度為256。
位的交易結果按4位為一個單位進行划分,共獲得256段
進制數,如0011為0X3,1101為0Xd。
碼表,形成相應的字符,如0X2為“2”,0Xd為“d”。
。
串后,從中獲取簽名(格式如上)和需簽名的原文。商戶端程序(商戶自行開發MD5withRSA簽名校驗程序)將簽名和商戶端的公鑰轉換成二進制格式,與簽名的原文一起對簽名的正確性進行校驗,校驗步驟如下:
算法運算原文
、2)結果。
仔細看上面兩項約定,無論是 MD5 加密還是 RSA 加密,都有一個基礎工作,就是將二進制數據分割,換算成 16 進制字符,還需要進行逆運算。將結果按 4 位為一個單位進行划分,共獲得 32 段, 將每段看成一個 16 進制數,如 0011 為 0X3 , 1101 為 0Xd 。 將這個數映射到 ASCII 碼表,形成相應的字符,如 0X2 為“ 2 ”, 0Xd 為“ d ”。 將這些字符連成一個字符串,長度為 32 。 我先實現這個功能,代碼如下:
ByteUtil.java
Java代碼
- package cn.ipanel.payment.business.bank.ccb.encryption;
- /**
- * 字節運算工具,其作用和背景請見建行接口文檔的"附錄1:MAC算法說明"
- *
- * @author wangxiaoxue
- *
- */
- public class ByteUtil {
- // 用來將字節轉換成 16 進制表示的字符
- private static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
- /**
- * 找到字符在數組中的位置
- *
- * @param c
- * @return
- */
- private static int getIndex(char c) {
- int p = -1;
- for (int i = 0; i < hexDigits.length; i++) {
- if (hexDigits[i] == c) {
- p = i;
- break;
- }
- }
- return p;
- }
- /**
- * 將字節轉化成字符串,轉換算法如下:<br>
- * 1:每個字節長度為8位,分割為兩個4位,高四位和低四位<br>
- * 2:將每個四位換算成16進制,並且對應ascii碼,如0x01對應1,0x0d對應d,具體對應關系請見數組hexDigits[]<br>
- * 3:將得到的字符拼成字符串
- *
- * @param bytes
- * @return
- */
- public static String byteToChar(byte[] bytes) {
- // 每個字節用 16 進制表示的話,使用兩個字符,所以字符數組長度是字節數字長度的2倍
- char str[] = new char[bytes.length * 2];
- // 表示轉換結果中對應的字符位置
- int = 0;
- // 每一個字節轉換成 16 進制字符
- for (int i = 0; i < bytes.length; i++) {
- byte byte0 = bytes[i]; // 取第 i 個字節
- // 取字節中高 4 位(左邊四位)的數字轉換,>>>為邏輯右移,右移后,高四位變成低四位,需要對低四位之外的值進行消零運算
- str[k++] = hexDigits[byte0 >>> 4 & 0xf];
- // 取字節中低 4 位(右邊四位)的數字轉換,並且和0xf進行"邏輯與"運算,以消除高位的值,得到純凈的低四位值
- str[k++] = hexDigits[byte0 & 0xf];
- }
- return new String(str);
- }
- /**
- * 將字節轉換成二進制數組,是byteToChar方法的逆運算,轉換算法如下:<br>
- * 1:將字符按順序每兩個分為一組,分別找出每個字符在映射表hexDigits[]中的索引值,請見getIndex(char c)方法<br>
- * 2:每兩個字符一組進行運算,將第一個字符的索引值邏輯左移四位,並和"0xf"進行"邏輯或"運算,目的是將低四位都設置為1,因為邏輯左移后,低四位都變成0了<br>
- * 3:將第二個字符的索引值和
- "0xf0"進行"邏輯或"運算,目的的是將高位設置為1<br>
- * 4:將兩個運算完的索引值進行"邏輯與"運算,得到了兩個字符所代表的一個字節值<br>
- * 5:依次運算,最后得到字節數組,返回
- *
- * @param str
- * @return
- */
- public static byte[] charToByte(String str) {
- char[] chars = str.toCharArray();
- byte[] bytes = new byte[chars.length / 2];
- int k = 0;
- for (int i = 0; i < chars.length; i = i + 2) {
- // 得到索引值
- byte high = (byte) getIndex(chars[i]);
- byte low = (byte) getIndex(chars[i + 1]);
- // 第一個字符索引邏輯左移四位,並進行或運算,將低四位設置為1
- high = (byte) ((high << 4) | 0xf);
- // 第二個字符索引進行或運算,將高四位設置為1
- low = (byte) (low | 0xf0);
- // 兩個字節進行與運算
- bytes[k++] = (byte) (high & low);
- }
- return bytes;
- }
- public static void main(String[] args) {
- String str = "abgcd1234";
- System.out.println("原始字符串:" + str);
- String result = ByteUtil.byteToChar(str.getBytes());
- System.out.println("運算結果:" + result);
- byte[] resultbytes = ByteUtil.charToByte(result);
- System.out.println("逆運算結果:" + new String(resultbytes));
- }
- }