如何使用 Retrofit 2 + OkHttp 3 加密/隐藏 HTTPS 调用的主体?

Posted

技术标签:

【中文标题】如何使用 Retrofit 2 + OkHttp 3 加密/隐藏 HTTPS 调用的主体?【英文标题】:How do you encrypt / hide the body of an HTTPS call using Retrofit 2 + OkHttp 3? 【发布时间】:2017-02-26 19:52:08 【问题描述】:

我目前正在做一个项目,我通过 https 调用向我们的服务器 api 发送数据。项目的基本 URL 支持 ssl(我们的 url api 端点以 https://api..... 开头)。我正在使用 Retrofit 2 和 OkHttp3 并像这样设置客户端:

    public static void buildClient()
        //Misc code here.... not showing for security reasons.
        OkHttpClient client = RetrofitClient.configureClient(new OkHttpClient());
        //I make calls here to update interceptors, timeouts, etc.
        Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .client(client)
            .build();
    

    //Setup the ssl stuff here
    public static OkHttpClient configureClient(final OkHttpClient client) 
        final TrustManager[] certs = new TrustManager[]new X509TrustManager() 
            @Override
            public X509Certificate[] getAcceptedIssuers() 
                return null;
            
            @Override
            public void checkServerTrusted(final X509Certificate[] chain,
                                           final String authType)
                    throws CertificateException 
            

            @Override
            public void checkClientTrusted(final X509Certificate[] chain,
                                           final String authType)
                    throws CertificateException 
            
        ;

        SSLContext ssl = null;
        try 
            ssl = SSLContext.getInstance("TLS");
            ssl.init(null, certs, new SecureRandom());
         catch (final java.security.GeneralSecurityException ex) 
        

        try 
            final HostnameVerifier hostnameVerifier = new HostnameVerifier() 
                @Override
                public boolean verify(final String hostname,
                                      final SSLSession session) 
                    return true;
                
            ;
            client.setHostnameVerifier(hostnameVerifier);
            client.setSslSocketFactory(ssl.getSocketFactory());
         catch (final Exception e) 
        

        return client;
    

所以在这之后,我们都准备好了。

现在,这是我所知道的:

1) 我通过 HTTPS 发送,因为如果我不是,服务器会抛出一个错误,但事实并非如此。

2) 我的代码运行良好,因为它正在与服务器通信并且应用程序可以运行。

这里的问题是实际的 Body 数据没有被加密。这里有 2 张照片示例来说明我的意思。

1)

2)

第一张图片显示了对实际正文数据的适当混淆,因为数据正在被转换为加密的“东西”,而第二张图片显示的是纯文本。第二个是我用一个对象向服务器发送一个 POST 调用。

我的问题是,我该如何复制它,以便我的正文像其他文本一样被隐藏/加密?

注意事项:

1) 我正在通过 Proguard 使用混淆

2) 我将 minifyEnabled 设置为 true

3) 我是通过数据包嗅探器发现的

任何人有任何想法如何做到这一点?或者任何人都可以为我指出正确的方向,具体称为什么?

谢谢。

编辑:

所以,我似乎没有理解这里的关键组件。

简短的回答是,呼叫已经加密并且正在发送 Https

长答案是,我一直在将我的数据调用与以下数据进行比较:

1)

2)

我只是假设 这些 是加密的,而我的不是。事实证明,我发送的呼叫被加密得很好,就像这些一样,但是这些数据被压缩/压缩,这使得它不可读,这让我认为这是加密数据的样子数据包嗅探器。

【问题讨论】:

如果数据实际上是通过 HTTPS 发送的,那么除了 URL 的地址部分之外,在线上的所有内容都会被加密。客户端和服务器都不会看到加密数据。除非您已将嗅探器设置为 MITM 代理,否则您也不会在嗅探器中看到未加密的内容。如果您在传输中看到未加密的数据,则说明未使用 HTTPS。 【参考方案1】:

您的问题是:为什么我使用 HTTPS,但 Packet CaptureCharles 可以查看客户端和 Internet 之间的所有 SSL/HTTPS 流量?

因为 Packet Capture(*** 代理)或 Charles 作为中间人欺骗了您的客户:

您的客户端 <--> 数据包捕获/Charles <--> 您的目标服务器。

所以代理工具可以查看你所有的 HTTPS 内容(事实上它们确实是加密的)。

解决方案

您可以参考 OkHttp wiki:https://github.com/square/okhttp/wiki/HTTPS

并为您的 HTTPS 检查设置 证书。例如:

public class MainActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView textView = (TextView) findViewById(R.id.text);

        ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
            .tlsVersions(TlsVersion.TLS_1_2)
            .cipherSuites(
                CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
            .build();

        OkHttpClient client = new OkHttpClient.Builder()
            .connectionSpecs(Collections.singletonList(spec))
            .certificatePinner(new CertificatePinner.Builder()
                .add("drakeet.me", "sha256/gGOcYKAwzEaUfun6YdxZvFSQq/x2lF/R8UizDFofveY=")
                .build())
            .build();

        Request request = new Request.Builder()
            .url("https://drakeet.me?s=type")
            .post(RequestBody.create(MediaType.parse("text"), "xxx...xxx"))
            .addHeader("token", "xxx")
            .build();

        final Handler handler = new Handler();
        client.newCall(request).enqueue(new Callback() 
            @Override public void onFailure(Call call, IOException e) 
                e.printStackTrace();
            


            @Override public void onResponse(Call call, final Response response)
                throws IOException 
                final String t = response.body().string();
                handler.post(new Runnable() 
                    @Override public void run() 
                        textView.setText(t);
                    
                );
            
        );
    

正如我上面的代码,我预设了一个certificatePinner与我的目标服务器的真实certificatePinner相关,这样如果我现在使用Packet Capture/Charles,它们会自己创建一个虚假的certificatePinner,OkHttp会比较两者pinning,如果不相等,抛出javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!

如果关闭 Packet Capture/Charles,异常会关闭并成功发送 HTTPS 内容。

【讨论】:

哦,哇,我不知道甚至有解决问题的方法。我只是假设这是 android 操作系统的一个漏洞。最高分@drakeet!我将在今天晚些时候实施并尝试。【参考方案2】:

您可能会看到,仍然使用“http”发出请求。使用不同的Retrofit.Builder方法baseUrl(HttpUrl):

HttpUrl httpUrl = new HttpUrl.Builder()
            .host(BASE_URL)
            .scheme("https").build();

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(httpUrl)
            .build();

【讨论】:

感谢您的回复,刚试了一下,收到了发送的电话,但它仍然显示未加密的数据。 (名称、用户 ID 等)。出站是 https 虽然它正在发送到api.OUR_API.com 好的!所以看起来呼叫已经安全地进行了,但是,我必须给你信任,因为我不知道你可以通过构建器更改前缀方案。谢谢,从现在开始我会用这个。

以上是关于如何使用 Retrofit 2 + OkHttp 3 加密/隐藏 HTTPS 调用的主体?的主要内容,如果未能解决你的问题,请参考以下文章

HTTP/2 与 OkHttp3 和 Retrofit2

OkHttp,Retrofit 1.x - 2.x 基本使用

如何指定 Get-Request 编码(Retrofit + OkHttp)

retrofit和okhttp请求url的参数拼接

如何使用 Retrofit 和 OKHttp 在下一次请求时使缓存路由无效/强制更新?

Retrofit+OkHttp中如何正确的使用https?