美文网首页
Retrofit请求加密,响应解密并生成类对象(自定义Conve

Retrofit请求加密,响应解密并生成类对象(自定义Conve

作者: 一叶枫飘 | 来源:发表于2017-09-07 19:32 被阅读0次

    1.自定义JsonConverterFactory类

    public class JsonConverterFactory extends Converter.Factory {
        private static final String TAG = "JsonConverterFactory";
        private final Gson gson;
    
        public static JsonConverterFactory create() {
            return create(new Gson());
        }
    
        public static JsonConverterFactory create(Gson gson) {
            return new JsonConverterFactory(gson);
    
        }
    
        private JsonConverterFactory(Gson gson) {
            if (gson == null) throw new NullPointerException("gson == null");
            this.gson = gson;
        }
    
    
        @Nullable
        @Override
        public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new JsonRequestBodyConverter<>(gson, adapter); //请求
        }
    
        @Nullable
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new JsonResponseBodyConverter<>(gson, adapter); //响应
        }
    
        /**
         * JsonRequestBodyConverter<T>
         * @param <T>
         */
        public static class JsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
            private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
            private final Gson gson;
            private final TypeAdapter<T> adapter;
    
            /**
             * 构造器
             */
            public JsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
                this.gson = gson;
                this.adapter = adapter;
            }
    
            @Override
            public RequestBody convert(T value) throws IOException {
    
                //这里需要,特别注意的是,request是将T转换成json数据。
                //你要在T转换成json之后再做加密。
                //再将数据post给服务器,同时要注意,你的T到底指的那个对象
    
                //加密操作,返回字节数组
                byte[] encrypt = AESUtils.encrypt(value.toString());
    
                Log.i("xiaozhang", "request中传递的json数据:" + value.toString()); //打印:加密前的json字符串
                Log.i("xiaozhang", "加密后的字节数组:" + encrypt.toString());//打印:字节数组
    
                //传入字节数组,创建RequestBody 对象
                return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),encrypt);
            }
        }
    
        /**
         * JsonResponseBodyConverter<T>
         * @param <T>
         */
        public class JsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
            private final Gson mGson;//gson对象
            private final TypeAdapter<T> adapter;
    
            /**
             * 构造器
             */
            public JsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
                this.mGson = gson;
                this.adapter = adapter;
            }
    
            /**
             * 转换
             *
             * @param responseBody
             * @return
             * @throws IOException
             */
            @Override
            public T convert(ResponseBody responseBody) throws IOException {
    
                byte[] bytes = responseBody.bytes();
    
                //对字节数组进行解密操作
                String decryptString = AESUtils.decrypt(bytes);
    
                //对解密的字符串进行处理
                int position = decryptString.lastIndexOf("}");
                String jsonString = decryptString.substring(0,position+1);
    
                Log.i(TAG, "需要解密的服务器数据字节数组:" + bytes.toString());
                Log.i(TAG, "解密后的服务器数据字符串:" + decryptString);
                Log.i(TAG, "解密后的服务器数据字符串处理为json:" + jsonString);
    
                //这部分代码参考GsonConverterFactory中GsonResponseBodyConverter<T>的源码对json的处理
                Reader reader = StringToReader(jsonString);
                JsonReader jsonReader = gson.newJsonReader(reader);
                try {
                    return adapter.read(jsonReader);
                } finally {
                    reader.close();
                    jsonReader.close();
                }
            }
    
            /**
             * String转Reader
             * @param json
             * @return
             */
            private Reader StringToReader(String json){
                Reader reader  = new StringReader(json);
                return reader;
            }
        }
    }
    
    

    注:为了可以像GsonConverterFactory将Json字符串转化为Java类对象,我参看了GsonConverterFactory的源码,在处理完解密操作之后得到了Json字符串,然后我加上GsonConverterFactory的源码部分来处理Json字符串。这样其实就是在GsonConverterFactory的基础上添加了加密和解密操作。

    参考

    1. Retrofit 2 之自定义Converter实现加密解密

    其他参考
    retrofit 自定义请求参数加密 和自定义响应解密 带你走出那些坑
    Retrofit 进阶篇 自定义转换器
    Retrofit:打造自己的Converter之byte[]

    2.AES/CBC/NoPadding加密解密

    找到了一篇关于PHP和Java的AES互通兼容加密文章,看完之后发现了原来PHP的AES加密填充只有ZeroPadding(补零 - 因为数据长度不是16的整数倍就需要填充),而Java是没有这种填充模式,杯具的只能自己写一个了,那Java的填充模式就用NoPadding(不填充内容);

    public class AESUtils {
    
        //初始向量(偏移)
        public static final String iv= "7983B5439EF75A69";   //AES 为16bytes. DES 为8bytes
    
        //编码方式
        //public static final String bm = "utf-8";
    
        //私钥  (密钥)
        private static final String key="7983b5439ef75a69";   //AES固定格式为128/192/256 bits.即:16/24/32bytes。DES固定格式为128bits,即8bytes。
    
        /**
         * 加密
         * @param data 加密前的字符串
         * @return 加密后的字节数组
         */
        public static byte[] encrypt(String data){
            try {
    
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                int blockSize = cipher.getBlockSize();
    
                //判断待加密的字节数组的长度,在此长度基础上扩展一个字节数组的长度为16的倍数
                byte[] dataBytes = data.getBytes();
                int plaintextLength = dataBytes.length;
                if (plaintextLength % blockSize != 0) {
                    plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
                }
    
                //创建需新的待加密的字节数组,将上面的字节数组复制进来,多余的补0
                byte[] plaintext = new byte[plaintextLength];
                System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
    
                SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
    
                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
    
                //加密后的字节数组
                byte[] encrypted = cipher.doFinal(plaintext);
    
                return encrypted;
    
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 解密
         * @param encrypted1 解密前的字节数组
         * @return 解密后的字符串
         */
        public static String decrypt(byte[] encrypted1) {
            try
            {
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
    
                cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
                
                //解密后的字节数组
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original);
                return originalString;
            }
            catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    

    可以看到aes加密的中间结果是byte[]类型,直接new String(byte[])会看不到有意义的中间结果,可以在这里用的是base64,是因为各个语言都有这样的支持。在同个语言内,也有bytesToHexString这样的方式。

    跨语言加解密的要求是:AES/CBC/ZeroPadding 128位模式,key和iv一样,编码统一用utf-8。不支持ZeroPadding的就用NoPadding.

    参考

    1. AES加密CBC模式兼容互通四种编程语言平台【PHP、Javascript、Java、C#】(看Java的AES加密解密)

    2.C#, Java, PHP, Python和Javascript几种语言的AES加密解密实现【多种语言AES/CBC/PKCS5Padding通用加解密数据】(看最后的总结)

    3.AES对称加密算法扫盲(看AES加密的方式)

    4.在线AES等加密解密验证工具(验证加解密)

    其他参考
    java加密算法之AES小记
    JAVA实现AES加密
    java使用Hex编码解码实现Aes加密解密功能示例
    Java加密算法 AES

    3.Retrofit+RxJava

    (1)定义HttpService接口

    public interface HttpService {
     /**
         * 管理员登录
         * @return
         */
        @Headers({"Content-Type: application/json","Accept: application/json"})//需要添加头
        @POST("ictweb.cgi")
        Observable<Result<MasterLoginInfo>> masterLoginEncrypt(@Body String parmasJson);
    }
    

    注:这里的参数是@Body String

    (2)// 获取retrofit的实例

    retrofit = new Retrofit
              .Builder()
              .baseUrl(UrlHelper.BASE_URL)  //自己配置
              .client(client)
              .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
           // .addConverterFactory(GsonConverterFactory.create())
              .addConverterFactory(JsonConverterFactory.create())
              .build();
    

    (3)获取HttpService代理对象

    HttpService httpService = retrofit.create(HttpService.class);
    

    (4)构建请求的json字符串

    String jsonString= "{\n" +
                "\t\"cmd\":\"10\",\n" +
                "\t\"content\":{\n" +
                "\t\t\"LoginName\":\"admin\",\t\t\n" +
                "\t\t\"Password\":\"admin\",\t\t\n" +
                "\"ClientType\":\"1\"\t\t\t\n" +
                "}\n" +
                "}";
    

    (5)请求数据

    httpService.masterLoginEncrypt(jsonString)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Subscriber<Result<MasterLoginInfo>>() {
                        @Override
                        public void onCompleted() {
                            Log.i(TAG, "onCompleted: ");
                        }
    
                        @Override
                        public void onError(Throwable e) {
                            Log.i(TAG, "error: ");
                        }
    
                        @Override
                        public void onNext(Result<MasterLoginInfo> masterLoginInfoResult0) {
                            Log.i(TAG, "onNext: result="+masterLoginInfoResult0.getContent().getResult());
                        }
                    });
    

    综上,使用Rertofit+RxJava进行网络操作,通过自定义继承Converter.Factory类可以实现,在客户端数据请求时进行AES/CBC/NoPadding加密,在服务器数据响应时进行AES/CBC/NoPadding解密。

    相关文章

      网友评论

          本文标题:Retrofit请求加密,响应解密并生成类对象(自定义Conve

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