接口规范 - 签名、加解密

## 参数加密 #### 第一步: 所有的参数转换为json字符串 ```String paramsJson= {"key1":"value1","key2":"value2"};``` #### 第二步: 获取到发放平台给的 publicKey 进行RSA加密 ```String encryptResult= RSAHelper.encryptByPublicKey(paramsJson, publicKey);``` >d RSA加密说明 ◆ 加密长度为512 ◆ 需要分段加密防止数据太长导致加密失败 ◆ 获取到加密结果后通过base64转成字符串即为加密后的数据 ◆ 如参数为空字符串无需加密 #### 加密代码示例(java) ```java /** * 使用公钥加密 * * @param content 待加密内容 * @param publicKeyBase64 公钥 base64 编码(发放平台给的publicKey) * @return 经过 base64 编码后的字符串 */ public static String encryptByPublicKey(String content, String publicKeyBase64) throws Exception { PublicKey publicKey = getPublicKey(publicKeyBase64); return encrypt(content, publicKey); } /** * 分段加密 * * @param ciphertext 密文 * @param key 加密秘钥 * @return */ public static String encrypt(String ciphertext, Key key) { try { // 用公钥加密 byte[] srcBytes = ciphertext.getBytes(); // Cipher负责完成加密或解密工作,基于RSA Cipher cipher = Cipher.getInstance("RSA"); // 根据公钥,对Cipher对象进行初始化 cipher.init(Cipher.ENCRYPT_MODE, key); //分段加密 byte[] resultBytes = encryptCipherDoFinal(cipher, srcBytes); //base64编码 String base64Str = Base64Utils.encodeToString(resultBytes); return base64Str; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 分段加密 * * @param cipher * @param srcBytes * @return * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws IOException */ public static byte[] encryptCipherDoFinal(Cipher cipher, byte[] srcBytes) throws IllegalBlockSizeException, BadPaddingException, IOException { int inputLen = srcBytes.length; int offLen = 0;//偏移量 int i = 0; ByteArrayOutputStream bops = new ByteArrayOutputStream(); while (inputLen - offLen > 0) { byte[] cache; if (inputLen - offLen > 53) { cache = cipher.doFinal(srcBytes, offLen, 53); } else { cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen); } bops.write(cache); i++; offLen = 53 * i; } bops.close(); byte[] encryptedData = bops.toByteArray(); return encryptedData; } ``` ## 结果解密 #### 第一步: 获取接口返回结果中的data字段,该值即为需要解密的数据 ``` { "resopnseType": 0, "errorCode": "", "errorMessage": "", "status": 0, "data": xxxxxxxxxxxxxxxxxxx, "ext": null, "extMessage": null, "success": true } ``` #### 第二步: 获取到发放平台提供的publicKey解密 ``` String decryptResult= RSAHelper.decryptByPublicKey(data, publicKey); ``` >d RSA解密说明 ◆ 需要分段解密密防止数据太长导致解密失败 ◆ 获取到加密结果后直接转成字符串即为解密结果 #### 解密代码示例(java) ```java /** * 使用公钥解密 * * @param contentBase64 待解密内容,base64 编码 * @param publicKeyBase64 私钥 base64 编码 * @return */ public static String decryptByPublicKey(String contentBase64, String publicKeyBase64) throws Exception { PublicKey publicKey = getPublicKey(publicKeyBase64); return decrypt(contentBase64, publicKey); } /** * 分段解密 * * @param contentBase64 密文 * @param key 解密秘钥 * @return */ public static String decrypt(String contentBase64, Key key) { try { // 用私钥解密 byte[] bytes = Base64Utils.decodeFromString(contentBase64); // Cipher负责完成加密或解密工作,基于RSA Cipher deCipher = Cipher.getInstance("RSA"); // 根据公钥,对Cipher对象进行初始化 deCipher.init(Cipher.DECRYPT_MODE, key); // 分段解密 byte[] decBytes = decryptCipherDoFinal(deCipher, bytes); // 结果转成字符串 String decrytStr = new String(decBytes); return decrytStr; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 分段解密 * * @param cipher * @param srcBytes * @return * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws IOException */ public static byte[] decryptCipherDoFinal(Cipher cipher, byte[] srcBytes) throws IllegalBlockSizeException, BadPaddingException, IOException { int inputLen = srcBytes.length; int offLen = 0; int i = 0; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); while (inputLen - offLen > 0) { byte[] cache; if (inputLen - offLen > 64) { cache = cipher.doFinal(srcBytes, offLen, 64); } else { cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen); } byteArrayOutputStream.write(cache); i++; offLen = 64 * i; } byteArrayOutputStream.close(); byte[] data = byteArrayOutputStream.toByteArray(); return data; } ``` ## 公共参数说明 |参数名称|参数含义|是否必填|参数备注| |-|-|-|-| |AppKey|由发放平台提供的|必填|商户签约后发放提供的唯一识别号| |Timestamp|时间戳|必填|请求时间戳精确到毫秒。需要将请求机器时间调整为北京时间,请求有效时间为两分钟| |Version|版本号|必填|如果无特别说明则填写1.0即可| |Sign|签名|必填|签名,具体来源请看下面的==签名算法==| >d 以上公共参数放在header中进行传递 #### 代码示例(java) ```java /** * * @param url 接口地址 * @param encryptResult 请求参数加密后的数据 * @param appKey * @param secretKey * @return */ public static String postRequest(String url, String encryptResult, String appKey, String secretKey) { String timestamp = String.valueOf(System.currentTimeMillis()); //将参数加密结果放在body中传输 RequestBody body = setRequestBody(encryptResult); String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0" + "&SecretKey" + secretKey; //签名 String sigin = Md5Util.getMd5(localString); Request request = new Request.Builder() //公共参数 .addHeader("AppKey", appKey) .addHeader("Timestamp", timestamp) .addHeader("Version", "1.0") .addHeader("Sign", sigin) .url(url) .post(body) .build(); Call call = getOkHttpClient().newCall(request); try { Response response = call.execute(); String res = response.body().string(); log.info("res====" + res); return res; } catch (IOException e) { log.error("HttpUtil.post网络请求失败:{}", e.getMessage()); return "网络请求失败"; } } ``` ## 签名算法 #### 第一步: 将加密结果跟公共字段拼接成新的字符串 ```java String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0" + "&SecretKey" + secretKey; ``` >d 备注:该处特别说明 &SecretKey 后面没有 ==等于==号 #### 第二步: 用MD5对新字符串加密即为签名 ```java //md5加密 String sigin = Md5Util.getMd5(localString); /** * md5 加密工具类 * 32小写英文字母 */ public class Md5Util { static String[] chars = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}; /** * md5 加密 * @param src 需的加密字符串 * @return */ public static String getMd5(String src){ MessageDigest md5 = null; try { // 参数代表的是算法名称 md5 = MessageDigest.getInstance("md5"); byte[] result = md5.digest(src.getBytes()); StringBuilder sb = new StringBuilder(32); // 将结果转为16进制字符 0~9 A~F for (int i = 0; i < result.length; i++) { // 一个字节对应两个字符 byte x = result[i]; // 取得高位 int h = 0x0f & (x >>> 4); // 取得低位 int l = 0x0f & x; sb.append(chars[h]).append(chars[l]); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } ```