RSA加密解密
调试记录
常见问题
用户私钥 privateKey
平台公钥 serverPublicKey
用户公钥 publicKey
原始数据
密文 cipher
密文 cipher
签名 sign
- 上传请求统一用POST提交,Content-Type: application/x-www-form-urlencoded。如果提示“accessID不可为空”请先检查请求格式是否正确。数据上传格式为:
- 中文编码非特殊说明统一为UTF-8。
- 如果返回结果500,请先检查上传参数格式是否正确:
(1)检查参数格式是否与文档一致,如数值型不可传字符类型等。
(2)检查加密前参数是否存在data域,即明文格式为:
{ "data": { "plateNo": "鲁B0101", "plateColor": 1, "parkingCode": "1001", "operatorCode": "01C001", "totalBerthNum": 150, "openBerthNum": 150, "freeBerthNum": 70, "arriveTime": "2015-09-01 09:10:11 ", "parkingType": 1, "entryNum": "01", "uid": "0100120160110121010", "arriveID":"1", "uploadTime": "2015-09-01 09:10:11" } }
签名失败或者解密失败
- 检查本网页密文和签名是否校验通过。
- 将本网页的密文和签名复制到HTTP工具中(如postman)参照上图,看是否校验通过,如果能通过,则传参方式不一致导致,请参考上图传参方式。
- 检查网页参数值与调试记录里边的参数是否一致,重点检查参数中的“+”号是否在调试记录里边显示成空格,如果存在这种情况,说明将参数放在了url里,需要参考上图将参数放在消息体里边,否则+号会被转义成空格。
账户秘钥
- 账户秘钥分为用户秘钥对和平台公钥,需要区分用户秘钥和平台秘钥。每个接入账号的秘钥均不相同,如果需要对接新的车场则需要新的接入账号。
- 如果是单个车场对接,则每个车场需要一个接入账号;如果是平台级对接,则一个平台可以用同一个接入账号accessID进行参数加解密,但是每个车场的编号是不同的,新接入车场之前需要先备案车场信息,备案后需要平台给接入账号添加相应车场的接入权限后再上传数据。
参数上传加密过程
- 对需要上传的参数进行拼装,拼装后用平台公钥进行参数加密得到cipher;
- 对上一步得到的cipher用用户私钥进行签名,得到签名值sign;
- 将生成的结果通过相应的接口上传。
下发数据处理步骤
平台下发的数据也是加密的,在收到下发数据后需要校验签名和解密后处理。数据处理步骤:
- 对接收到的参数cipher用平台公钥进行验签,看是否与sign值一致;
- 对于上一步验签通过的数据,用用户私钥对cipher进行解密,得到原始数据;
- 对原始数据进行处理。
签名验证失败排查步骤
- 参与签名的字符串为加密后的cipher,而不是原始参数;
- 签名的秘钥为用户私钥;
- 签名算法:SHA1WithRSA;
- 字符串编码:UTF-8;
- 签名过程有问题,请利用在线工具自行校验;
- 如果在线校验通过,仍然存在签名验证失败的问题,联系技术人员。
图片上传失败,提示:只支持JPG格式
- 请确保上传的文件是JPG文件格式,文件不能是通过重命名文件扩展名得到的JPG文件,重命名不会改变文件信息和文件头信息;
- 请确认文件是以0xFFD8开头(即字符串以/9j开头,不要带文件头)的JPG文件;
- 请确认file文件base64格式内容,确保不存在换行符等特殊字符;
- 如果仍然存在疑问,联系技术人员。
错误提示:车牌号格式不正确
- 请确认车牌号是正确的车牌格式,不带空格或-等特殊符号;
- 对于无车牌或未识别的统一传“无车牌”或“未识别”三个字,其他无法通过校验;
- 如果有特殊车牌规则校验不通过,联系技术人员。
示例代码(Java)
公钥(平台公钥)加密。因为对数据填充padding的原因,每次加密的结果不一样
public static String rsaEncryptPublicKey(String data, String serverPublicKey) { try { byte[] decode = java.util.Base64.getDecoder().decode(serverPublicKey); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decode); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey generatePublic = keyFactory.generatePublic(x509EncodedKeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());; cipher.init(Cipher.ENCRYPT_MODE, generatePublic); // 加密字符串超过117时循环处理 byte[] bytes = data.getBytes("UTF-8"); int inputLen = bytes.length; int offLen = 0; // 偏移量 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); while(inputLen - offLen > 0){ byte [] cache; if(inputLen - offLen > 117){ cache = cipher.doFinal(bytes, offLen, 117); } else{ cache = cipher.doFinal(bytes, offLen, inputLen - offLen); } outputStream.write(cache); offLen += 117; } outputStream.close(); byte[] encryptedData = outputStream.toByteArray(); String encodeToString = java.util.Base64.getEncoder().encodeToString(encryptedData); return encodeToString; } catch (Exception e) { e.printStackTrace(); } return ""; }
私钥(用户私钥)解密
public static String rsaDecryptPrivateKey(String data, String privateKey) { String result = ""; try { java.util.Base64.Decoder decoder = java.util.Base64.getDecoder(); byte[] decode = decoder.decode(privateKey); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decode); // 私钥用PKCS8EncodedKeySpec KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey generatePrivate = keyFactory.generatePrivate(pkcs8EncodedKeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());; cipher.init(Cipher.DECRYPT_MODE, generatePrivate); byte[] bytes = decoder.decode(data); int inputLen = bytes.length; int offLen = 0; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 128解密 while(inputLen - offLen > 0){ byte[] cache; if(inputLen - offLen > 128){ cache = cipher.doFinal(bytes, offLen, 128); }else{ cache = cipher.doFinal(bytes, offLen, inputLen - offLen); } byteArrayOutputStream.write(cache); offLen += 128; } byteArrayOutputStream.close(); byte[] byteArray = byteArrayOutputStream.toByteArray(); result = new String(byteArray, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } return result; }
私钥(用户私钥)签名
public static String sign(String content, String privateKey) { try { PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(java.util.Base64.getDecoder().decode(privateKey)); KeyFactory keyf = KeyFactory.getInstance("RSA"); PrivateKey priKey = keyf.generatePrivate(priPKCS8); java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA"); signature.initSign(priKey); signature.update(content.getBytes("UTF-8")); byte[] signed = signature.sign(); return java.util.Base64.getEncoder().encodeToString(signed); } catch (Exception e) { e.printStackTrace(); } return ""; }
公钥(平台公钥)验签
public static boolean doCheck(String content, String sign, String serverPublicKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] encodedKey = java.util.Base64.getDecoder().decode(serverPublicKey); PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA"); signature.initVerify(pubKey); signature.update(content.getBytes("UTF-8")); boolean bverify = signature.verify(java.util.Base64.getDecoder().decode(sign)); return bverify; } catch (Exception e) { e.printStackTrace(); } return false; }
数据上报
// 上传入场记录 public static String carin() { JSONObject paramJson = new JSONObject(); paramJson.put("uid", "202204150011"); // 流水号,一次停车的进场、离场、缴费信息等要保证uid相同 paramJson.put("arriveID", "20220415001"); // 入场记录id paramJson.put("plateNo", "鲁B12345"); // 车牌号 paramJson.put("plateColor", 1); // 车牌颜色,1:蓝色;2:黄色;3:白色;4:黑色;5:绿色 paramJson.put("carType", 1); // 车型,1:小型车;2:中型车;3:大型车 paramJson.put("parkingCode", "3712121111aabbcc"); // 停车场编号 paramJson.put("gateNo", ""); // 岗亭编号,可空 paramJson.put("entryNum", "01"); // 入口编号 paramJson.put("operatorCode", ""); // 收费员编号,可空 paramJson.put("totalBerthNum", 150); // 泊位总数 paramJson.put("openBerthNum", 150); // 开放泊位数 paramJson.put("freeBerthNum", 70); // 剩余开放泊位数 paramJson.put("arriveTime", "2022-04-07 15:24:12"); // 入场时间(格式:yyyy-MM-dd HH:mm:ss) paramJson.put("parkingType", 1); // 停车类型,1:临时停车;2:包月停车;3:共享停车;4:特殊停车 paramJson.put("uploadTime", "2022-04-07 15:24:12"); // 上传时间(格式:yyyy-MM-dd HH:mm:ss) JSONObject requestJson = new JSONObject(); requestJson.put("data", paramJson.toString()); logger.info("车辆入场明文信息:{}", paramJson.toString()); Map<String, String> paramMap = new HashMap<>(8); paramMap.put("accessID", accessID); String cipher = RSAUtil.rsaEncryptPublicKey(requestJson.toJSONString(), serverPublicKey); paramMap.put("cipher", cipher); String sign = RSAUtil.sign(cipher, privateKey); paramMap.put("sign", sign); logger.info("发送车辆入场信息:{}", paramMap.toString()); String result = doPost(parkurl + "/uploadCarInData", paramMap); logger.info("入场结果:" + result); return result; } public static String doPost(String url, Mapparam) { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建参数列表 if (param != null) { List<NameValuePair> paramList = new ArrayList<>(); for (String key : param.keySet()) { paramList.add(new BasicNameValuePair(key, param.get(key))); } // 模拟表单 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList,Consts.UTF_8); httpPost.setEntity(entity); } // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { if (response != null) { response.close(); } } catch (IOException e) { e.printStackTrace(); } } return resultString; }