需求

为了安全考虑,Android端的接口需要做加密

刚接到这个需求时,心里一惊。由于这一块,算是归于常识的盲区。不过也正好趁着这个机会,学一下关于接口加密相关的常识。

AES加解密

加密办法分好多种,由于后端同学和ios端已经接入了AES加密,所以这儿首要介绍AES加密。

关于AES加密,重点还是密钥,客户端和后台根据对应的密钥进行加解密。

而接口加密,并不是加密整个接口。
www.baidu.com/search?=android
例如这个接口,假如需要对其进行加密的话,一般状况下都是对”search?=android”进行加密。加密的办法一般是由后台和前端一致商量(大部分还都是后端做决定的)。

加密逻辑

网络恳求大略分两种,一种是GET,另一种是POST。注:当然还有其他的的恳求,如PUT、DELETE,但大多数开发状况下,只会用到这两种办法。

GET恳求是将参数query拼接在url后边,而POST则是将参数存放在requestBody中。所以这两种办法加密逻辑是不一样的。

GET

按着后端同学提供出来的文档能够知道,GET恳求分两种状况,假如参数为空,则不需要加密,直接恳求;假如参数不为空,则将一切参数加密,将加密后的字符串拼接在域名后边,

例如,

原链接为www.baidu.com/search?=android

那么,加密后格局为www.baidu.com/url?key=abcdefg

其中”abcdefg“为加密后的字符串

POST

由于post恳求一切参数都在body里,所以只要对整个body进行加密,然后body传递加密后的字符串就行了。

Response

一切响应接口回来的数据都进行Base64编码及Aes解密。

其他的逻辑

这儿提到的加密逻辑,只是和后端协商后得到的,当然还有其他的加密办法,像能够在接口处拼接时刻戳,将token进行MD5,再拼接在url上,或许其他的加密办法(RSA等办法)。

代码

直接在拦截器一致处理GETPOST恳求。

1、判别办法

2、假如是GET,则获取一切的query,不然直接第5步
3、将获取的参数转为json格局并加密
4、拼接
5、假如是POST,获取body数据
6、将body转为json格局字符串并加密
7、将字符串作为恳求体post恳求
8、回来response的data数据解密

1、在拦截器内,经过val originalRequest = chain.request()办法获取恳求链接,再经过 originalRequest.method()办法获取恳求的办法。

2、经过val url = originalRequest.url()获取恳求的url链接,再经过 url.queryParameterNames()
获取到此次GET恳求的一切参数名,经过url.queryParameter(QueryName)获取参数名对应的参数值。

3、将一切参数转换为json格局,然后经过密钥进行加密操作。

4、拼接。这儿用密钥对json字符串进行aes加密,然后对链接进行拼接。能够经过url.encodePath()获取途径。

5、POST恳求同理

6、将response的body的data数据解密

7、将response的body替换,并回来response

问题

1、POST恳求的body办法不能直接获取。

2、有一点疑问的是,什么叫将body加密,再把加密后的字符串作为body传值。

3、response并不能直接设置body。

处理

1、这个容易处理,网上有很多方案。

2、一般POST恳求,会经过

data.toJson().toRequestBody(“application/json;charset=UTF-8”.toMediaTypeOrNull())

或许

RequestBody。create(MediaType.parse(“application/json;charset=utf-8”), data.toJson())

这两种办法,都将数据转为body,而经过POSTMAN能够看到此时body是一串json字符串
{”name“:"xiaoming", age: 13}

那,怎样讲json转化,然后将加密后的内容重新作为body传值呢?

或许聪明的我们一下子想到了,但其时coding的时分,我脑子一时刻没转过来。

既然data能够toRequestBody转换成body的值,那么,加密串呢?

AesUtil.decrypt(data).toRequestBody(“application/json;charset=UTF-8”.toMediaTypeOrNull())

发现这样也是行的,body里能够传任何数据,但它有必要是RequestBody格局。

3、本来的val response = chain.proceed(originalRequest)中的response是不能设置body的,那么,解密后的数据只能用新建的response。

val newResponse = okhttp3.Response.Builder()

将老的response的code、message赋值给newResponse,设置body,当然还不要忘记了设置newRepsonse的request和protocol,不然后报错。

相关代码

以下是拦截器内的代码

var originalRequest = chain.request()
val builder = originalRequest.newBuilder()
builder.header("token", token)
val url = originalRequest.url()
val path = url.encodedPath()
val query = url.encodedQuery()
val method = originalRequest.method()
when (method) {
    "GET" -> {
        if (!query.isNullOrEmpty()) {
            //恳求参数不为空:GET恳求接口加密为全参数加密,详细格局为  url?key=加密串
            val queryList = url.queryParameterNames()
            val map = HashMap<String, String>()
            val iterator = queryList.iterator()
            for (i in queryList.indices) {
                val queryName: String = iterator.next()
                map[queryName] = url.queryParameter(queryName) ?: ""
            }
            val s = gson.toJson(map)
            val newBody = AesUtil.encrypt(s, KEY)
            val newQuery = "?key=${URLEncoder.encode(newBody)}"
            val newUrl = BASE_URL + path + newQuery
            builder.url(newUrl)
            runOnUiThread {
                tvNew?.text = newUrl
            }
        } else {
            //恳求参数为空:不需要进行加密,直接恳求
        }
    }
    "POST" -> {
        val body = originalRequest.body()
        //暂不考虑formBody的状况,由于表单格局提交的post恳求,不需要加密
        val buffer = okio.Buffer()
        body?.writeTo(buffer)
        var charset = Charset.forName("utf-8")
        val contentType = body?.contentType()
        if (contentType != null) {
            charset = contentType.charset(Charset.forName("utf-8"))
        }
        val data = buffer.readString(charset)
        runOnUiThread {
            tvOrigin?.text = data
        }
        val newBody =
            AesUtil.encrypt(data, KEY) ?: ""
        runOnUiThread {
            tvNew?.text = newBody
        }
        val requestBody = RequestBody.create(
            MediaType.parse("application/json;charset=utf-8"),
            newBody
        )
        builder.post(requestBody)
    }
}
originalRequest = builder.build()
val response = chain.proceed(originalRequest)
val responseBody = response.body()
var charset = Charset.forName("utf-8")
val source = responseBody?.source()
source?.request(Long.MAX_VALUE)
val buffer = source?.buffer
val contentType = responseBody?.contentType()
if (contentType != null) {
    try {
        charset = contentType.charset(Charset.forName("utf-8"))
    } catch (e: Exception) {
        return@Interceptor response
    }
}
val contentLength = responseBody?.contentLength()
if (contentLength != 0L) {
    val oriData = buffer?.clone()?.readString(charset)
    Log.e("TAG", oriData.toString())
    val data = AesUtil.decrypt(oriData, KEY) ?: ""
    val newResponse = okhttp3.Response.Builder()
    newResponse.code(response.code())
    newResponse.message(response.message())
    val responseBody1 =
        ResponseBody.create(MediaType.parse("application/json;charset=utf-8"), data)
    newResponse.body(responseBody1)
    newResponse.request(originalRequest)
    newResponse.protocol(response.protocol())
    return@Interceptor newResponse.build()
}
response

结语

新技能get!+1

接口加密,尤其是AES加密其实不难,将需求一步步剥离出来就好处理了,而且加解密重点是密钥。