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

一、加密方案

比较安全的方案应该是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();
            }
            response.close();
            //返回
            return response;
        }
    }

-----------------------------------------------------------------------------------------------------------------------------------------------
----->然后OkHttp加入该拦截器:
-----------------------------------------------------------------------------------------------------------------------------------------------

new OkHttpClient.Builder()
                .addInterceptor(new DataEncryptInterceptor())
                ....
                .build();

-----------------------------------------------------------------------------------------------------------------------------------------------
----->这样就搞定了。
-----------------------------------------------------------------------------------------------------------------------------------------------

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • 本文主要介绍移动端的加解密算法的分类、其优缺点特性及应用,帮助读者由浅入深地了解和选择加解密算法。文中会包含算法的...
    苹果粉阅读 11,425评论 5 29
  • 随着对于安全度的不断要求,对于数据加解密与破解之间的斗争,加解密的方式也在不断发生着变化,来看看现在流行的一些加解...
    zhouhao_180阅读 2,048评论 1 12
  • 最近公司用到RSA数据加密传输,本人也只会使用,并不知其原理,刚好今天在csdn看到一位大牛的博客写得很到位,遂搬...
    爸比好酷阅读 1,412评论 0 1
  • 马上暑假啦是否还在愁青铜白银的小学生坑到你呢?[em]e400837[/em] 这里在线接LOL代练。安全效率。价...
    没有故事的淡忘阅读 246评论 1 1
  • 君君喜欢吃重庆辣子鸡,今天说起要做这个菜,朵朵说她是高手。嗯哼,跟高手学习学习。认真听清楚朵朵指点辣子鸡,再...
    七七行记阅读 256评论 0 0