Appendix A (Data Encryption / Decryption)

To secure your Biz data with data encryption

Data Encryption Process with API Transaction Data

As our API protocol, The Transaction Data within API need to be encrypted in AES methodology which encryption mode is ECB and fill mode is PKCS5 Padding.

☑️ Encryption STEPS:

  1. Sort the data (JSON data format) in dictionary order by their key name, then concatenate their corresponding value into one data string.

  2. Calculate the MD5 value of the data string from step one, and then convert to uppercase. This value will be also passed in as one of the HTTP request parameters.

  3. Concatenate the MD5 value from the STEP 2 and merchant encryption key (signkey) provided by OTT

  4. Perform the MD5 16-bit calculation, and then uppercase it again as AES key

  5. Perform AES and Base64 encryption to the data string

☑️ Encryption Example:

Here is the original transaction data (JSON data format) as below,

{'orderId' : '1234567890', 'idCardName' : ‘张三‘, 'idCardNum' : '123456789012345678', 'bankCardNum' : '62000000000010', 'mobile' : '13711111111', 'idCardType' : '1'}

1️⃣ After Step-1 sorting and concatenate:

62000000000010张三1234567890123456781137111111111234567890

2️ After Step-2 MD5 calculation:

C12F9560769C2CB55E6954935B325916

3️⃣ After Step-3 (Merchant SignKey:6698851A525C9433):

C12F9560769C2CB55E6954935B3259166698851A525C9433

4️⃣ After Step 4: Get AES Key

 EF1712FC070C2C21

5️⃣ After Step 5: get Encrypted String

Y2+GoqBBPc6EJ9JzXRFoOziKd+DqWg5FiUZY1IYoVBVv0xfFznT9/qanMpiNEamEN2NM7J+hxoRn8VGSJImA2soeip9nYr+VJvTXe7D8j4aXiKyFipuPVCQLiCDg3jkJP+S2EezYMf7crqKY/YAni1CCeIwr2aJfFVT1vYhsWIm8t3eyPL//cY4kwombAKhE2gAdEFXz6gVvencz80aRWQ==

Data Decryption Process with API Transaction Data

Response/Call-Back API Transaction data is encrypted in AES methodology which encryption mode is ECB and fill mode is PKCS5 Padding.

☑️ Decryption STEPS:

  1. get the data field string value from response/call-back payload parameters level data.

  2. get the md5 field string value from response/call-back payload parameters level data

  3. Get AES key by above above md5 string and merchant signkey provided by OTT Pay

  4. Apply Base64 decoding and Decrypt the step-2 result by using the AES key get from Step 3.

☑️Decryption Example:

Here is the response message with transaction data (JSON data format) as below,

{
 "rsp_code":"SUCCESS",
 "rsp_msg":"success",
 "data":"Y2+GoqBBPc6EJ9JzXRFoOziKd+DqWg5FiUZY1IYoVBVv0xfFznT9/qanMpiNEamEN2NM7J+hxoRn8VGSJImA2soeip9nYr+VJvTXe7D8j4aXiKyFipuPVCQLiCDg3jkJP+S2EezYMf7crqKY/YAni1CCeIwr2aJfFVT1vYhsWIm8t3eyPL//cY4kwombAKhE2gAdEFXz6gVvencz80aRWQ==",
 "md5":"C12F9560769C2CB55E6954935B325916"
}

1️⃣ After Step-1 data string value:

Y2+GoqBBPc6EJ9JzXRFoOziKd+DqWg5FiUZY1IYoVBVv0xfFznT9/qanMpiNEamEN2NM7J+hxoRn8VGSJImA2soeip9nYr+VJvTXe7D8j4aXiKyFipuPVCQLiCDg3jkJP+S2EezYMf7crqKY/YAni1CCeIwr2aJfFVT1vYhsWIm8t3eyPL//cY4kwombAKhE2gAdEFXz6gVvencz80aRWQ==

2️ After Step-2 MD5 string value:

C12F9560769C2CB55E6954935B325916

3️⃣ After Step 3: Get AES Key(Merchant SignKey: 6698851A525C9433):

EF1712FC070C2C21

4️⃣ After Step 4: Base64 decoding and decrypted String:

{'orderId' : '1234567890', 'idCardName' : ‘张三‘, 'idCardNum' : '123456789012345678', 'bankCardNum' : '62000000000010', 'mobile' : '13711111111', 'idCardType' : '1'}

Code Samples (Payment Request API)

Here are code samples in different development languages.

package com.ottpay.demo.test;

import com.google.gson.Gson;
import com.ottpay.demo.utils.AppCommonUtils;
import com.ottpay.demo.utils.HTTPSCommClient;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


public class ActivePayTest {
    static String key = "XXXyourSignKeyXXX"; //using your SignKey provided by OTTPAY;
    static Gson gson = new Gson();
    static String url = "https://frontapi.ottpay.com/process";
    static String merchantId="ON0000XXXX"; //using your Merchant ID provided by OTTPAY;
    public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException, IOException {

    Map<String, String> bo = new HashMap<String, String>();
    bo.put("order_id", "ACT" + System.currentTimeMillis());
    bo.put("call_back_url", "http://www.yourcallbackurl.com");  //using your call back url
    bo.put("biz_type", "WECHATPAY");
		bo.put("operator_id", "XXXXXXX");  //using your 10-digital operator number provided by OTTPAY;
    bo.put("amount", "1");
    Map map = doSend(bo, "ACTIVEPAY");
}
    /**
     * 将Map存储的对象,转换为key=value&key=value&的字符,并将key按顺序排序
     */
    public static String sortStringByMap(Map<String, String> map) {
        String[] arrayToSort = map.keySet().toArray(new String[map.keySet().size()]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < arrayToSort.length; i++) {
            sb.append(map.get(arrayToSort[i]));
        }
        return sb.toString();
    }

    private static String signByMD5(Map<String, String> objectAsMap) {
        String result = sortStringByMap(objectAsMap);
        String sign = DigestUtils.md5Hex(result).toUpperCase();
        return sign;
    }

    private static Map doSend(Map<String,String> bo, String type) throws NoSuchAlgorithmException, KeyManagementException, IOException {
        Map<String, String> vo = new HashMap<String, String>();
        vo.put("action", type);
        vo.put("version", "1.0");
        vo.put("merchant_id", merchantId);
        String md5 = signByMD5(bo);
        String encrypted = AppCommonUtils.encrypted(gson.toJson(bo), key, md5);
        System.out.println(encrypted);
        vo.put("data", encrypted);
        vo.put("md5", md5);
        Map<String, String> appRespVO = gson.fromJson(HTTPSCommClient.post(url, gson.toJson(vo)), Map.class);
        System.out.println(ToStringBuilder.reflectionToString(appRespVO));
        Map decipher = gson.fromJson(AppCommonUtils.decipher(appRespVO.get("data"), key, appRespVO.get("md5")), Map.class);
        System.out.println("data:" + decipher.toString());
        return decipher;
    }
}

//---------------------------AppCommonUtils
package com.ottpay.demo.utils;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import java.util.TreeMap;


public class AppCommonUtils {
    static Logger logger = LoggerFactory.getLogger(AppCommonUtils.class);
    static Gson gson = new Gson();


    /**
     * 解密
     *
     * @param data
     * @param key
     * @param md5
     * @return
     */
    public static String decipher(String data, String key, String md5) {

        byte[] orgData = Base64Utils.decodeFromString(data);
        String aesKeyStr = DigestUtils.md5Hex(md5 + key).substring(8, 24).toUpperCase();
        byte[] aesKey = aesKeyStr.getBytes();
        String decData = new String(AES.decrypt(orgData, aesKey));

        TreeMap<String, Object> treeMap = null;
        try {
            treeMap = gson.fromJson(decData, TreeMap.class);
        } catch (JsonSyntaxException e) {
            logger.error("decrypt error!exception:{}", e);
            return null;
        }
        StringBuilder stb = new StringBuilder();
        for (String tk : treeMap.keySet()) {
            stb.append(treeMap.get(tk));
        }
        logger.debug("Check the original string data:{}", stb.toString());
        String calmd5 = DigestUtils.md5Hex(stb.toString());
        if (calmd5.toUpperCase().equals(md5.toUpperCase())) {
            return decData;
        } else {
            throw new RuntimeException("check sign fail");
        }
    }



    /**
     * 加密
     *
     * @param data json
     * @param key
     * @param md5
     * @return
     */
    public static String encrypted(String data, String key, String md5) {
        String aesKeyStr = DigestUtils.md5Hex(md5 + key).substring(8, 24).toUpperCase();
        byte[] encrypt = AES.encrypt(data.getBytes(), aesKeyStr.getBytes());
        return Base64Utils.encodeToString(encrypt);
    }

    public static String getMd5(Object obj) {
        String str = gson.toJson(obj);

        TreeMap<String, Object> treeMap = gson.fromJson(str, TreeMap.class);
        StringBuffer stb = new StringBuffer();
        for (String tk : treeMap.keySet()) {
            stb.append(treeMap.get(tk));
        }
        return DigestUtils.md5Hex(stb.toString()).toUpperCase();
    }


}

//---------------------------AES
/** 
 * Copyright: Copyright (c)2017
 */
package com.ottpay.demo.utils;


import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;



public class AES {
	/**
	 * 加密
	 * 
	 * @param data
	 *            需要加密的内容
	 * @param key
	 *            加密密码
	 * @return
	 */
	public static byte[] encrypt(byte[] data, byte[] key) {
		CheckUtils.notEmpty(data, "data");
		CheckUtils.notEmpty(key, "key");
		if(key.length!=16){
			throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
		}
		try {
			SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
			byte[] enCodeFormat = secretKey.getEncoded();
			SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");// 创建密码器
			cipher.init(Cipher.ENCRYPT_MODE, seckey);// 初始化
			byte[] result = cipher.doFinal(data);
			return result; // 加密
		} catch (Exception e){
			throw new RuntimeException("encrypt fail!", e);
		}
	}

	/**
	 * 解密
	 * 
	 * @param data
	 *            待解密内容
	 * @param key
	 *            解密密钥
	 * @return
	 */
	public static byte[] decrypt(byte[] data, byte[] key) {
		CheckUtils.notEmpty(data, "data");
		CheckUtils.notEmpty(key, "key");
		if(key.length!=16){
			throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
		}
		try {
			SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
			byte[] enCodeFormat = secretKey.getEncoded();
			SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");// 创建密码器
			cipher.init(Cipher.DECRYPT_MODE, seckey);// 初始化
			byte[] result = cipher.doFinal(data);
			return result; // 加密
		} catch (Exception e){
			throw new RuntimeException("decrypt fail!", e);
		}
	}
}

//----------------------HTTPSCommClient 
package com.ottpay.demo.utils;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;


public class HTTPSCommClient {

	/**
	 * POST方式请求
	 * 
	 * @param uri
	 *            服务器的uri要用物理IP或域名,不识别localhost或127.0.0.1形式!
	 * @param paramMap
	 * @return
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws KeyManagementException
	 */
	public static String post(String uri, Map<String, String> paramMap)
			throws NoSuchAlgorithmException, KeyManagementException,
			IOException {
		String data = null;
		StringBuffer stb = new StringBuffer();
		if (paramMap != null) {
			for (String key : paramMap.keySet()) {
				stb.append(key).append("=").append(paramMap.get(key))
						.append("&");
			}
			System.out.println(stb.toString());
			data = stb.toString().substring(0, stb.toString().length() - 1);
			System.out.println(data.toString());
		}

		URL url = new URL(uri);
		TrustManager[] tms = { new MyX509TrustManager() };
		SSLContext sc = SSLContext.getInstance("SSL");
		sc.init(null, tms, new SecureRandom());
		SSLSocketFactory ssf = sc.getSocketFactory();
		HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
		
		https.setSSLSocketFactory(ssf);
		https.setHostnameVerifier(new TrustAnyHostnameVerifier());
		https.setDoInput(true);
		https.setDoOutput(true);
		https.setRequestMethod("POST");
		https.setRequestProperty("Content-Length",
				String.valueOf(data.getBytes().length));
		https.connect();
		
		DataOutputStream os = new DataOutputStream(https.getOutputStream());
		if (data != null) {
			os.write(data.getBytes("utf-8"));
		}
		os.flush();
		os.close();
		int responseCode = https.getResponseCode();
		if(responseCode!=200){
			throw new IOException("https's responseCode is :"+responseCode);
		}
		InputStream in = https.getInputStream();
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		byte[] bs = new byte[1024];
		int len = 0;
		while ((len = in.read(bs)) != -1) {
			out.write(bs, 0, len);
		}
		in.close();
		byte[] byteArray = out.toByteArray();
		out.close();
		System.out.println(new String(byteArray, "utf-8"));
		https.disconnect();
		return new String(byteArray, "utf-8");
	}
	
	public static String post(String uri,String data) throws NoSuchAlgorithmException, KeyManagementException, IOException{
		URL url = new URL(uri);
		TrustManager[] tms = { new MyX509TrustManager() };
		SSLContext sc = SSLContext.getInstance("SSL");
		sc.init(null, tms, new SecureRandom());
		SSLSocketFactory ssf = sc.getSocketFactory();
		HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
		https.setSSLSocketFactory(ssf);
		https.setHostnameVerifier(new TrustAnyHostnameVerifier());
		https.setDoInput(true);
		https.setDoOutput(true);
		https.setRequestMethod("POST");
		https.setRequestProperty("Accept-Charset", "application/json");
		https.setRequestProperty("Content-Type", "application/json");
		https.setRequestProperty("Content-Length",
				String.valueOf(data.getBytes("utf-8").length));
		https.setConnectTimeout(30000);
		https.setReadTimeout(30000);
		https.connect();
		DataOutputStream os = new DataOutputStream(https.getOutputStream());
		if (data != null) {
			os.write(data.getBytes("utf-8"));
		}
		os.flush();
		os.close();
		int responseCode = https.getResponseCode();
		if(responseCode!=200){
			throw new IOException("https's responseCode is :"+responseCode);
		}
		InputStream in = https.getInputStream();
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		byte[] bs = new byte[1024];
		int len = 0;
		while ((len = in.read(bs)) != -1) {
			out.write(bs, 0, len);
		}
		in.close();
		byte[] byteArray = out.toByteArray();
		out.close();
		System.out.println(new String(byteArray, "utf-8"));
		https.disconnect();
		return new String(byteArray, "utf-8");
	}
}

Last updated