Android 接口加签的使用

tech2026-08-06  2

为什么要做接口加签呢,目的就是防止别人恶意估计,就是频繁的调用例如 发短信验证的接口,导致公司产生高昂的短信费用。

那么接口加签就可以解决这个问题了,但是需要客户端和服务端约定好。以下就是加签的过程。

步骤一:服务端给我们一个接口来获取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(); } }

最重要的是要和后端定好拼接的规则,其他的倒是没什么难点。

最新回复(0)