如何设计安全可靠的开放接口 您所在的位置:网站首页 类似md5加密的还有哪些 如何设计安全可靠的开放接口

如何设计安全可靠的开放接口

2023-03-25 15:28| 来源: 网络整理| 查看: 265

文章目录 【如何设计安全可靠的开放接口】系列前言AES加解密代码实现

【如何设计安全可靠的开放接口】系列

1. 如何设计安全可靠的开放接口—之Token 2. 如何设计安全可靠的开放接口—之AppId、AppSecret 3. 如何设计安全可靠的开放接口—之签名(sign) 4. 如何设计安全可靠的开放接口【番外篇】—关于MD5应用的介绍 5. 如何设计安全可靠的开放接口—还有哪些安全保护措施 6. 如何设计安全可靠的开放接口—对请求参加密保护 7. 如何设计安全可靠的开放接口【番外篇】— 对称加密算法

前言

前面提到过,到目前为止我们已经基本上实现了一个安全可靠的开放接口设计,本节我们再来完善最后一块拼图:对业务参数的加密。 有些时候,如果有些参数比较敏感,你不希望以明文的方式在网络中传输,那么就可以使用加解密的方式。

AES加解密

一般来说,要完成对业务参数的加解密,最常用的算法就是AES了,它是一种对称加密算法,也是当前公认的即安全,又兼顾性能的对称加密算法,加解密双方通过约定好的密钥来分别对明文加密,和密文解密,基本常见主流的工具包中都有其实现的API,可以直接拿来使用,非常方便。

代码实现

具体代码实现方式,就继续接着前面的案例,添加了clientCallWithEncryption和serverVerifyWithDecryption(string)两个方法,分别是接口请求方对请求的业务参数进行加密,和接口提供方对请求参数进行解密的版本。

import com.alibaba.fastjson.JSONObject; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Maps; import lombok.SneakyThrows; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.RandomStringUtils; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; public class AppUtils { /** * key:appId、value:appSecret */ static Map appMap = Maps.newConcurrentMap(); /** * key:appId、value:appAESSecret */ static Map appAesMap = Maps.newConcurrentMap(); /** * 分别保存生成的公私钥对 * key:appId,value:公私钥对 */ static Map appKeyPair = Maps.newConcurrentMap(); static Cache cache = CacheBuilder.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .build(); private static final String AES_ALG = "AES"; private static final String AES_CBC_PCK_ALG = "AES/CBC/PKCS5Padding"; private static final byte[] AES_IV = initIv(); public static void main(String[] args) throws Exception { // 模拟生成appId、appSecret String appId = initAppInfo(); // 根据appId生成公私钥对 initKeyPair(appId); // 模拟请求方 String requestParam = clientCall(); // 模拟提供方验证 serverVerify(requestParam); System.out.println("***************************业务参数加密版本***************************"); // 以下是带加密业务参数的版本 String encryptionContent = clientCallWithEncryption(); serverVerifyWithDecryption(encryptionContent); } private static void serverVerifyWithDecryption(String encryptionContent) throws Exception { APIRequestEntity apiRequestEntity = JSONObject.parseObject(encryptionContent, APIRequestEntity.class); Header header = apiRequestEntity.getHeader(); // AES解密 Object body = apiRequestEntity.getBody(); String content = decrypt(body.toString(), appAesMap.get(header.getAppId()), "UTF-8"); System.out.println(); System.out.println("【业务参数解密后内容】:" + content); UserEntity userEntity = JSONObject.parseObject(content, UserEntity.class); // 首先,拿到参数后同样进行签名 String sign = getSHA256Str(JSONObject.toJSONString(userEntity)); if (!sign.equals(header.getSign())) { throw new Exception("数据签名错误!"); } // 从header中获取相关信息,其中appSecret需要自己根据传过来的appId来获取 String appId = header.getAppId(); String appSecret = getAppSecret(appId); String nonce = header.getNonce(); String timestamp = header.getTimestamp(); // 请求时间有效期校验 long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); if ((now - Long.parseLong(timestamp)) / 1000 / 60 >= 5) { throw new Exception("请求过期!"); } // nonce有效性判断 String str = cache.getIfPresent(appId + "_" + nonce); if (Objects.nonNull(str)) { throw new Exception("请求失效!"); } cache.put(appId + "_" + nonce, "1"); // 按照同样的方式生成appSign,然后使用公钥进行验签 Map data = Maps.newHashMap(); data.put("appId", appId); data.put("nonce", nonce); data.put("sign", sign); data.put("timestamp", timestamp); Set keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[0]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("appSecret=").append(appSecret); if (!rsaVerifySignature(sb.toString(), appKeyPair.get(appId).get("publicKey"), header.getAppSign())) { throw new Exception("验签错误!"); } System.out.println(); System.out.println("【提供方】验证通过!"); } private static byte[] initIv() { try { Cipher cipher = Cipher.getInstance(AppUtils.AES_CBC_PCK_ALG); int blockSize = cipher.getBlockSize(); byte[] iv = new byte[blockSize]; for (int i = 0; i int blockSize = 16; byte[] iv = new byte[blockSize]; for (int i = 0; i Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG); IvParameterSpec iv = new IvParameterSpec(initIv()); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(key.getBytes()), AES_ALG), iv); byte[] cleanBytes = cipher.doFinal(Base64.decodeBase64(content.getBytes())); return new String(cleanBytes, charset); } public static String encrypt(String content, String aesKey, String charset) throws Exception { Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG); IvParameterSpec iv = new IvParameterSpec(AES_IV); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(aesKey.getBytes()), AES_ALG), iv); byte[] encryptBytes = cipher.doFinal(content.getBytes(charset)); return new String(Base64.encodeBase64(encryptBytes)); } private static String clientCallWithEncryption() throws Exception { // 假设接口请求方与接口提供方,已经通过其他渠道,确认了双方交互的appId、appSecret String appId = "123456"; String appSecret = appMap.get(appId); String timestamp = String.valueOf(System.currentTimeMillis()); // 随机数 String nonce = RandomStringUtils.randomAlphanumeric(10); // 业务请求参数 UserEntity userEntity = new UserEntity(); userEntity.setUserId("1"); userEntity.setPhone("13912345678"); // 使用sha256的方式生成签名 String sign = getSHA256Str(JSONObject.toJSONString(userEntity)); Map data = Maps.newHashMap(); data.put("appId", appId); data.put("nonce", nonce); data.put("sign", sign); data.put("timestamp", timestamp); Set keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[0]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("appSecret=").append(appSecret); System.out.println("【请求方】拼接后的参数:" + sb.toString()); System.out.println(); // 使用sha256withRSA的方式对header中的内容加签 String appSign = sha256withRSASignature(appKeyPair.get(appId).get("privateKey"), sb.toString()); System.out.println("【请求方】appSign:" + appSign); System.out.println(); // 请求参数组装 Header header = Header.builder() .appId(appId) .nonce(nonce) .sign(sign) .timestamp(timestamp) .appSign(appSign) .build(); APIRequestEntity apiRequestEntity = new APIRequestEntity(); apiRequestEntity.setHeader(header); apiRequestEntity.setBody(encrypt(JSONObject.toJSONString(userEntity), appAesMap.get(appId), "UTF-8")); String requestParam = JSONObject.toJSONString(apiRequestEntity); System.out.println("【请求方】接口请求参数: " + requestParam); return requestParam; } private static String initAppInfo() { // appId、appSecret生成规则,依据之前介绍过的方式,保证全局唯一即可 String appId = "123456"; String appSecret = "654321"; // AES密钥,同样每个appId分别对应一个AES密钥。 String encryptionKey = "50AHsYx7H3OHVMdF123456"; appMap.put(appId, appSecret); appAesMap.put(appId, encryptionKey); return appId; } private static void serverVerify(String requestParam) throws Exception { APIRequestEntity apiRequestEntity = JSONObject.parseObject(requestParam, APIRequestEntity.class); Header header = apiRequestEntity.getHeader(); UserEntity userEntity = JSONObject.parseObject(JSONObject.toJSONString(apiRequestEntity.getBody()), UserEntity.class); // 首先,拿到参数后同样进行签名 String sign = getSHA256Str(JSONObject.toJSONString(userEntity)); if (!sign.equals(header.getSign())) { throw new Exception("数据签名错误!"); } // 从header中获取相关信息,其中appSecret需要自己根据传过来的appId来获取 String appId = header.getAppId(); String appSecret = getAppSecret(appId); String nonce = header.getNonce(); String timestamp = header.getTimestamp(); // 请求时间有效期校验 long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); if ((now - Long.parseLong(timestamp)) / 1000 / 60 >= 5) { throw new Exception("请求过期!"); } // nonce有效性判断 String str = cache.getIfPresent(appId + "_" + nonce); if (Objects.nonNull(str)) { throw new Exception("请求失效!"); } cache.put(appId + "_" + nonce, "1"); // 按照同样的方式生成appSign,然后使用公钥进行验签 Map data = Maps.newHashMap(); data.put("appId", appId); data.put("nonce", nonce); data.put("sign", sign); data.put("timestamp", timestamp); Set keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[0]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("appSecret=").append(appSecret); if (!rsaVerifySignature(sb.toString(), appKeyPair.get(appId).get("publicKey"), header.getAppSign())) { throw new Exception("验签错误!"); } System.out.println(); System.out.println("【提供方】验证通过!"); } public static String clientCall() { // 假设接口请求方与接口提供方,已经通过其他渠道,确认了双方交互的appId、appSecret String appId = "123456"; String appSecret = appMap.get(appId); String timestamp = String.valueOf(System.currentTimeMillis()); // 随机数 String nonce = RandomStringUtils.randomAlphanumeric(10); // 业务请求参数 UserEntity userEntity = new UserEntity(); userEntity.setUserId("1"); userEntity.setPhone("13912345678"); // 使用sha256的方式生成签名 String sign = getSHA256Str(JSONObject.toJSONString(userEntity)); Map data = Maps.newHashMap(); data.put("appId", appId); data.put("nonce", nonce); data.put("sign", sign); data.put("timestamp", timestamp); Set keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[0]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("appSecret=").append(appSecret); System.out.println("【请求方】拼接后的参数:" + sb.toString()); System.out.println(); // 使用sha256withRSA的方式对header中的内容加签 String appSign = sha256withRSASignature(appKeyPair.get(appId).get("privateKey"), sb.toString()); System.out.println("【请求方】appSign:" + appSign); System.out.println(); // 请求参数组装 Header header = Header.builder() .appId(appId) .nonce(nonce) .sign(sign) .timestamp(timestamp) .appSign(appSign) .build(); APIRequestEntity apiRequestEntity = new APIRequestEntity(); apiRequestEntity.setHeader(header); apiRequestEntity.setBody(userEntity); String requestParam = JSONObject.toJSONString(apiRequestEntity); System.out.println("【请求方】接口请求参数: " + requestParam); return requestParam; } /** * 私钥签名 * * @param privateKeyStr * @param dataStr * @return */ public static String sha256withRSASignature(String privateKeyStr, String dataStr) { try { byte[] key = Base64.decodeBase64(privateKeyStr); byte[] data = dataStr.getBytes(); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data); return new String(Base64.encodeBase64(signature.sign())); } catch (Exception e) { throw new RuntimeException("签名计算出现异常", e); } } /** * 公钥验签 * * @param dataStr * @param publicKeyStr * @param signStr * @return * @throws Exception */ public static boolean rsaVerifySignature(String dataStr, String publicKeyStr, String signStr) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr)); PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(publicKey); signature.update(dataStr.getBytes()); return signature.verify(Base64.decodeBase64(signStr)); } /** * 生成公私钥对 * * @throws Exception */ public static void initKeyPair(String appId) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map keyMap = Maps.newHashMap(); keyMap.put("publicKey", new String(Base64.encodeBase64(publicKey.getEncoded()))); keyMap.put("privateKey", new String(Base64.encodeBase64(privateKey.getEncoded()))); appKeyPair.put(appId, keyMap); } private static String getAppSecret(String appId) { return String.valueOf(appMap.get(appId)); } @SneakyThrows public static String getSHA256Str(String str) { MessageDigest messageDigest; messageDigest = MessageDigest.getInstance("SHA-256"); byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8)); return Hex.encodeHexString(hash); } }

对比带加密和不带加密版本的执行效果。

【请求方】拼接后的参数:appId=123456&nonce=TWDo7kxIkE&sign=c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5×tamp=1653486151381&appSecret=654321 【请求方】appSign:N9QZEUvaK6HrzGlg2tnry4BoqWpuvy9UnXw2UBmJIyc6kDwrofCwnJs1Djc6+m619vVNbsrM8/EOHFnROrxVeNwD0n3hCDAlYgZ4uZVI/d3ZAFQqbl9qiNJKp0UgyB/Za6oyT+xdtihwwZb6qH5J/UOeGBJLel61kxk7/tIgtQsZ9s2Akk4epjsLEYbGhlHzBT7Misq7Ij60ODr21eKtzdEj6NQeM+3mLkAF/O27BOcjBcOdExRYwtmwvHRtYUnBuapVcnLtDif+7oauHJDnEFCt5+n8P5Q0lGSf5kD7sVz9rviqGV271n8R/P3rIc0Y/g1rm5jm42uy0j7fwIIj7Q== 【请求方】接口请求参数: {"body":{"phone":"13912345678","userId":"1"},"header":{"appId":"123456","appSign":"N9QZEUvaK6HrzGlg2tnry4BoqWpuvy9UnXw2UBmJIyc6kDwrofCwnJs1Djc6+m619vVNbsrM8/EOHFnROrxVeNwD0n3hCDAlYgZ4uZVI/d3ZAFQqbl9qiNJKp0UgyB/Za6oyT+xdtihwwZb6qH5J/UOeGBJLel61kxk7/tIgtQsZ9s2Akk4epjsLEYbGhlHzBT7Misq7Ij60ODr21eKtzdEj6NQeM+3mLkAF/O27BOcjBcOdExRYwtmwvHRtYUnBuapVcnLtDif+7oauHJDnEFCt5+n8P5Q0lGSf5kD7sVz9rviqGV271n8R/P3rIc0Y/g1rm5jm42uy0j7fwIIj7Q==","nonce":"TWDo7kxIkE","sign":"c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5","timestamp":"1653486151381"}} 【提供方】验证通过! ***************************业务参数加密版本*************************** 【请求方】拼接后的参数:appId=123456&nonce=5haU5Ltbm5&sign=c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5×tamp=1653486151555&appSecret=654321 【请求方】appSign:ytqqV+llqbpRBzzO2Kr/gEyLgmBnQHQ5iLDdEGvU2Rq6HhahC46i3hV85LI392Yduh98EvC9x2i8Vve6IHY4Gsp6pch4gw6DxvndB9Gk84NYBMYnsXc9+/ddKD44CbDMd7BzHB+A5zNcuXyJqFYQLxV+5GpHXFYtmqHB+jVUYK2ZnPxMi2PXkKENlsSYUdNh8xp2gM1OX+gB9T/yCoVTAN3itHAzBCAS5c52vTGbz9qKw7oUx4L5Hs5OkxAw8q3t8H+srX2pi/F9+1KOAIbw6Y7V4YggMXq3WtXTlPLnx7z+YJu9DsoevDKDdJPkiq86liUMmUFyXlAViW2bx26RYQ== 【请求方】接口请求参数: {"body":"RLdUZ42CyRpUAzu9CsDS8Q0X1ARoM0dynwSmF66bfkDxpWBDQC0xYFDjBJWk13WK","header":{"appId":"123456","appSign":"ytqqV+llqbpRBzzO2Kr/gEyLgmBnQHQ5iLDdEGvU2Rq6HhahC46i3hV85LI392Yduh98EvC9x2i8Vve6IHY4Gsp6pch4gw6DxvndB9Gk84NYBMYnsXc9+/ddKD44CbDMd7BzHB+A5zNcuXyJqFYQLxV+5GpHXFYtmqHB+jVUYK2ZnPxMi2PXkKENlsSYUdNh8xp2gM1OX+gB9T/yCoVTAN3itHAzBCAS5c52vTGbz9qKw7oUx4L5Hs5OkxAw8q3t8H+srX2pi/F9+1KOAIbw6Y7V4YggMXq3WtXTlPLnx7z+YJu9DsoevDKDdJPkiq86liUMmUFyXlAViW2bx26RYQ==","nonce":"5haU5Ltbm5","sign":"c630885277f9d31cf449697238bfc6b044a78545894c83aad2ff6d0b7d486bc5","timestamp":"1653486151555"}} 业务参数解密后内容:{"phone":"13912345678","userId":"1"} 【提供方】验证通过!


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有