美文网首页
Android-接口数据加密

Android-接口数据加密

作者: 超人TIGA | 来源:发表于2020-07-23 22:47 被阅读0次

    为了增加项目的安全性,避免被别人通过拦截接口请求和相关参数,从而模拟数据并频繁调用接口,造成服务器压力过大;还有一些重要的接口,例如红包、抽奖、充值兑换等,如果被恶意刷,都会对服务器和项目造成十分严重的影响。所以,对接口请求时加密或者防止被刷接口就很有必要了。

    我使用过的加密方式有2种,一种是使用ProtocolBuffers来加密;另一种就是对接口参数进行MD5加密,生成一个token给后台验证,验证通过的才是正常调用。
    这次主要讲的是后一种,使用MD5对接口的数据进行加密并生成token。
    我使用的网络请求框架是OKhttp+retrofit,所以统一在request里增加interceptor进行拦截处理就好。
    1、定义interceptor

    class RequestEncryptInterceptor : Interceptor {
    
        private val UTF8 = Charset.forName("UTF-8")
    
        override fun intercept(chain: Interceptor.Chain): Response {
            var token: String? = initTokenByMD5(chain.request(), ApiConstants.UUID)
    
            if (!TextUtils.isEmpty(token)) {
                builder.addHeader("token", token)
            }
            return chain.proceed(builder.build())
        }
    }
    

    上面代码中的ApiConstants.UUID是一个服务器返回的值,在APP一启动的时候,根据手机的唯一码进行请求,服务器返回后保存到手机本地。initTokenByMD5方法就是主要的加密token方法。

    
        private fun initTokenByMD5(request: Request, uuid: String): String {
            val uri = Uri.parse(request.url().toString())
            var path = uri.path
            val map = HashMap<String, Any>()
            if (request.body() != null) {
                try {
                    val buffer = Buffer()
                    request.body()!!.writeTo(buffer)
                    var charset: Charset? = UTF8
                    val contentType = request.body()!!.contentType()
                    if (contentType != null) {
                        charset = contentType.charset(UTF8)
                    }
                    if (isPlaintext(buffer)) {
                        map.putAll(JsonParseUtil.jsonFormatter(buffer.readString(charset!!)))
                    }
                } catch (e: Exception) {
                    KLog.e(e)
                }
    
            } else {
                val names = uri.queryParameterNames
                val iterator = names.iterator()
                while (iterator.hasNext()) {
                    val key = iterator.next()
                    map[key] = uri.getQueryParameter(key)!!
                }
            }
            return TokenUrlValidate.getUrlValidateToken(path, map, uuid)
        }
    
    

    initTokenByMD5方法,作用主要就是把请求里面的参数取出,填入一个HashMap中,而请求又分get和post,所以需要根据request.body()是否为空来判断是哪种类型。else里就是get请求,处理也相对简单,取出键值对,对应填入map就可以了。
    而post请求,则需要对请求体进行处理,首先检查内容是否都是相同格式(UTF-8),将多层嵌套的json转化为单层的。

    @Throws(EOFException::class)
    fun Interceptor.isPlaintext(buffer: Buffer): Boolean {
        try {
            val prefix = Buffer()
            val byteCount = if (buffer.size() < 64) buffer.size() else 64
            buffer.copyTo(prefix, 0, byteCount)
            for (i in 0..15) {
                if (prefix.exhausted()) {
                    break
                }
                val codePoint = prefix.readUtf8CodePoint()
                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                    return false
                }
            }
            return true
        } catch (e: EOFException) {
            return false // Truncated UTF-8 sequence.
        }
    }
    
    /**
     * 多层嵌套json数据转换为单层,同时规格化
     **/
    public class JsonParseUtil {
    
        /**
         * 把拍平后的json进行格式化处理,输出标准的json格式
         *
         * @return
         */
        public static Map<String, String> jsonFormatter(String uglyJSONString) {
            Map<String, String> map = new HashMap<>();
            parseJson2Map(map, uglyJSONString, null);
    
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(map.entrySet());
            // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
                @Override
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                    return (o1.getKey()).compareTo(o2.getKey());
                }
            });
            return map;
        }
    
        public static void parseJson2Map(Map<String, String> map, JsonObject jsonObject, String parentKey) {
            for (Map.Entry<String, JsonElement> object : jsonObject.entrySet()) {
                String key = object.getKey();
                JsonElement value = object.getValue();
                String fullkey = (null == parentKey || parentKey.trim().equals("")) ? key : parentKey.trim() + "." + key;
                //判断对象的类型,如果是空类型则安装空类型处理
                if (value.isJsonNull()) {
                    map.put(fullkey, null);
                    continue;
                    //如果是JsonObject对象则递归处理
                } else if (value.isJsonObject()) {
                    parseJson2Map(map, value.getAsJsonObject(), fullkey);
                    //如果是JsonArray数组则迭代,然后进行递归
                } else if (value.isJsonArray()) {
                    JsonArray jsonArray = value.getAsJsonArray();
                    Iterator<JsonElement> iterator = jsonArray.iterator();
                    while (iterator.hasNext()) {
                        JsonElement jsonElement1 = iterator.next();
                        if (jsonElement1.isJsonPrimitive()) {
    //                        parseJson2Map(map, jsonElement1.getAsJsonArray(), fullkey);
                            getJsonPrimitive(map, jsonElement1, fullkey);
                            continue;
                        }
                        parseJson2Map(map, jsonElement1.getAsJsonObject(), fullkey);
                    }
                    continue;
                    // 如果是JsonPrimitive对象则获取当中的值,则还需要再次进行判断一下
                } else if (value.isJsonPrimitive()) {
                    getJsonPrimitive(map, value, fullkey);
                }
            }
        }
    
    
        private static void getJsonPrimitive(Map map, JsonElement value, String fullkey) {
            try {
                JsonElement element = new JsonParser().parse(value.getAsString());
                if (element.isJsonNull()) {
                    map.put(fullkey, value.getAsString());
                } else if (element.isJsonObject()) {
                    parseJson2Map(map, element.getAsJsonObject(), fullkey);
                } else if (element.isJsonPrimitive()) {
                    JsonPrimitive jsonPrimitive = element.getAsJsonPrimitive();
    
                    if (jsonPrimitive.isNumber()) {
                        map.put(fullkey, jsonPrimitive.getAsNumber());
                    } else {
                        map.put(fullkey, jsonPrimitive.getAsString());
                    }
                } else if (element.isJsonArray()) {
                    JsonArray jsonArray = element.getAsJsonArray();
                    Iterator<JsonElement> iterator = jsonArray.iterator();
                    while (iterator.hasNext()) {
                        parseJson2Map(map, iterator.next().getAsJsonObject(), fullkey);
                    }
                }
            } catch (Exception e) {
                Object val = map.get(fullkey);
                if (val != null) {
                    map.put(fullkey, val + value.getAsString());
                } else {
                    map.put(fullkey, value.getAsString());
                }
            }
        }
    
        /**
         * 使用Gson拍平json字符串,即当有多层json嵌套时,可以把多层的json拍平为一层
         *
         * @param map
         * @param json
         * @param parentKey
         */
        public static void parseJson2Map(Map map, String json, String parentKey) {
            JsonElement jsonElement = new JsonParser().parse(json);
            if (jsonElement.isJsonObject()) {
                JsonObject jsonObject = jsonElement.getAsJsonObject();
                parseJson2Map(map, jsonObject, parentKey);
                //传入的还是一个json数组
            } else if (jsonElement.isJsonArray()) {
                JsonArray jsonArray = jsonElement.getAsJsonArray();
                Iterator<JsonElement> iterator = jsonArray.iterator();
                while (iterator.hasNext()) {
                    JsonElement next = iterator.next();
                    if (next.isJsonPrimitive()) {
                        Object val = map.get("array");
                        if (val != null) {
                            map.put("array",val+ next.getAsString());
                        }else {
                            map.put("array", next.getAsString());
                        }
                        continue;
                    }
                    parseJson2Map(map, next.getAsJsonObject(), parentKey);
                }
            } else if (jsonElement.isJsonPrimitive()) {
                KLog.e("jsonElement.isJsonPrimitive() please check the json format!");
            } else if (jsonElement.isJsonNull()) {
                KLog.e("jsonElement.isJsonNull() please check the json format!");
            }
        }
    }
    

    到这里为止,就已经算是把加密生产token前的工作做好了,分别得到了请求url的路径,请求参数封装后的map,还有uuid。接下来就可以进行加密生成token:

    
        public static String getUrlValidateToken(String path, Map<String, Object> mapParam, String uuid) {
            String token = path + formatUrlMap(mapParam, false, false) + "&key=" + TaUtils.getAbc() + "&uuid=" + uuid + "&timeStamp=" + ApiConstants.getComputeServerTime() / TotpUtil.STEP;
            if (!TnaotApplication.Companion.instance().isRelease()) {
                KLog.v("getUrlValidateToken", token);
            }
            return Md5Util.toMD5(token).toUpperCase();
        }
    
    
        /**
         * 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
         * 实现步骤: <br>
         *
         * @param paraMap    要排序的Map对象
         * @param urlEncode  是否需要URLENCODE
         * @param keyToLower 是否需要将Key转换为全小写
         *                   true:key转化成小写,false:不转化
         * @return
         */
        private static String formatUrlMap(Map<String, Object> paraMap, boolean urlEncode, boolean keyToLower) {
            String buff = "";
            Map<String, Object> tmpMap = paraMap;
            try {
                List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(tmpMap.entrySet());
                // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
                Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
                    @Override
                    public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                        return (o1.getKey()).compareTo(o2.getKey());
                    }
                });
                // 构造URL 键值对的格式
                StringBuilder buf = new StringBuilder();
                for (Map.Entry<String, Object> item : infoIds) {
                    if (!TextUtils.isEmpty(item.getKey())) {
                        String key = item.getKey();
                        String val = "";
                        if (item.getValue() != null) {
                            val = item.getValue().toString();
                        }
                        if (urlEncode) {
                            val = URLEncoder.encode(val, "utf-8");
                        }
                        if (keyToLower) {
                            buf.append(key.toLowerCase() + "=" + val);
                        } else {
                            buf.append(key + "=" + val);
                        }
                        buf.append("&");
                    }
                }
                buff = buf.toString();
                if (buff.isEmpty() == false) {
                    buff = buff.substring(0, buff.length() - 1);
                }
            } catch (Exception e) {
                return "";
            }
            return buff;
        }
    
    
    public class Md5Util {
    
        private static MessageDigest mDigest = null;
    
        static {
            try {
                mDigest = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 对key进行MD5加密,如果无MD5加密算法,则直接使用key对应的hash值。
         */
        public static String toMD5(String key) {
            //获取MD5算法失败时,直接使用key对应的hash值
            if (mDigest == null) {
                return String.valueOf(key.hashCode());
            } else {
                return toMD5(key.getBytes());
            }
        }
    
        public static String toMD5(byte[] bytes) {
            if (mDigest == null) {
                return "";
            }
            String cacheKey;
            mDigest.update(bytes);
            cacheKey = bytesToHexString(mDigest.digest());
            return cacheKey;
        }
    
    
        private static String bytesToHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                String hex = Integer.toHexString(0xFF & bytes[i]);
                if (hex.length() == 1) {
                    sb.append('0');
                }
                sb.append(hex);
            }
            return sb.toString();
        }
    }
    

    完成!这样就能根据接口参数加密生成一个token了,只有token对的上的请求,服务器才做出响应,达到了一定的防止刷接口的风险。

    相关文章

      网友评论

          本文标题:Android-接口数据加密

          本文链接:https://www.haomeiwen.com/subject/ljxfhktx.html