文章目录
- 【如何设计安全可靠的开放接口】系列
- 前言
- 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 {
static Map<String, String> appMap = Maps.newConcurrentMap();
static Map<String, String> appAesMap = Maps.newConcurrentMap();
static Map<String, Map<String, String>> appKeyPair = Maps.newConcurrentMap();
static Cache<String, String> 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 {
String appId = initAppInfo();
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();
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("数据签名错误!");
}
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("请求过期!");
}
String str = cache.getIfPresent(appId + "_" + nonce);
if (Objects.nonNull(str)) {
throw new Exception("请求失效!");
}
cache.put(appId + "_" + nonce, "1");
Map<String, String> data = Maps.newHashMap();
data.put("appId", appId);
data.put("nonce", nonce);
data.put("sign", sign);
data.put("timestamp", timestamp);
Set<String> 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 < blockSize; ++i) {
iv[i] = 0;
}
return iv;
} catch (Exception e) {
int blockSize = 16;
byte[] iv = new byte[blockSize];
for (int i = 0; i < blockSize; ++i) {
iv[i] = 0;
}
return iv;
}
}
public static String decrypt(String content, String key, String charset) throws Exception {
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 {
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");
String sign = getSHA256Str(JSONObject.toJSONString(userEntity));
Map<String, String> data = Maps.newHashMap();
data.put("appId", appId);
data.put("nonce", nonce);
data.put("sign", sign);
data.put("timestamp", timestamp);
Set<String> 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();
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() {
String appId = "123456";
String appSecret = "654321";
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("数据签名错误!");
}
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("请求过期!");
}
String str = cache.getIfPresent(appId + "_" + nonce);
if (Objects.nonNull(str)) {
throw new Exception("请求失效!");
}
cache.put(appId + "_" + nonce, "1");
Map<String, String> data = Maps.newHashMap();
data.put("appId", appId);
data.put("nonce", nonce);
data.put("sign", sign);
data.put("timestamp", timestamp);
Set<String> 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() {
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");
String sign = getSHA256Str(JSONObject.toJSONString(userEntity));
Map<String, String> data = Maps.newHashMap();
data.put("appId", appId);
data.put("nonce", nonce);
data.put("sign", sign);
data.put("timestamp", timestamp);
Set<String> 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();
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;
}
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);
}
}
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));
}
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<String, String> 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"}
【提供方】验证通过!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)