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, Map param) {
// 创建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;
}