1、身份验证:是否是我规定的那个人
2、防篡改:是否被第三方劫持并篡改参数
3、防重放:是否重复请求
1、约定appKey,保证该调用请求是平台授权过的调用方发出的,保证请求方唯一性。
2、将appKey加入值请求参数,如:http://****?appKey=1232456&其他参数。
3、对参数进行排序(排序方法约定好即可,如:ASCII码对比),将参数名参数值拼接成字符串。
4、使用md5摘要算法获取摘要,将摘要放入请求头。
package com.zhangteng.rxhttputils.interceptor
import android.text.TextUtils
import com.google.gson.JsonParser
import com.zhangteng.rxhttputils.utils.MD5Util.md5Decode32
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.RequestBody
import okhttp3.Response
import okio.Buffer
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
import kotlin.collections.set
/**
* 添加签名拦截器
* Created by Swing on 2019/10/20.
*/
class SignInterceptor(private val appKey: String) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val requestBuilder = request.newBuilder()
val urlBuilder = request.url().newBuilder()
val params: MutableMap =
TreeMap()
if (METHOD_GET == request.method()) {
val httpUrl = urlBuilder.build()
val paramKeys = httpUrl.queryParameterNames()
for (key in paramKeys) {
val value = httpUrl.queryParameter(key)
if (!TextUtils.isEmpty(value)) params[key] = value
}
} else if (METHOD_POST == request.method()) {
if (request.body() is FormBody) {
val formBody = request.body() as FormBody?
for (i in 0 until formBody!!.size()) {
params[formBody.encodedName(i)] = formBody.encodedValue(i)
}
} else if (request.body() is RequestBody) {
val requestBody = request.body()
val buffer = Buffer()
requestBody!!.writeTo(buffer)
var charset = Charset.forName("UTF-8")
val contentType = requestBody.contentType()
if (contentType != null) {
charset = contentType.charset()
}
val paramJson =
buffer.readString(charset ?: Charset.defaultCharset())
val jsonObject = JsonParser().parse(paramJson).asJsonObject
jsonObject.entrySet().forEach {
val jsonElement = it.value
if (jsonElement != null && !jsonElement.isJsonArray && !jsonElement.isJsonObject && !jsonElement.isJsonNull) {
val value = jsonElement.asString
if (!TextUtils.isEmpty(value)) params[it.key] = value
}
}
}
}
val sign = StringBuilder()
sign.append(appKey)
for (key in params.keys) {
sign.append(key).append(params[key])
}
val _timestamp = System.currentTimeMillis()
sign.append("_timestamp").append(_timestamp)
sign.append(appKey)
requestBuilder.addHeader("_timestamp", _timestamp.toString())
requestBuilder.addHeader("_sign", md5Decode32(sign.toString()))
return chain.proceed(requestBuilder.build())
}
companion object {
private const val METHOD_GET = "GET"
private const val METHOD_POST = "POST"
}
}
数据加密,防止信息截取,RSA加解密更耗时
AES/CBC/NoPadding
package com.zhangteng.rxhttputils.utils
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* Created by Swing on 2017/12/6.
*/
object AESUtils {
/**
* 随机生成秘钥
*/
val key: String
get() = try {
val kg = KeyGenerator.getInstance("AES")
kg.init(128)
val sk = kg.generateKey()
val b = sk.encoded
byteToHexString(b)
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
""
}
/**
* 使用指定的字符串生成秘钥
*/
fun getKeyByPass(keyRaw: String): String {
return try {
val kg = KeyGenerator.getInstance("AES")
// kg.init(128);//要生成多少位,只需要修改这里即可128, 192或256
//SecureRandom是生成安全随机数序列,password.getBytes()是种子,只要种子相同,序列就一样,所以生成的秘钥就一样。
kg.init(128, SecureRandom(keyRaw.toByteArray()))
val sk = kg.generateKey()
val b = sk.encoded
byteToHexString(b)
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
""
}
}
/**
* byte数组转化为16进制字符串
*
* @param bytes
* @return
*/
fun byteToHexString(bytes: ByteArray): String {
val sb = StringBuffer()
for (i in bytes.indices) {
val strHex = Integer.toHexString(bytes[i].toInt())
if (strHex.length > 3) {
sb.append(strHex.substring(6))
} else {
if (strHex.length < 2) {
sb.append("0$strHex")
} else {
sb.append(strHex)
}
}
}
return sb.toString()
}
//加密
@Throws(Exception::class)
fun encrypt(data: String, key: String, iv: String): String? {
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
val blockSize = cipher.blockSize
val dataBytes = data.toByteArray()
var plaintextLength = dataBytes.size
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - plaintextLength % blockSize)
}
val plaintext = ByteArray(plaintextLength)
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.size)
val keyspec =
SecretKeySpec(key.toByteArray(), "AES")
val ivspec =
IvParameterSpec(iv.toByteArray())
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec)
val encrypted = cipher.doFinal(plaintext)
return Base64Utils.encode(encrypted)
}
//解密
@Throws(Exception::class)
fun decrypt(data: String, key: String, iv: String): String {
val encrypted1 = Base64Utils.decode(data)
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
val keyspec =
SecretKeySpec(key.toByteArray(), "AES")
val ivspec =
IvParameterSpec(iv.toByteArray())
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec)
val original = cipher.doFinal(encrypted1)
return String(original)
}
}
AES秘钥交换困难
RSA/ECB/PKCS1Padding
package com.zhangteng.rxhttputils.utils
import android.util.Base64
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.NoSuchAlgorithmException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.*
import javax.crypto.Cipher
/**
* 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式
* 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,
* 非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全
*/
object RSAUtils {
/**
* 非对称加密密钥算法
*/
const val RSA = "RSA"
/**
* 加密填充方式
*/
const val ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding"
/**
* 秘钥默认长度
*/
const val DEFAULT_KEY_SIZE = 1024
/**
* 当要加密的内容超过bufferSize,则采用partSplit进行分块加密
*/
val DEFAULT_SPLIT = "#PART#".toByteArray()
/**
* 当前秘钥支持加密的最大字节数
*/
const val DEFAULT_BUFFERSIZE = DEFAULT_KEY_SIZE / 8 - 11
/**
* 随机生成RSA密钥对
*
* @param keyLength 密钥长度,范围:512~2048
* 一般1024
* @return
*/
fun generateRSAKeyPair(keyLength: Int): KeyPair? {
return try {
val kpg =
KeyPairGenerator.getInstance(RSA)
kpg.initialize(keyLength)
kpg.genKeyPair()
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
null
}
}
/**
*
*
* 公钥加密
*
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
@Throws(Exception::class)
fun encryptByPublicKey(data: String, publicKey: String?): String {
return String(
Base64.encode(
encryptByPublicKey(
data.toByteArray(),
Base64.decode(publicKey, 0)
), 0
)
).replace("\n", "").replace("\\", "")
}
/**
* 用公钥对字符串进行加密
*
* @param data 原文
*/
@Throws(Exception::class)
fun encryptByPublicKey(
data: ByteArray?,
publicKey: ByteArray?
): ByteArray {
// 得到公钥
val keySpec =
X509EncodedKeySpec(publicKey)
val kf = KeyFactory.getInstance(RSA)
val keyPublic = kf.generatePublic(keySpec)
// 加密数据
val cp = Cipher.getInstance(ECB_PKCS1_PADDING)
cp.init(Cipher.ENCRYPT_MODE, keyPublic)
return cp.doFinal(data)
}
/**
*
*
* 私钥加密
*
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
@Throws(Exception::class)
fun encryptByPrivateKey(data: String, publicKey: String?): String {
return String(
Base64.encode(
encryptByPrivateKey(
data.toByteArray(),
Base64.decode(publicKey, 0)
), 0
)
).replace("\n", "").replace("\\", "")
}
/**
* 私钥加密
*
* @param data 待加密数据
* @param privateKey 密钥
* @return byte[] 加密数据
*/
@Throws(Exception::class)
fun encryptByPrivateKey(
data: ByteArray?,
privateKey: ByteArray?
): ByteArray {
// 得到私钥
val keySpec =
PKCS8EncodedKeySpec(privateKey)
val kf = KeyFactory.getInstance(RSA)
val keyPrivate = kf.generatePrivate(keySpec)
// 数据加密
val cipher =
Cipher.getInstance(ECB_PKCS1_PADDING)
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate)
return cipher.doFinal(data)
}
/**
*
*
* 公钥解密
*
*
* @param encryptedData 已加密数据(BASE64编码)
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
@Throws(Exception::class)
fun decryptByPublicKey(
encryptedData: String?,
publicKey: String?
): String {
return String(
decryptByPublicKey(
Base64.decode(encryptedData, 0),
Base64.decode(publicKey, 0)
)
)
}
/**
* 公钥解密
*
* @param data 待解密数据
* @param publicKey 密钥
* @return byte[] 解密数据
*/
@Throws(Exception::class)
fun decryptByPublicKey(
data: ByteArray?,
publicKey: ByteArray?
): ByteArray {
// 得到公钥
val keySpec =
X509EncodedKeySpec(publicKey)
val kf = KeyFactory.getInstance(RSA)
val keyPublic = kf.generatePublic(keySpec)
// 数据解密
val cipher =
Cipher.getInstance(ECB_PKCS1_PADDING)
cipher.init(Cipher.DECRYPT_MODE, keyPublic)
return cipher.doFinal(data)
}
/**
*
* 私钥解密
*
*
* @param encryptedData 已加密数据(BASE64编码)
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
@Throws(Exception::class)
fun decryptByPrivateKey(
encryptedData: String?,
privateKey: String?
): String {
return String(
decryptByPrivateKey(
Base64.decode(encryptedData, 0),
Base64.decode(privateKey, 0)
)
)
}
/**
* 使用私钥进行解密
*/
@Throws(Exception::class)
fun decryptByPrivateKey(
encrypted: ByteArray?,
privateKey: ByteArray?
): ByteArray {
// 得到私钥
val keySpec =
PKCS8EncodedKeySpec(privateKey)
val kf = KeyFactory.getInstance(RSA)
val keyPrivate = kf.generatePrivate(keySpec)
// 解密数据
val cp = Cipher.getInstance(ECB_PKCS1_PADDING)
cp.init(Cipher.DECRYPT_MODE, keyPrivate)
return cp.doFinal(encrypted)
}
}
package com.zhangteng.rxhttputils.interceptor
import android.text.TextUtils
import com.google.gson.JsonParser
import com.zhangteng.rxhttputils.http.HttpUtils
import com.zhangteng.rxhttputils.http.OkHttpClient
import com.zhangteng.rxhttputils.utils.AESUtils
import com.zhangteng.rxhttputils.utils.DiskLruCacheUtils
import com.zhangteng.rxhttputils.utils.RSAUtils
import com.zhangteng.rxhttputils.utils.SPUtils
import okhttp3.*
import okio.Buffer
import java.io.IOException
import java.nio.charset.Charset
/**
* 添加加解密拦截器
* Created by Swing on 2019/10/20.
*/
class EncryptionInterceptor(private val publicKeyUrl: HttpUrl) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val request = chain.request()
val headers = request.headers()
if (headers.names().contains(SECRET) && "true" == headers[SECRET]) {
var secretRequest: Request? = buildRequest(request) ?: return errorSecretResponse
var secretResponse = chain.proceed(secretRequest!!)
val secretResponseBody = secretResponse.body()
val secretResponseStr =
if (secretResponseBody != null) secretResponseBody.string() else ""
val jsonObject = JsonParser().parse(
secretResponseStr.substring(
0,
secretResponseStr.lastIndexOf("}") + 1
)
).asJsonObject
val jsonElement = jsonObject["status"]
if (jsonElement != null
&& !jsonElement.isJsonArray
&& !jsonElement.isJsonObject
&& !jsonElement.isJsonNull
&& "2100" == jsonElement.asString
) {
SPUtils.put(HttpUtils.getInstance().getContext()!!, SPUtils.FILE_NAME, SECRET, "")
DiskLruCacheUtils.remove(publicKeyUrl)
DiskLruCacheUtils.flush()
secretRequest = buildRequest(request)
if (secretRequest == null) {
return errorSecretResponse
}
secretResponse = chain.proceed(secretRequest)
} else {
val mediaType =
if (secretResponseBody != null) secretResponseBody.contentType() else MediaType.parse(
"application/json;charset=UTF-8"
)
val newResonseBody = ResponseBody.create(mediaType, secretResponseStr)
secretResponse = secretResponse.newBuilder().body(newResonseBody).build()
}
return secretResponse
}
return chain.proceed(request)
}
/**
* 构建加密请求
*
* @param request 原请求
*/
@Throws(IOException::class)
private fun buildRequest(request: Request): Request? {
if (TextUtils.isEmpty(
SPUtils[HttpUtils.getInstance()
.getContext()!!, SPUtils.FILE_NAME, SECRET, ""].toString()
)
) {
val secretResponse = OkHttpClient.getInstance().client.newCall(
Request.Builder().url(publicKeyUrl).build()
).execute()
val secretResponseString = secretResponse.body()?.string()
if (secretResponse.code() == 200) {
val jsonObject = JsonParser().parse(secretResponseString).asJsonObject
val jsonElement = jsonObject["result"].asJsonObject["publicKey"]
SPUtils.put(
HttpUtils.getInstance().getContext()!!,
SPUtils.FILE_NAME,
SECRET,
jsonElement.asString
)
} else {
return null
}
}
val aesRequestKey: String = AESUtils.key
val requestBuilder = request.newBuilder()
requestBuilder.removeHeader(SECRET)
try {
requestBuilder.addHeader(
SECRET,
RSAUtils.encryptByPublicKey(
aesRequestKey,
SPUtils[HttpUtils.getInstance()
.getContext()!!, SPUtils.FILE_NAME, SECRET, publicKey] as String
)
)
} catch (e: Exception) {
return null
}
if (METHOD_GET == request.method()) {
val url = request.url().url().toString()
val paramsBuilder = url.substring(url.indexOf("?") + 1)
try {
val encryptParams =
AESUtils.encrypt(paramsBuilder, aesRequestKey, aesRequestKey.substring(0, 16))
requestBuilder.url(url.substring(0, url.indexOf("?")) + "?" + encryptParams)
} catch (e: Exception) {
return null
}
} else if (METHOD_POST == request.method()) {
val requestBody = request.body()
if (requestBody != null && aesRequestKey.length >= 16) {
if (requestBody is FormBody) {
val formBody = request.body() as FormBody?
val bodyBuilder = FormBody.Builder()
try {
if (formBody != null) {
for (i in 0 until formBody.size()) {
val value = formBody.encodedValue(i)
if (!TextUtils.isEmpty(value)) {
val encryptParams = AESUtils.encrypt(
value,
aesRequestKey,
aesRequestKey.substring(0, 16)
)
bodyBuilder.addEncoded(formBody.encodedName(i), encryptParams)
}
}
requestBuilder.post(bodyBuilder.build())
}
} catch (e: Exception) {
return null
}
} else {
val buffer = Buffer()
requestBody.writeTo(buffer)
var charset =
Charset.forName("UTF-8")
val contentType = requestBody.contentType()
if (contentType != null) {
charset = contentType.charset()
}
val paramsRaw =
buffer.readString(charset ?: Charset.defaultCharset())
if (!TextUtils.isEmpty(paramsRaw)) {
try {
val encryptParams = AESUtils.encrypt(
paramsRaw,
aesRequestKey,
aesRequestKey.substring(0, 16)
)
requestBuilder.post(
RequestBody.create(
requestBody.contentType(),
encryptParams
)
)
} catch (e: Exception) {
return null
}
}
}
}
}
return requestBuilder.build()
}
/**
* 获取加密失败响应
*/
private val errorSecretResponse: okhttp3.Response
private get() {
val failureResponseBuilder = okhttp3.Response.Builder()
failureResponseBuilder.body(
ResponseBody.create(
MediaType.parse("application/json;charset=UTF-8"),
"{\"message\": \"移动端加密失败\",\"status\": ${SECRET_ERROR}}"
)
)
return failureResponseBuilder.build()
}
companion object {
private const val METHOD_GET: String = "GET"
private const val METHOD_POST: String = "POST"
const val SECRET: String = "_secret"
const val SECRET_ERROR: Int = 2100
const val publicKey: String = ""
}
}
package com.zhangteng.rxhttputils.interceptor
import android.text.TextUtils
import com.zhangteng.rxhttputils.http.HttpUtils
import com.zhangteng.rxhttputils.interceptor.EncryptionInterceptor.Companion.SECRET
import com.zhangteng.rxhttputils.interceptor.EncryptionInterceptor.Companion.SECRET_ERROR
import com.zhangteng.rxhttputils.utils.AESUtils.decrypt
import com.zhangteng.rxhttputils.utils.RSAUtils
import com.zhangteng.rxhttputils.utils.SPUtils
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Response
import okhttp3.ResponseBody
import java.io.IOException
/**
* 添加加解密拦截器
* Created by Swing on 2019/10/20.
*/
class DecryptionInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (!response.isSuccessful || response.code() != 200) {
return response
}
val responseBuilder = response.newBuilder()
val responseBody = response.body()
val responseHeaders = response.headers()
for (name in responseHeaders.names()) {
if (SECRET.contains(name!!) && !TextUtils.isEmpty(responseHeaders[name])) {
return try {
val encryptKey = responseHeaders[name]
val aesResponseKey: String = RSAUtils.decryptByPublicKey(
encryptKey,
SPUtils[HttpUtils.getInstance()
.getContext()!!, SPUtils.FILE_NAME, SECRET, EncryptionInterceptor.publicKey] as String
)
val mediaType =
if (responseBody != null) responseBody.contentType() else MediaType.parse(
"application/json;charset=UTF-8"
)
val responseStr =
if (responseBody != null) responseBody.string() else ""
val rawResponseStr = decrypt(
responseStr,
aesResponseKey,
aesResponseKey.substring(0, 16)
)
responseBuilder.body(ResponseBody.create(mediaType, rawResponseStr))
responseBuilder.build()
} catch (e: Exception) {
val failureResponse = Response.Builder()
failureResponse.body(
ResponseBody.create(
MediaType.parse("application/json;charset=UTF-8"),
"{\"message\": \"移动端解密失败${e.message}\",\"status\": ${SECRET_ERROR}}"
)
)
failureResponse.build()
}
}
}
return response
}
}