HTTP1.x가 데이터를 전송할 때 전송되는 모든 내용은 클라이언트나 서버 모두 상대방의 신원을 확인할 수 없습니다.
실제로 이러한 문제는 HTTP뿐만 아니라 다른 암호화되지 않은 프로토콜에서도 발생합니다.
(1) 일반 텍스트를 사용한 통신은 도청될 수 있습니다
TCP/IP 프로토콜 제품군의 작동 메커니즘에 따르면 통신 내용이 인터넷 어디에서나 도청될 위험이 있습니다. HTTP 프로토콜 자체에는 암호화 기능이 없으며 전송되는 모든 데이터는 일반 텍스트입니다. 통신이 암호화되어 있더라도 통신 내용은 암호화되지 않은 통신과 동일하게 엿볼 수 있습니다. 이는 통신이 암호화되면 메시지 정보의 의미를 해독하는 것이 불가능할 수 있지만 암호화된 메시지 자체는 계속 볼 수 있다는 의미입니다.
(2) 통신 당사자의 신원을 확인하지 않으면 위장으로 이어질 수 있습니다
HTTP 프로토콜 통신 중에는 통신 당사자를 확인하는 처리 단계가 없으므로 누구나 요청을 시작할 수 있습니다. 또한 서버가 요청을 수신하는 한 상대방이 누구인지에 관계없이 응답을 반환합니다. 따라서 통신 당사자가 확인되지 않으면 다음과 같은 숨겨진 위험이 있습니다.
(3) 메시지의 무결성을 입증할 수 없으며 변조되었을 수 있습니다
소위 완전성이란 정보의 정확성을 의미합니다. 완전성을 입증하지 못한다는 것은 일반적으로 정보가 정확한지 판단할 수 없다는 것을 의미합니다. HTTP 프로토콜은 요청이나 응답이 전송된 후 상대방이 이를 수신할 때까지 통신 메시지의 무결성을 입증할 수 없습니다. 요청이나 응답의 내용이 변조되었는지 알 수 있는 방법이 없습니다.
예를 들어, 특정 웹사이트에서 콘텐츠를 다운로드할 때 클라이언트가 다운로드한 파일과 서버에 저장된 파일이 일치하는지 판단하는 것은 불가능합니다. 파일 내용이 전송 중에 다른 내용과 변조되었을 수 있습니다. 내용이 실제로 변경되더라도 수신자인 클라이언트는 이를 인식하지 못합니다. 이처럼 요청이나 응답을 공격자가 가로채서 전송 중에 내용을 변조하는 공격을 MITM(Man-in-the-Middle 공격)이라고 합니다.
(4) 보안 HTTP 버전이 가져야 하는 몇 가지 특성
위의 문제로 인해 다음과 같은 기능을 제공할 수 있는 HTTP 보안 기술이 필요합니다.
(1) 서버 인증(클라이언트는 가짜 서버가 아닌 실제 서버와 통신하고 있음을 알고 있음);
(2) 클라이언트 인증(서버는 자신이 가짜 클라이언트가 아닌 실제 클라이언트와 통신하고 있음을 알고 있음);
(3) 무결성(클라이언트 및 서버의 데이터는 수정되지 않음);
(4) 암호화(클라이언트와 서버 간의 대화는 비공개이므로 도청될 염려가 없습니다);
(5) 효율성(저사양 클라이언트 및 서버에서 사용할 수 있을 만큼 빠르게 실행되는 알고리즘);
(6) 보편성(기본적으로 모든 클라이언트와 서버가 이러한 프로토콜을 지원함);
이러한 요구 속에서 HTTPS 기술이 탄생했습니다. HTTPS 프로토콜의 주요 기능은 기본적으로 TLS/SSL 프로토콜에 의존하는데, 이는 인증, 정보 암호화 및 무결성 검증 기능을 제공하여 HTTP의 보안 문제를 해결할 수 있습니다. 이 섹션에서는 HTTPS 프로토콜의 몇 가지 주요 기술 사항에 중점을 둘 것입니다.
(1) 암호화 기술
암호화 알고리즘은 일반적으로 두 가지 유형으로 나뉩니다.
대칭 암호화: 암호화 및 복호화 키가 동일합니다. DES 알고리즘으로 표현됩니다.
비대칭 암호화: 암호화 키와 복호화 키가 다릅니다. RSA 알고리즘으로 표현됩니다.
대칭 암호화는 매우 강력하며 일반적으로 해독할 수 없습니다. 그러나 큰 문제는 클라이언트와 서버 간의 각 세션이 암호화 및 복호화에 고정된 동일한 키를 사용하는 경우 키를 생성하고 안전하게 저장할 수 없다는 것입니다. 큰 안전 위험이 있습니다.
비대칭 키 교환 알고리즘이 등장하기 전에는 대칭 암호화의 큰 문제는 키를 안전하게 생성하고 저장하는 방법을 모른다는 것이었습니다. 비대칭 키 교환 프로세스는 주로 이 문제를 해결하고 키 생성 및 사용을 보다 안전하게 만드는 것입니다. 하지만 이는 HTTPS 성능과 속도를 심각하게 저하시키는 '범인'이기도 합니다.
HTTPS는 대칭 암호화와 비대칭 암호화를 모두 사용하는 하이브리드 암호화 메커니즘을 사용합니다. 키 교환 프로세스에서는 비대칭 암호화를 사용하고 후속 통신 및 메시지 교환 단계에서는 대칭 암호화를 사용합니다.
(2) 인증 - 공개키의 정확성을 증명하는 인증서
비대칭 암호화의 가장 큰 문제점 중 하나는 공개키 자체가 진짜 공개키인지 증명할 수 없다는 점입니다. 예를 들어, 공개키 암호화를 사용해 특정 서버와의 통신 설정을 준비할 때, 수신한 공개키가 원래 예상했던 서버에서 발급한 공개키인지 어떻게 증명할 수 있는지. 아마도 공개 키 전송 중에 실제 공개 키가 공격자에 의해 대체되었을 수 있습니다.
공개키의 신뢰성이 검증되지 않으면 적어도 중간자 공격과 정보 거부라는 두 가지 문제가 발생할 수 있습니다.
위 문제를 해결하기 위해서는 디지털 인증서 인증기관(CA, 인증기관) 및 관련 기관에서 발급한 공개키 인증서를 이용하면 됩니다.
CA를 사용하기 위한 구체적인 절차는 다음과 같습니다.
(1) 서버 운영자는 디지털 인증서 인증 기관(CA)에 공개 키를 신청합니다.
(2) CA는 조직의 존재 여부, 기업의 합법성 여부, 도메인 이름의 소유권 보유 여부 등 온라인, 오프라인 및 기타 수단을 통해 신청자가 제공한 정보의 진위 여부를 확인합니다.
(3) 정보가 승인되면 CA는 적용된 공개키에 디지털 서명을 한 후 서명된 공개키를 배포하고 공개키 인증서에 공개키를 넣어 결합합니다. 인증서에는 신청자의 공개 키, 신청자의 조직 정보 및 개인 정보, 발급 기관 CA 정보, 유효 기간, 인증서 일련 번호 및 기타 일반 텍스트 정보가 포함되어 있으며 서명도 포함되어 있습니다. 서명 생성 알고리즘: 먼저 해시를 사용합니다. 열 함수는 공개 일반 텍스트 정보의 정보 다이제스트를 계산한 다음 CA의 개인 키를 사용하여 정보 다이제스트를 암호화하고 암호문이 서명입니다.
(4) 클라이언트는 HTTPS 핸드셰이크 단계 중에 서버에 인증서 파일 반환을 요청하는 요청을 보냅니다.
(5) 클라이언트는 인증서의 관련 일반 텍스트 정보를 읽고 동일한 해시 함수를 사용하여 정보 다이제스트를 계산한 다음 해당 CA의 공개 키를 사용하여 서명 데이터를 해독하고 인증서의 정보 다이제스트를 비교합니다. 일관성이 있으면 인증서의 적법성, 즉 공개 키가 적법한지 확인할 수 있습니다.
(6) 그런 다음 클라이언트는 도메인 이름 정보, 유효 기간 및 인증서와 관련된 기타 정보를 확인합니다.
(7) 클라이언트에는 신뢰 CA 인증서 정보(공개 키 포함)가 내장되어 있습니다. CA를 신뢰할 수 없으면 해당 CA에 해당하는 인증서를 찾을 수 없으며 해당 인증서는 불법으로 간주됩니다.
이 과정에서 몇 가지 사항에 주의하세요.
(1) 인증서를 신청할 때 개인 키를 제공할 필요가 없으므로 개인 키는 서버에서만 영원히 마스터할 수 있습니다.
(2) 인증서의 유효성은 여전히 비대칭 암호화 알고리즘에 따라 다릅니다. 인증서는 주로 서버 정보와 서명을 추가합니다.
(3) 내장 CA에 해당하는 인증서를 루트 인증서라고 하며, 발급자와 사용자가 동일하며 자체 서명된 인증서라고 합니다.
(4) 인증서 = 공개 키 + 신청자 및 발급자 정보 + 서명
(1) HTTPS의 역사
HTTPS 프로토콜 기록 소개:
(2) 프로토콜 구현
거시적으로 TLS는 레코드 프로토콜에 의해 구현됩니다. 레코드 프로토콜은 전송 연결을 통해 모든 하위 수준 메시지를 교환하는 역할을 하며 암호화를 위해 구성될 수 있습니다. 모든 TLS 레코드는 짧은 헤더로 시작됩니다. 헤더에는 레코드 내용의 유형(또는 하위 프로토콜), 프로토콜 버전 및 길이가 포함됩니다. 메시지 데이터는 아래와 같이 헤더 뒤에 나옵니다.
TLS 마스터 사양은 4가지 핵심 하위 프로토콜을 정의합니다.
(3) 악수약정
핸드셰이크는 TLS 프로토콜에서 가장 정교한 부분입니다. 이 프로세스 동안 통신 당사자는 연결 매개변수를 협상하고 ID 인증을 완료합니다. 사용되는 기능에 따라 전체 프로세스에는 일반적으로 6~10개의 메시지 교환이 필요합니다. 구성 및 지원되는 프로토콜 확장에 따라 교환 프로세스에 다양한 변형이 있을 수 있습니다. 사용 중에 다음 세 가지 프로세스를 자주 볼 수 있습니다.
(4) 단방향 검증 핸드셰이크 프로세스
이 섹션에서는 QQ 메일함의 로그인 프로세스를 예로 들어 패킷 캡처를 통한 단방향 확인의 핸드셰이크 프로세스를 분석합니다. 단방향 검증을 위한 전체 핸드셰이크 프로세스는 다음과 같습니다.
주로 4단계로 나뉩니다:
1.ClientHello
핸드셰이크 과정에서 ClientHello가 첫 번째 메시지입니다. 이 메시지는 클라이언트의 기능과 기본 설정을 서버에 전달합니다. 클라이언트가 지원하는 지정된 SSL 버전, Cipher Suite 목록(사용된 암호화 알고리즘, 키 길이 등)이 포함되어 있습니다.
2.ServerHello
ServerHello 메시지는 서버가 선택한 연결 매개변수를 클라이언트로 다시 전송합니다. 이 메시지의 구조는 각 필드에 하나의 옵션만 포함된다는 점을 제외하면 ClientHello와 유사합니다. 서버의 암호화 구성 요소 콘텐츠와 압축 방법은 모두 수신된 클라이언트 암호화 구성 요소에서 필터링됩니다.
3.인증서
그런 다음 서버는 공개 키 인증서가 포함된 인증서 메시지를 보냅니다. 서버는 보내는 인증서가 선택한 알고리즘 제품군과 일치하는지 확인해야 합니다. 그러나 모든 패키지가 인증을 사용하는 것은 아니며 모든 인증 방법에 인증서가 필요한 것은 아니기 때문에 인증서 메시지는 선택 사항입니다.
4.ServerKeyExchange
ServerKeyExchange 메시지의 목적은 키 교환을 위한 추가 데이터를 전달하는 것입니다. 메시지 내용은 협상 알고리즘 제품군에 따라 다릅니다. 일부 시나리오에서는 서버가 아무것도 보낼 필요가 없으며 이러한 시나리오에서는 ServerKeyExchange 메시지를 보낼 필요가 없습니다.
5.ServerHelloDone
ServerHelloDone 메시지는 서버가 예상되는 모든 핸드셰이크 메시지 전송을 완료했음을 나타냅니다. 그 후 서버는 클라이언트가 메시지를 보낼 때까지 기다립니다.
6.ClientKeyExchange
ClientKeyExchange 메시지는 키 교환을 위해 클라이언트가 제공한 모든 정보를 전달합니다. 이 메시지는 협상된 암호 제품군의 영향을 받으며 내용은 협상된 암호 제품군에 따라 다릅니다.
7.ChangeCipherSpec
ChangeCipherSpec 메시지는 발신자가 연결 매개변수를 생성하기에 충분한 정보를 얻었고, 암호화 키를 생성했으며, 암호화 모드로 전환할 것임을 나타냅니다. 조건이 성숙되면 클라이언트와 서버 모두 이 메시지를 보냅니다. 참고: ChangeCipherSpec은 핸드셰이크 메시지에 속하지 않으며 하위 프로토콜로 구현된 메시지가 하나만 있는 또 다른 프로토콜입니다.
8.완료
완료 메시지는 악수가 완료되었음을 의미합니다. 메시지 내용은 암호화되어 양 당사자가 전체 핸드셰이크의 무결성을 확인하는 데 필요한 데이터를 안전하게 교환할 수 있습니다. 조건이 성숙되면 클라이언트와 서버 모두 이 메시지를 보냅니다.
(5) 양방향 검증의 핸드셰이크 과정
보안 요구 사항이 더 높은 일부 시나리오에서는 양방향 인증이 필요할 수 있습니다. 완전한 양방향 인증 과정은 다음과 같습니다:
단방향 확인 프로세스와 비교하면 양방향 확인에는 CertificateRequest 및 CertificateVerify라는 두 가지 메시지가 더 있다는 것을 알 수 있습니다. 나머지 프로세스는 거의 동일합니다.
1.인증서요청
인증서 요청은 TLS에서 지정한 선택적 기능이며 서버에서 클라이언트의 신원을 인증하는 데 사용됩니다. 클라이언트에게 인증서 전송을 요청하는 서버에 의해 구현되면 서버는 ServerKeyExchange 직후에 CertificateRequest 메시지를 전송해야 합니다.
메시지 구조는 다음과 같습니다:
으아악선택적으로 고유 이름으로 표시되는 허용된 인증 기관 목록을 보낼 수 있습니다.
2.인증서확인
클라이언트 인증이 필요한 경우 클라이언트는 실제로 클라이언트 인증서의 개인 키를 소유하고 있음을 증명하기 위해 CertificateVerify 메시지를 보냅니다. 이 메시지는 클라이언트 인증서에 서명 기능이 있는 경우에만 전송됩니다. CertificateVerify는 ClientKeyExchange 바로 뒤에 와야 합니다. 메시지 구조는 다음과 같습니다:
으아악(6) 애플리케이션 데이터 프로토콜
애플리케이션 데이터 프로토콜은 TLS의 관점에서 애플리케이션 메시지를 전달합니다. 레코드 계층은 현재 연결 보안 매개변수를 사용하여 이러한 메시지를 패키지화하고 조각 모음하고 암호화합니다. 아래 그림과 같이 전송된 데이터가 암호화된 것을 확인할 수 있습니다.
(7) 경고 프로토콜
알람의 목적은 간단한 알림 메커니즘을 통해 비정상적인 통신 상태를 상대방에게 알리는 것입니다. 일반적으로 오류를 보고하기 위해 연결이 닫힐 때 사용되는 close_notify 예외를 전달합니다. 알림은 매우 간단하며 다음 두 개의 필드만 있습니다.
으아악(1) 服务器证书验证错误
这是最常见的一种问题,通常会抛出如下类型的异常:
出现此类错误通常可能由以下的三种原因导致:
当服务器的CA不被系统信任时,就会发生 SSLHandshakeException。可能是购买的CA证书比较新,Android系统还未信任,也可能是服务器使用的是自签名证书(这个在测试阶段经常遇到)。
解决此类问题常见的做法是:指定HttpsURLConnection信任特定的CA集合。在本文的第5部分代码实现模块,会详细的讲解如何让Android应用信任自签名证书集合或者跳过证书校验的环节。
(2) 域名验证失败
SSL连接有两个关键环节。首先是验证证书是否来自值得信任的来源,其次确保正在通信的服务器提供正确的证书。如果没有提供,通常会看到类似于下面的错误:
出现此类问题的原因通常是由于服务器证书中配置的域名和客户端请求的域名不一致所导致的。
有两种解决方案:
(1) 重新生成服务器的证书,用真实的域名信息;
(2) 自定义HostnameVerifier,在握手期间,如果URL的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。可以通过自定义HostnameVerifier实现一个白名单的功能。
代码如下:
HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { // 设置接受的域名集合 if (hostname.equals(...)) { return true; } } }; HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY);
(3) 客户端证书验证
SSL支持服务端通过验证客户端的证书来确认客户端的身份。这种技术与TrustManager的特性相似。本文将在第5部分代码实现模块,讲解如何让Android应用支持客户端证书验证的方式。
(4) Android上TLS版本兼容问题
之前在接口联调的过程中,测试那边反馈过一个问题是在Android 4.4以下的系统出现HTTPS请求不成功而在4.4以上的系统上却正常的问题。相应的错误如下:
03-09 09:21:38.427: W/System.err(2496): javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb7fa0620: Failure in SSL library, usually a protocol error 03-09 09:21:38.427: W/System.err(2496): error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0xa90e6990:0x00000000)
按照官方文档的描述,Android系统对SSL协议的版本支持如下:
也就是说,按官方的文档显示,在API 16+以上,TLS1.1和TLS1.2是默认开启的。但是实际上在API 20+以上才默认开启,4.4以下的版本是无法使用TLS1.1和TLS 1.2的,这也是Android系统的一个bug。
参照stackoverflow上的一些方式,比较好的一种解决方案如下:
SSLSocketFactory noSSLv3Factory; if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { noSSLv3Factory = new TLSSocketFactory(mSSLContext.getSSLSocket().getSocketFactory()); } else { noSSLv3Factory = mSSLContext.getSSLSocket().getSocketFactory(); }
对于4.4以下的系统,使用自定义的TLSSocketFactory,开启对TLS1.1和TLS1.2的支持,核心代码:
public class TLSSocketFactory extends SSLSocketFactory { private SSLSocketFactory internalSSLSocketFactory; public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, null, null); internalSSLSocketFactory = context.getSocketFactory(); } public TLSSocketFactory(SSLSocketFactory delegate) throws KeyManagementException, NoSuchAlgorithmException { internalSSLSocketFactory = delegate; } ...... @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); } // 开启对TLS1.1和TLS1.2的支持 private Socket enableTLSOnSocket(Socket socket) { if(socket != null && (socket instanceof SSLSocket)) { ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"}); } return socket; } }
本部分主要基于第四部分提出的Android应用中使用HTTPS遇到的一些常见的问题,给出一个比较系统的解决方案。
(1) 整体结构
不管是使用自签名证书,还是采取客户端身份验证,核心都是创建一个自己的KeyStore,然后使用这个KeyStore创建一个自定义的SSLContext。整体类图如下:
类图中的MySSLContext可以应用在HttpURLConnection的方式与服务端连接的过程中:
if (JarConfig.__self_signed_https) { SSLContextByTrustAll mSSLContextByTrustAll = new SSLContextByTrustAll(); MySSLContext mSSLContext = new MySSLContext(mSSLContextByTrustAll); SSLSocketFactory noSSLv3Factory; if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { noSSLv3Factory = new TLSSocketFactory(mSSLContext.getSSLSocket().getSocketFactory()); } else { noSSLv3Factory = mSSLContext.getSSLSocket().getSocketFactory(); } httpsURLConnection.setSSLSocketFactory(noSSLv3Factory); httpsURLConnection.setHostnameVerifier(MY_DOMAIN_VERIFY); }else { httpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()); httpsURLConnection.setHostnameVerifier(DO_NOT_VERIFY); }
核心是通过httpsURLConnection.setSSLSocketFactory使用自定义的校验逻辑。整体设计上使用策略模式决定采用哪种验证机制:
(2) 单向验证并自定义信任的证书集合
在App中,把服务端证书放到资源文件下(通常是asset目录下,因为证书对于每一个用户来说都是相同的,并且也不会经常发生改变),但是也可以放在设备的外部存储上。
public class SSLContextWithServer implements GetSSLSocket { // 在这里进行服务器正式的名称的配置 private String[] serverCertificateNames = {"serverCertificateNames1" ,"serverCertificateNames2"}; @Override public SSLContext getSSLSocket() { String[] caCertString = new String[serverCertificateNames.length]; for(int i = 0 ; i < serverCertificateNames.length ; i++) { try { caCertString[i] = readCaCert(serverCertificateNames[i]); } catch(Exception e) { } } SSLContext mSSLContext = null; try { mSSLContext = SSLContextFactory.getInstance().makeContextWithServer(caCertString); } catch(Exception e) { } return mSSLContext; }
serverCertificateNames中定义了App所信任的证书名称(这些证书文件必须要放在指定的文件路径下,并其要保证名称相同),而后就可以加载服务端证书链到keystore,通过获取到的可信任并带有服务端证书的keystore,就可以用它来初始化自定义的SSLContext了:
@Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { originalX509TrustManager.checkServerTrusted(chain, authType); } catch(CertificateException originalException) { try { X509Certificate[] reorderedChain = reorderCertificateChain(chain); CertPathValidator validator = CertPathValidator.getInstance("PKIX"); CertificateFactory factory = CertificateFactory.getInstance("X509"); CertPath certPath = factory.generateCertPath(Arrays.asList(reorderedChain)); PKIXParameters params = new PKIXParameters(trustStore); params.setRevocationEnabled(false); validator.validate(certPath, params); } catch(Exception ex) { throw originalException; } } }
(3) 跳过证书校验过程
和上面的过程类似,只不过这里提供的TrustManager不需要提供信任的证书集合,默认接受任意客户端证书即可:
public class AcceptAllTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { //do nothing,接受任意客户端证书 } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { //do nothing,接受任意服务端证书 } @Override public X509Certificate[] getAcceptedIssuers() { return null; }
而后构造相应的SSLContext:
public SSLContext makeContextToTrustAll() throws Exception { AcceptAllTrustManager tm = new AcceptAllTrustManager(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[] { tm }, null); return sslContext; }
위 내용은 HTTPS의 원리와 Android에서의 사용 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!