联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

HTTPS协议是安全传输,为啥还要再加密?

作者:访客发布时间:2024-02-14分类:程序开发学习浏览:307


导读:背景这几天,我们家娃吃奶粉的量嗷嗷的涨起来了.我这颗小鹿乱撞的心,忍不住去面了两家互联网金融公司.因为没有准备,结果你懂的~这两家共同都有一个共同点,特别关系安全问题,尤其是看到你...

背景

这几天,我们家娃吃奶粉的量嗷嗷的涨起来了.我这颗小鹿乱撞的心,忍不住去面了两家互联网金融公司.
因为没有准备,结果你懂的~
这两家共同都有一个共同点,特别关系安全问题,尤其是看到你用过okHtp3,那不得给你撸脱毛了就不算完事儿.

协议

HTTP VS HTTPS

我们都知道,HTTPS相比于之前的http多了一层,如下:

HTTPS协议是安全传输,为啥还要再加密?
HTTP是一个基于tcp/IP通信协议来传递数据的协议、tcp/IP通信协议只有四层,从上往下依次为:应用层、传输层、网络层、数据链路层这四层,大学课本上的计算机网络知识是不是来了。但是、Http协议在在网上进行传输用的是明文,就像某鸟给你发的快递,你的手机号、姓名都是写的清清楚楚,用户看着都瑟瑟发抖。
后来京东和顺丰意识到了这一点,就对手机号中间四位做了加密处理,姓名中间那几个个字也看不到了,甚至快递小哥给你打电话都是虚拟号码,你自己的电话只有自己心里清楚.
HTTPS也是这个路子,为了解决http明文传输存在的安全问题,在应用层和传输层之间加了一层安全层:ssl/tls。
安全套接字层,安全套接层
TLS:传输层安全、传输层安全协议
关于HTTPS和HTTPS的对比文章,知乎上有一大坨,这里贴几篇,喜欢的可以多撸几遍:

  1. Www.zhihu.com/Query/19…
  2. 转兰网知乎网/p/158593966
  3. 握手与挥手:转兰网知乎/p/78405711

HTTPS优缺点

  1. 使用HTTPS明文传输存在被窃听风险、HTTPS通过内容加密解决,注意这一步用的是对称加密哦
  2. 使用HTTPS明文传输存在通信内容被篡改风险、HTTPS通过校验机制解决
  3. 使用HTTPS明文传输存在冒充服务风险、HTTPS通过身份证书认证机制解决

通过以上三点,我们知道HTTPS相对于HTTPS有很强的安全感,但是对于一些应用程序这种安全级别还不够,如果出现以下意外咋整?

我干脆去问了讯飞星火,他是这么回答我的:

HTTPS协议是安全传输,为啥还要再加密?

我又去问了ChatGpt,他的回答是这样的

HTTPS协议是安全传输,为啥还要再加密?

这俩货说的差不多、HTTPS只能保证传输过程的安全性,不能保证全链路的安全性,更不能满足特定安全标准的规范诉求,所以我们需要根据安全的级别要求,采取不同加密策略。

既然如此,我们使用的三方网络库这里特指还好吗?是怎么全链路传输安全的?我们接着往下唠:

OK HTTP库

Okhttp库是基于http和HTTPS协议封装的网络请求库,用于安卓和JAVA应用中的网络通信。库在这儿:githeb.com/Square/okht…,

我们可以先了解一下OK Http库是如何保证传输安全的~

问:OK Http是如何支持HTTPS协议,以保证传输安全的,尤其是如何进行防止侦听、篡改、拦截信息?

AI回答:
OkHttp是一个流行的开源http客户端库,由广场公司开发。它提供了对HTTPS和HTTPS的支持,并通过内置的TLS(传输层安全)来确保HTTPS连接的安全性。以下是OkHttp如何支持HTTPS协议以及保障传输安全的一些关键方面:

  1. Tls/ssl支持:

    • OkHttp使用JAVA的javax.net.sslSSLTLS/包来支持协议。Tls是http的安全版本,用于加密通信内容,防止中间人攻击、窃听和篡改。
    • OKHttp通过SSLSocketFactoryHostnameVerifierSSLTLS/等类来处理握手和验证服务器证书的过程。
  2. 证书验证:

    • OkHttp会验证服务器提供的ssl证书,确保通信双方的身份合法。如果服务器的证书无效或无法验证,OkHttp会拒绝连接,从而防止中间人攻击.
  3. 证书锁定(证书绑定):

    • OkHttp支持证书锁定机制,即在应用中预先指定服务端的证书信息。这样可以防止使用伪造证书的攻击,即使系统受到信任的根证书被破坏,应用仍然可以保持安全.
  4. 连接池和持久连接:

    • OkHttp通过连接池(连接池)来重用已建立的连接,从而减少握手次数,提高性能。持久连接也有助于减少建立连接时的潜在风险.
  5. 安全配置:

    • OkHttp允许开发者配置tls版本、密码套件(密码套件)等参数,以满足特定的安全要求.
  6. 拦截器(拦截器):

    • OkHttp提供了拦截器机制,可以在http请求和响应的过程中插入自定义的逻辑。这使得开发者可以在请求或响应阶段添加额外的安全检查或处理逻辑,从而增强安全性.

总的来说,OkHttp通过支持tls/ssl、证书验证、证书锁定、连接池等安全机制,以及提供灵活的配置和拦截器功能,确保在HTTPS连接中防止侦听、篡改和拦截信息的风险。开发者在使用OkHttp时,应该合理配置相关参数,以满足应用的安全需求.

好家伙、OK Http是支持合理配置安全参数的,而且其拦截器机制可以很方便的进行安全检查,那还等什么,走一波~

(策略一:对称加密(加密内容)

这里主打一个,客户端不想让别人看到自己的传输内容,想当然对自己的请求内容进行加密处理.基于这种思路我们封装一个EncryptedOkHttp客户端,代码如下:

public static OkHttpClient createEncryptedOkHttpClient() {
    // 创建一个OkHttpClient.Builder
    OkHttpClient.Builder builder = new OkHttpClient.Builder();

    // 添加自定义拦截器,用于加密请求内容
    builder.addInterceptor(new EncryptionInterceptor());

    // 创建OkHttpClient实例
    return builder.build();
}

注释里已经写了,通过加密拦截器拦截器对请求进行加密处理,这里选择加密请求体请求体HTTPS协议是安全传输,为啥还要再加密?在加密RequestBody方法中,RequestBody依赖okio的Buffer类转换为字节数组用于加密,加密算法选择对称加密算法aes加密字节数据,实现如下:

private RequestBody encryptRequestBody(RequestBody originalRequestBody) throws IOException {
    // 从原始RequestBody中读取字节数据
    // Read the byte data from the original RequestBody using Okio
    Buffer buffer = new Buffer();
    originalRequestBody.writeTo(buffer);
    byte[] bytes = buffer.readByteArray();

    // 使用对称加密算法(AES)加密字节数据
    byte[] encryptedBytes = encryptWithAES(bytes, SECRET_KEY);

    // 创建新的RequestBody
    return RequestBody.create(originalRequestBody.contentType(), encryptedBytes);
}

可以看到,aes使用了加密使用aes方法加密字节数据,同时传了SECRET_Key这个密钥,那我们看看aes是怎么加密的:

private byte[] encryptWithAES(byte[] input, String key) {
    try {
        SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES_ALGORITHM);
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return cipher.doFinal(input);
    } catch (Exception e) {
        e.printStackTrace();
        return new byte[0];
    }
}

四行代码搞定,首先通过秘书密钥规范类将SECRET_KEY字符串加密成秘书密钥对象,然后密码以加密模式对密钥进行初始化然后加密输入也就是转换为字节数组的请求体。 加密完成了,服务器当然要进行解密,解密方法如下:

public static String decrypt(String encryptedText) {
    try {
        byte[] encryptedData = Base64.decode(encryptedText,Base64.DEFAULT);

        SecretKey secretKey = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), AES_ALGORITHM);
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        byte[] decryptedBytes = cipher.doFinal(encryptedData);

        return new String(decryptedBytes, StandardCharsets.UTF_8);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

可以看到,解密过程使用了相同aes算法和密钥密钥,这就是对称加密使用一把钥匙上锁和开锁。但是这种加密算法有很大的问题:

首先,这把钥匙如果想通过网络传输让服务端知道,传输过程中被劫持了密钥就会暴露.

另外,Secure_Key是硬编码在代码中的,这也不安全,这可咋整啊?

千里之堤,溃于黑客

为了防止这种中间人攻击的问题,非对称加密开始表演了~

策略二:非对称加密

非对称加密是一把锁两把钥匙:公钥和私钥.前者是给大家伙用的,谁都能够来配一把公钥进行数据加密,但是要对加密数据进行解密,只能使用私钥.

假设我们用公钥加密一份数据,就不怕拦截了.因为只有拿着私钥的服务端才能解密数据,我们拿着服务器提供的公钥把策略一中的对称密钥给加密了,那不就解决了网络传输密钥的问题了.对的,HTTPS也是这么做的,按照这个思路我们再添加一个混合加密拦截器拦截器。

// 添加自定义拦截器,用服务器非对称加密的公钥加密对称加密的密钥,然后用对称加密密钥加密请求内容
builder.addInterceptor(new MixtureEncryptionInterceptor());

混合加密拦截器拦截器同样实现拦截器接口如下:

HTTPS协议是安全传输,为啥还要再加密?

其拦截方法跟加密拦截器一模一样,具体的变化在加密请求主体()方法中。具体实现如下:

private RequestBody encryptRequestBody(RequestBody originalRequestBody) throws IOException {
    // 生成对称加密的密钥
    byte[] secretKeyBytes = generateSecretKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyBytes, "AES");
    // 使用服务器的公钥加密对称加密的密钥
    byte[] encryptedSecretKey = encryptWithPublicKey(secretKeyBytes, SERVER_PUBLIC_KEY);
    // 从原始 RequestBody 中读取字节数据
    Buffer buffer = new Buffer();
    originalRequestBody.writeTo(buffer);
    byte[] bytes = buffer.readByteArray();

    // 使用对称加密算法(AES)加密请求体
    byte[] encryptedRequestBodyBytes = encryptWithAES(bytes, secretKeySpec);

    // 创建新的 RequestBody,将加密后的密钥和请求体一并传输
    return RequestBody.create(null, concatenateArrays(encryptedSecretKey, encryptedRequestBodyBytes));
}

如代码中注释,整个混合加密共4个步骤,依次是:

  1. 生成对称加密的密钥,用来加密传输内容.代码如下:
/**
 * try block 里使用的是加密算法和随机数生成器,生成的较为复杂的密钥
 * catch block 里使用的是示范性的非安全密钥
 * @return
 */
private byte[] generateSecretKey() {
    // 生成对称加密的密钥
    try {
        // 创建KeyGenerator对象,指定使用AES算法
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

        // 初始化KeyGenerator对象,设置密钥长度为128位
        keyGenerator.init(128, new SecureRandom());

        // 生成密钥
        SecretKey secretKey = keyGenerator.generateKey();

        // 获取密钥的字节数组表示形式
        byte[] keyBytes = secretKey.getEncoded();

        // 打印密钥的字节数组表示形式
        for (byte b : keyBytes) {
            Log.d(TAG,b + " ");
        }
        return keyBytes;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        // 这里简单地示范了生成密钥的过程,实际上可以使用更复杂的方法来生成密钥
        return "YourSecretKey".getBytes(StandardCharsets.UTF_8);
    }

}

如注释所言,上面尝试阻止里使用的是加密算法和随机数生成器,生成的较为复杂的密钥,捕捉阻止里使用的是示范性的非安全密钥。这里主要是想说明生成对称密钥的方式有很多,但是硬编码生成密钥那是不推荐的,因为太不安全了,很容易被恶意用户获取到.

  1. 使用服务器的公钥加密对称加密的密钥,防止被破解
private byte[] encryptWithPublicKey(byte[] input, String publicKeyString) {
    try {
        // 封装 PublicKey
        byte[] keyBytes = Base64.decode(publicKeyString, Base64.DEFAULT);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        return cipher.doFinal(input);
    } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException |
             InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
        e.printStackTrace();
        return new byte[0];
    }
}

将服务端提供的公钥字符串转化成字节数组,然后通过rsa非对称算法加密输入,也就是我们的对称密钥。

注意:cipher.getInstance(“rsa/ecb/pkcs1Pding”)表示获取一个密码对象,该对象使用rsa算法、ecb模式和pkcs1填充方式。

  1. 使用对称加密算法(Aes)加密请求体,请求体仍然要用对称加密密钥加密,只是对称加密密钥用公钥保护起来
private byte[] encryptWithAES(byte[] input, SecretKeySpec secretKeySpec) {
    try {
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        return cipher.doFinal(input);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
             BadPaddingException | IllegalBlockSizeException e) {
        e.printStackTrace();
        return new byte[0];
    }

}

非对称加密加密内容,策略一已经实现了.

  1. 创建新的请求主体、将加密后的密钥和请求体一并传输,这样就算黑客拦截了请求解析出请求体的数据,也无法直接获取到原始对称密钥。 加密完成后,通过串接阵列方法将加密后的密钥和请求体,实现如下:
private byte[] concatenateArrays(byte[] a, byte[] b) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
        outputStream.write(a);
        outputStream.write(b);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return outputStream.toByteArray();
}

非对称加密解决了密钥网络传输的问题,但是黑客还是可以伪装成目标服务端,骗取客户端的密钥。在伪装成客户端,用服务端的公钥加密自己篡改的内容,目标服务端对此无法辨别真伪.这就需要证书校验.

(策略三:证书校验(单向认证)

OkHttp3提供了认证打印机这个类用于证书校验,CertificatePinner可以验证服务器返回的证书是否是预期的证书.在创建创建加密的OkHttpClient()方法中,添加证书代码如下:

HTTPS协议是安全传输,为啥还要再加密?

好的,会利用其内置的证书固定机制来校验服务器返回证书的有效性。如果证书匹配,请求会继续进行;如果不匹配,OkHttp会抛出一个异常,通常是一个SSLPeerUnverifiedException,表明证书验证失败。验证过程在认证针脚类的检查()方法中,如下:

/**
 * Confirms that at least one of the certificates pinned for {@code hostname} is in {@code
 * peerCertificates}. Does nothing if there are no certificates pinned for {@code hostname}.
 * OkHttp calls this after a successful TLS handshake, but before the connection is used.
 *
 * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match the certificates
 * pinned for {@code hostname}.
 */
public void check(String hostname, List<Certificate> peerCertificates)
    throws SSLPeerUnverifiedException {
  List<Pin> pins = findMatchingPins(hostname);
  if (pins.isEmpty()) return;

  if (certificateChainCleaner != null) {
    peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
  }

  for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
    X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);

    // Lazily compute the hashes for each certificate.
    ByteString sha1 = null;
    ByteString sha256 = null;

    for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
      Pin pin = pins.get(p);
      if (pin.hashAlgorithm.equals("sha256/")) {
        if (sha256 == null) sha256 = sha256(x509Certificate);
        if (pin.hash.equals(sha256)) return; // Success!
      } else if (pin.hashAlgorithm.equals("sha1/")) {
        if (sha1 == null) sha1 = sha1(x509Certificate);
        if (pin.hash.equals(sha1)) return; // Success!
      } else {
        throw new AssertionError();
      }
    }
  }

  // If we couldn't find a matching pin, format a nice exception.
  StringBuilder message = new StringBuilder()
      .append("Certificate pinning failure!")
      .append("\n  Peer certificate chain:");
  for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
    X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
    message.append("\n    ").append(pin(x509Certificate))
        .append(": ").append(x509Certificate.getSubjectDN().getName());
  }
  message.append("\n  Pinned certificates for ").append(hostname).append(":");
  for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
    Pin pin = pins.get(p);
    message.append("\n    ").append(pin);
  }
  throw new SSLPeerUnverifiedException(message.toString());
}

从校验方法中得知,

  1. 可以没有固定证书
  2. 证书加密使用sha256/sha 1
  3. 证书校验失败会导致AssertionError错误
  4. 获取不到匹配的固定证书,会妹异常SSLPeerUnverifiedException

可以看到,使用相当方便。But it has a problem:hash value needed before request.也就是说如果证书到期需要更换,老版本的应用程序就无法获取到更新的证书哈希值了,老用户要统一升级this ~

策略四:创建SSLContext认证(客户端,服务端双向认证)

除了固定的证书校验,还有一种基于 SSLContext 的校验方式. In this paper,we study the relationship between the factors of the在这里我们通过一个简单的TwoWayAuthClient()方法实现如下:

private static OkHttpClient createTwoWayAuthClient() throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
    // 服务器证书
    InputStream serverCertStream = TwoWayAuthHttpClient.class.getResourceAsStream("/server_certificate.crt");
    X509Certificate serverCertificate = readCertificate(serverCertStream);
    if (serverCertStream != null) {
        serverCertStream.close();
    }

    // 客户端证书和私钥
    InputStream clientCertStream = TwoWayAuthHttpClient.class.getResourceAsStream("/client_centificate.p12");
    KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
    clientKeyStore.load(clientCertStream, "client_password".toCharArray());
    if (clientCertStream != null) {
        clientCertStream.close();
    }

    // 创建 KeyManagerFactory 和 TrustManagerFactory
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(clientKeyStore, "client_password".toCharArray());

    // 创建信任管理器,信任服务器证书
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(null, null);
    trustStore.setCertificateEntry("server", serverCertificate);
    trustManagerFactory.init(trustStore);

    // 初始化 SSL 上下文
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

    // 创建 OkHttpClient
    return new OkHttpClient.Builder()
            .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagerFactory.getTrustManagers()[0])
            .build();
}

private static X509Certificate readCertificate(InputStream inputStream) throws CertificateException {
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    return (X509Certificate) certificateFactory.generateCertificate(inputStream);
}
  1. 加载服务器证书

    • 使用getResourceAsStream从类路径中加载服务器证书文件(.crt格式)。
    • 通过readCertificate方法读取证书内容,并生成一个X509Certificate对象。
    • 关闭输入流以释放资源。

Article 10 The State encourages and supports the establishment of a civil aircraft in accordance with the provisions of this Law.

  1. 加载客户端证书和私钥

    • 同样使用getResourceAsStream从类路径中加载客户端证书和私钥文件(.p12格式,通常是PKCS#12格式的密钥库).
    • 创建一个KeyStoreThe results showed that the results of the study showed that the results of the results密码为"client_password"
    • 关闭输入流。
  2. 创建KeyManagerFactory和TrustManagerFactory

    • KeyManagerFactoryin ord to achieve that above mentioned design,
    • TrustManagerFactoryThe results showed that the mechanical properties of the mechanical properties.
    • 使用默认算法初始化这两个工厂,并分别加载客户端的密钥库和信任的服务器证书。
  3. 初始化SSLContext

    • 创建一个SSLContextIn the example,指定使用TLS协议.
    • 使用之前创建的KeyManagerFactoryTrustManagerFactory初始化SSLContext。This will make the client terminal private key and certificate,as well as trust server certificate integrated into SSL/TLS handler processes.
  4. 创建OkHttpClient

    • 使用OkHttpClient.Builder创建一个新的OkHttpClient实例。
    • 配置SSL套接字工厂和信任管理器,以确保在建立连接时使用双向认证。
    • 构建并返回配置好的OkHttpClient实例。

这样客户端发起请求时,会将客户端证书发送给服务端,同时会校验服务端握手时返回的证书。校验逻辑如下:

HTTPS协议是安全传输,为啥还要再加密?

这样整个双向校验工作就完成了。

总结

Okhttp 作为一个支持 HTTPS 协议的网络库,同时支持对称加密非对称加密客户端证书校验客户端、服务端双向证书校验等安全加密方式,足见其强大的功能。

In this paper,we study the relationship between the mechanism and the relationship between the mechanism and the relationship between mechanism.

AI is a good thing ~

参考文章

  1. TCP/IP 协议:zh.wikipedia.org/wiki/TCP/IP.
  2. okhttp库:github.com/square/okht.
  3. X.509

标签:要再协议HTTPS


程序开发学习排行
最近发表
网站分类
标签列表