为什么要做接口加签呢,目的就是防止别人恶意估计,就是频繁的调用例如 发短信验证的接口,导致公司产生高昂的短信费用。
那么接口加签就可以解决这个问题了,但是需要客户端和服务端约定好。以下就是加签的过程。
步骤一:服务端给我们一个接口来获取RAS的公钥,或者将服务端的公钥写死在客户端。
这里为了方便说明,我们是将公钥放在本地的。
例如,我们拿到的密钥为:KEY = “kqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFCVD2j”
然后 通过算法获取对应KEY 的密钥对象 RSAPrivateKey :
public static RSAPrivateKey loadPrivateKey(String privateKeyStr) {
RSAPrivateKey key = null;
try {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyStr, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (NoSuchAlgorithmException e) {
Logger.e(e);
} catch (InvalidKeySpecException e) {
Logger.e(e);
} catch (NullPointerException e) {
Logger.e(e);
}
return key;
}
然后将时间戳、应用的一些信息,如版本号、包名等,拼接成字符串 info,然后将 生成的 info作为http请求的 header 的Value, Key 可以和后台约定好,假设为 base-info。
.addHeader("base-info", info)
如果有请求体,那么需要对类型为json的请求体和上面的info拼接生成字符串 originData
然后再用和后端约定的对称加密算法 对 上面的 originData 进行加密生成 encryData。
然后将 生成的 encryData 作为http请求的 header 的Value , Key 可以和后台约定好,假设为 signature。
.addHeader("signature", encryData)
服务端 : 服务端通过相同的规则,将info 和 originData 拼接起来作为result,然后利用之前约定对称加密算法 进行验证 result 和 encryData 是否匹配。
下面给出代码:
我这边是通过 给 Retrofit 的 OkHttp设置 inteceptor来实现的:
public class SignInterceptor implements Interceptor {
private static final String TAG = "SignInterceptor";
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final String PKG_NAME = Engine.getInstance().getContext().getPackageName();
private static final String DEVICE_ID = DeviceUtils.getUUID();
private static final String IFULI_OS_INFO = "pig|" + AppUtils.getAppVersion(Engine.getInstance().getContext()) + "|" + PKG_NAME + "|" + DEVICE_ID;
private static final RSAPrivateKey KEY = EncryptUtils.loadPrivateKey("MIIEvQIBAxxx90YCGOPf8CjAZQUQ7Ke+j8DXRoBYjDlhXXETMP/A3tZ26k0X4iW5/BSDw4HGSHQdcD13CeDb3O60rsbZckJGYg1s=");
private static final RSAPublicKey TEST_KEY = EncryptUtils.loadPublicKey("MIIBIjANBgkqhkiG9w0BAQ3V1oqGj0pOwK2Prpfgx6NcHV7NLynEvh+bU03hLnn73MJc0uD+aaWsawIDAQAB");
private static final int RSA = 1;
private static final Pattern PATH_PATTERN = Pattern.compile("/v\\d+/");
@Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(createSignRequest(chain.request()));
}
private Request createSignRequest(Request request) {
String timestamp = String.valueOf(System.currentTimeMillis());
String sign = createSign(request, timestamp);
return request.newBuilder()
.addHeader("pig-info", IFULI_OS_INFO)
.addHeader("pig-signature", timestamp + "|" + RSA + "|" + sign)
.build();
}
private String createSign(Request request, String timestamp) {
StringBuilder signBuilder = new StringBuilder();
appendSignValue(signBuilder, IFULI_OS_INFO);
String path = request.url().encodedPath();
appendSignValue(signBuilder, subPath(path));
String query = request.url().query();
appendSignValue(signBuilder, sortQuery(query));
String body = null;
RequestBody requestBody = request.body();
//只对json类型请求加签,否则可能是上传图片啥的
try {
if (requestBody != null && requestBody.contentType() != null &&
"application".equalsIgnoreCase(requestBody.contentType().type()) &&
"json".equalsIgnoreCase(requestBody.contentType().subtype())) {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
body = buffer.readString(UTF8);
}
} catch (Exception e) {
e.printStackTrace();
}
appendSignValue(signBuilder, body);
appendSignValue(signBuilder, timestamp);
String originalSign = signBuilder.toString();
String sign = EncryptUtils.signSHA256withRSAPrivate(KEY, originalSign);
if (Engine.getInstance().isDebug()) {
Logger.d(TAG, "original sign: " + originalSign + "\nfinal sign: " + sign + "\nverify result: " + EncryptUtils.verifySignSHA256withRSAPublic(TEST_KEY, originalSign, sign));
}
return sign;
}
private String subPath(String path) {
if (TextUtils.isEmpty(path)) {
return path;
}
int index = 0;
try {
Matcher m = PATH_PATTERN.matcher(path);
if (m.find()) {
index = m.start();
}
} catch (Exception e) {
Logger.e(e);
}
return path.substring(index); //保留'/',结果为'/v1/xxx'
}
private String sortQuery(String query) {
if (TextUtils.isEmpty(query) || !query.contains("&")) {
return query;
}
String[] params = query.split("&");
List<String> list = Arrays.asList(params);
Collections.sort(list);
StringBuilder ret = new StringBuilder();
for (String s : list) {
ret.append(s).append("&");
}
return ret.toString().substring(0, ret.length() - 1);
}
private void appendSignValue(StringBuilder signBuilder, String value) {
if (TextUtils.isEmpty(value)) {
return;
}
signBuilder.append(value);
}
}
public class EncryptUtils {
/**
* 字节数据转字符串专用集合
*/
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* @param text 要加密的字符串
* @return 加密的字符串
* SHA1加密
*/
public static String SHA1(String text) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA-1");
digest.update(text.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
public static long CRC32(String text) {
CRC32 crc32 = new CRC32();
crc32.update(text.getBytes());
return crc32.getValue();
}
/**
* 从字符串中加载公钥
*
* @param publicKeyStr 公钥数据字符串
*/
@Nullable
public static RSAPublicKey loadPublicKey(String publicKeyStr) {
RSAPublicKey key = null;
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKeyStr, Base64.DEFAULT));
key = (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
Logger.e(e);
} catch (InvalidKeySpecException e) {
Logger.e(e);
} catch (NullPointerException e) {
Logger.e(e);
}
return key;
}
/**
* 从字符串中加载公钥
*
* @param privateKeyStr 密钥数据字符串
*/
@Nullable
public static RSAPrivateKey loadPrivateKey(String privateKeyStr) {
RSAPrivateKey key = null;
try {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyStr, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (NoSuchAlgorithmException e) {
Logger.e(e);
} catch (InvalidKeySpecException e) {
Logger.e(e);
} catch (NullPointerException e) {
Logger.e(e);
}
return key;
}
/**
* 公钥加密过程
*
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
*/
@Nullable
public static byte[] encryptRSAPublic(RSAPublicKey publicKey, byte[] plainTextData) {
byte[] output = null;
if (publicKey == null) {
Logger.d("public key is empty");
return output;
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
output = cipher.doFinal(plainTextData);
} catch (NoSuchAlgorithmException e) {
Logger.e(e);
} catch (NoSuchPaddingException e) {
Logger.e(e);
} catch (InvalidKeyException e) {
Logger.e(e);
} catch (IllegalBlockSizeException e) {
Logger.e(e);
} catch (BadPaddingException e) {
Logger.e(e);
}
return output;
}
@Nullable
public static String signSHA256withRSAPrivate(RSAPrivateKey privateKey, String data) {
String ret = null;
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
ret = Base64.encodeToString(signature.sign(), Base64.NO_WRAP);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
public static boolean verifySignSHA256withRSAPublic(RSAPublicKey publicKey, String data, String sign) {
boolean ret = false;
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes());
ret = signature.verify(Base64.decode(sign, Base64.NO_WRAP));
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
@Nullable
public static String signMD5withRSAPrivate(RSAPrivateKey privateKey, String data) {
String ret = null;
try {
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
ret = Base64.encodeToString(signature.sign(), Base64.NO_WRAP);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
public static boolean verifySignMD5withRSAPublic(RSAPublicKey publicKey, String data, String sign) {
boolean ret = false;
try {
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes());
ret = signature.verify(Base64.decode(sign, Base64.NO_WRAP));
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/**
* 利用java原生的类实现SHA256加密
* @param str 加密后的报文
* @author
*/
public static String getSHA256(String str) {
MessageDigest messageDigest;
String encodestr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodestr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encodestr;
}
/**
* 将byte转为16进制
*
* @author CaoLu.
*/
private static String byte2Hex(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i = 0; i < bytes.length; i++) {
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length() == 1) {
// 1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
}
最重要的是要和后端定好拼接的规则,其他的倒是没什么难点。