美文网首页Android开发Android技术知识Android开发经验谈
Okhttp/Retrofit网络请求加解密实现方案

Okhttp/Retrofit网络请求加解密实现方案

作者: BrightVan | 来源:发表于2018-06-15 11:39 被阅读38次

    一、加密方案

    比较安全的方案应该是AES+RSA的加密方式。具体如下图(此图源于网络)所示。


    AES+RSA流程

    为什么要这样做呢?
    1、RSA是非对称加密,公钥和私钥分开,且公钥可以公开,很适合网络数据传输场景。但RSA加密比较慢,据说比AES慢100倍,且对加密的数据长度也有限制。
    2、AES是对称加密,加密速度快,安全性高,但密钥的保存是个问题,在网络数据传输的场景就很容易由于密钥泄露造成安全隐患
    3、所以,AES+RSA结合才更好,AES加密数据,且密钥随机生成,RSA用对方(服务器)的公钥加密随机生成的AES密钥。传输时要把密文,加密的AES密钥和自己的公钥传给对方(服务器)。对方(服务器)接到数据后,用自己的私钥解密AES密钥,再拿AES密钥解密数据得到明文。这样就综合了两种加密体系的优点。
    4、除上面说的外,还可以加签名,即对传输的数据(加密前)先做个哈希,然后用自己的RSA私钥对哈希签名(对方拿到自己的公钥可以验签),这样可以验证传输内容有没有被修改过。

    二、加密相关的一些坑

    1、数据类型

    就java来说,加密的输入和输出都是字节数组类型的,也就是二进制数据,网络传输或本地保存都需要重新编码为字符串。推荐使用Base64。Android 有自带的Base64实现,flag要选Base64.NO_WRAP,不然末尾会有换行影响服务端解码。
    Android中Base64加密

    //字节数组转字符串
    String str = Base64.encodeToString(byte_data, Base64.NO_WRAP);
    //字符串转字节数组
    byte[] bytes = Base64.decode(keyStr, Base64.NO_WRAP);
    

    2、加密参数

    总而言之,这些不同语言都有实现库,调用即可,关键是参数要一致,具体还需要和后台联调一下。
    rsa加解密的内容超长的问题解决

    AES算法:
    Android端--->"AES/CFB/NOPADDING"
    密钥长度一般128,256安全性更高
    ECB模式不安全,使用会有黄色警告。
    
    RSA算法:
    密钥长度=1024已经被认为不安全了(RSA 768已于2009年被破解),推荐>=2048。加密的明文长度和密钥长度是相关的。
    Android端-->"RSA/ECB/PKCS1Padding"
    
    RSA签名算法:
    Android端-->"SHA1withRSA"
    试过MD5withRSA,但是和后台无法兼容
    

    三、OkHttp/Retrofit的实现

    现在说到网络框架,应该毫无疑问是Retrofit了。上面说的加密方案说到底还是要在网络请求框架内加上,怎么做入侵最小,怎么做最方便才是重点。
    1、坑定不能直接在接口调用层做加密,加参数,这样每个接口都要修改,这是不可能的。
    2、ConverterFactory处理,这也是网上可以搜到的很多文章的写法,但我觉得还是有入侵。而且有点麻烦。
    3、OkHttp添加拦截器,这种方法入侵最小(可以说没有),实现呢也非常优雅。
    下面的实现,网上也找不到多少可以参考的文章,但不得不说,OkHttp的封装和设计真的很好用,所见即所得。看下源码,就知道该怎么用了,连文档都不用查。

    -----------------------------------------------------------------------------------------------------------------------------------------------
    ----->先定义一个拦截器的实现:
    -----------------------------------------------------------------------------------------------------------------------------------------------
    
    public class DataEncryptInterceptor implements Interceptor {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //请求
                Request request = chain.request();
                RequestBody oldRequestBody = request.body();
                Buffer requestBuffer = new Buffer();
                oldRequestBody.writeTo(requestBuffer);
                String oldBodyStr = requestBuffer.readUtf8();
                requestBuffer.close();
                MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
                //生成随机AES密钥并用serverPublicKey进行RSA加密
                SecretKeySpec appAESKeySpec = EncryptUtils.generateAESKey(256);
                String appAESKeyStr = EncryptUtils.covertAESKey2String(appAESKeySpec);
                String appEncryptedKey = RSAUtils.encryptDataString(appAESKeyStr, serverPublicKey);
                //计算body 哈希 并使用app私钥RSA签名
                String appSignature = RSAUtils.signature(oldBodyStr, appPrivateKey);
                //随机AES密钥加密oldBodyStr
                String newBodyStr = EncryptUtils.encryptAES(appAESKeySpec, oldBodyStr);
                RequestBody newBody = RequestBody.create(mediaType, newBodyStr);
                //构造新的request
                request = request.newBuilder()
                        .header("Content-Type", newBody.contentType().toString())
                        .header("Content-Length", String.valueOf(newBody.contentLength()))
                        .method(request.method(), newBody)
                        .header("appEncryptedKey", appEncryptedKey)
                        .header("appSignature", appSignature)
                        .header("appPublicKey", appPublicKeyStr)
                        .build();
                //响应
                Response response = chain.proceed(request);
                if (response.code() == 200) {//只有约定的返回码才经过加密,才需要走解密的逻辑
                    //获取响应头
                    String serverEncryptedKey = response.header("serverEncryptedKey");
                    //用app的RSA私钥解密AES加密密钥
                    String serverDecryptedKey = RSAUtils.decryptDataString(serverEncryptedKey, appPrivateKey);
                    SecretKeySpec serverAESKeySpec = EncryptUtils.covertString2AESKey(serverDecryptedKey);
                    //用AES密钥解密oldResponseBodyStr
                    ResponseBody oldResponseBody = response.body();
                    String oldResponseBodyStr = oldResponseBody.string();
                    String newResponseBodyStr = EncryptUtils.decryptAES(serverAESKeySpec, oldResponseBodyStr);
                    oldResponseBody.close();
                    //构造新的response
                    ResponseBody newResponseBody = ResponseBody.create(mediaType, newResponseBodyStr);
                    response = response.newBuilder().body(newResponseBody).build();
                    newResponseBody.close();
                }
                //返回
                return response;
            }
        }
    
    -----------------------------------------------------------------------------------------------------------------------------------------------
    ----->然后OkHttp加入该拦截器:
    -----------------------------------------------------------------------------------------------------------------------------------------------
    
    new OkHttpClient.Builder()
                    .addInterceptor(new DataEncryptInterceptor())
                    ....
                    .build();
    
    -----------------------------------------------------------------------------------------------------------------------------------------------
    ----->这样就搞定了。
    -----------------------------------------------------------------------------------------------------------------------------------------------
    
    

    主要注意点:
    0、和接口无关的新加的数据放在请求头里。
    1、该close的要close,不然会内存泄漏。
    2、新旧Request和Response要区分好,新的要替换旧的去传递或返回。
    3、要对response.code()做处理,只有在和后台约定好的返回码下才走解密的逻辑,具体看自己的需求,不一定都是200。

    相关文章

      网友评论

        本文标题:Okhttp/Retrofit网络请求加解密实现方案

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