1. Antecedentes
O HTTP é um protocolo público que permite a legibilidade da transferência de conteúdo, e os dados do cliente e do servidor são completamente transmitidos através do texto simples. Nesse contexto, todos os dados da Internet que dependem do protocolo HTTP são transparentes, o que traz ótimos riscos de segurança de dados. Existem duas idéias para resolver esse problema:
O primeiro tipo tem uma gama mais ampla de aplicações na realidade do que se imaginou. As duas partes trocam chaves offline e o cliente usa um texto cifrado ao enviar dados, que são transmitidos na Internet através do protocolo HTTP transparente. Depois de receber a solicitação, o servidor descriptografa e obtém o texto simples da maneira acordada. Mesmo que esse tipo de conteúdo seja seqüestrado, isso não importa, porque terceiros não conhecem seus métodos de criptografia e descriptografia. No entanto, essa abordagem é muito especial, e o cliente e o servidor precisam se preocupar com essa lógica especial de criptografia e descriptografia.
O segundo tipo de lado C/S pode não se importar com a lógica especial acima. Eles acreditam que o envio e o recebimento são de texto simples, porque a peça de criptografia e descriptografia foi processada pelo próprio protocolo.
A julgar pelos resultados, parece não haver diferença entre as duas soluções, mas da perspectiva dos engenheiros de software, a diferença é muito enorme. Como o primeiro tipo exige que o sistema de negócios desenvolva a função de criptografia e descriptografia para a resposta e a chave interativa offline é necessária, o segundo tipo não possui o volume de desenvolvimento.
O HTTPS é a forma mais popular de segurança para o HTTP, criada pela primeira vez pelo Netscape. Nos HTTPs, os URLs começam com https: // em vez de http: //. Quando o HTTPS é usado, todas as solicitações e respostas HTTP são criptografadas antes de serem enviadas para a rede, que é implementada na camada SSL.
2. Método de criptografia
Os dados de texto simples são criptografados através da camada SSL e enviados à Internet para transmitir, que resolve o problema original de segurança de dados do protocolo HTTP. De um modo geral, os métodos de criptografar dados são divididos em criptografia simétrica e criptografia assimétrica.
2.1 Criptografia simétrica
A criptografia simétrica refere -se ao uso da mesma chave que a criptografia e a descriptografia. Os algoritmos comuns incluem DES e AES, etc., e o tempo do algoritmo está relacionado ao comprimento da chave.
A maior desvantagem das chaves simétricas é que elas precisam manter um grande número de teclas simétricas e exigir troca offline. Para ingressar em uma rede com n entidades, são necessárias teclas n (n-1).
2.2 Criptografia assimétrica
A criptografia assimétrica refere -se a métodos de criptografia com base em chaves públicas/privadas. Os algoritmos comuns incluem RSA, que geralmente criptografia mais lenta que a criptografia simétrica.
A criptografia simétrica tem mais uma etapa que a criptografia assimétrica, ou seja, para obter a chave pública do servidor, em vez da chave mantida por cada uma.
Todo o algoritmo de criptografia é baseado em uma determinada teoria dos números, e o efeito é que o resultado da criptografia é irreversível. Isto é, somente através da chave privada que a Cifraxt criptografada pela chave pública pode ser descriptografada.
Sob esse algoritmo, o número de chaves em toda a rede é bastante reduzido e cada pessoa só precisa manter um par de chaves da empresa. Ou seja, na rede de n entidades, o número de chaves é 2n.
A desvantagem é que ela funciona lentamente.
2.3 Criptografia híbrida
Há uma cena no filme de Stephen Chow, "God of Cookery", onde o submundo está em um hot spot, discutindo sobre a questão da divisão de chassi entre camarão e bolas de carne. O deus da comida disse: "É realmente problemático. É misturado para fazer uma bola de carne, estúpida!"
A vantagem da criptografia simétrica é sua velocidade rápida, e a desvantagem é que ela requer troca -chave. A vantagem da criptografia assimétrica é que ela não requer uma chave interativa, e a desvantagem é que é velocidade lenta. Apenas misture e use bem.
A criptografia híbrida é exatamente o método de criptografia usado pelo protocolo HTTPS. A chave simétrica é trocada primeiro através da criptografia assimétrica e, em seguida, os dados são transmitidos através da chave simétrica.
Como a quantidade de transmissão de dados é muito maior que a quantidade de dados usados para trocar chaves no estágio inicial de estabelecer uma conexão, o impacto de desempenho da criptografia assimétrica pode ser basicamente insignificante e, ao mesmo tempo, melhora a eficiência.
3. HTTPS Handshake
Pode -se observar que, com base no protocolo HTTP original, o HTTPS adicionou processamento de camada de segurança:
4. Suporte da HttpClient para o protocolo HTTPS
4.1 Obtenha a fábrica de conexão SSL e o dispositivo de verificação de nomes de domínio
Como engenheiro de software, o que nos preocupamos é como o "protocolo HTTPS" é implementado no código? Para explorar o mistério do código -fonte httpclient, tudo começa com o httpclientbuilder.
Public ClosableHttpClient Build () {// omita algum código httpclientConnectionManager ConnManagerCopy = this.connManager; // Se o Gerenciador de pool de conexões estiver especificado, use o especificado, caso contrário, crie um novo padrão if (ConnManagerCopy == null) {LayeredConnectionSocketFFactory SSLSocketFactoryCopy = this.ssSocketFactory; if (sslSocketFactoryCopy == NULL) {// Se a variável de ambiente de uso estiver ativada, a versão HTTPS e o controle de senha Leia a string final [] suportadaProtocols = SystemProperties? Split (System.getProperty ("https.protocols")): null; Final String [] SupportEdCipherSuesuses = SystemProperties? Split (System.getProperty ("https.cipheruses")): null; // Se não for especificado, use o validador de nomes de domínio padrão e ele verificará se o nome de domínio corresponde ao certificado retornado pelo servidor no SSL Session HostNameRriFier hostNameVerriFierCopy = this.hostnameVerifier; if (hostNameVerriFierCopy == NULL) {HostNameVerriFierCopy = new DefaultThostNameVerriFier (PublicSuffixMatcherCopy); } // Se o SSLContext for formulado, uma fábrica de conexão SSL personalizada será gerada; caso contrário, a fábrica de conexão padrão será usada se (sslContext! = Null) {sslSocketFactoryCopy = newSslConnectionSocketFactory (sslContext, suportesProtocols, suportadoCipHerSuSuses, hostNameInMeRiSTeRiSTeRiSTeRiStier; } else {if (SystemProperties) {sslSocketFactoryCopy = new SSLConnectionSocketFactory ((SSLSocketFactory) sslSocketFactory.getDefault (), suportedProtocols, supportedCipheruuições, hostNameVerriFierCoy); } else {sslSocketFactoryCopy = new SSLConnectionSocketFactory (sslContexts.createFault (), hostNameVerriFierCopy); }}} // Registre a fábrica de conexão SSL no Gerenciador de pool de conexões. Quando uma conexão HTTPS é necessária, a conexão SSL será produzida com base na conexão SSL acima @suppresswarnings ("Resource") Final PoolingHttpClientConnectionManager PoolingMgr = New PoolingHttpClientConnectionManager (RegistryBuilder. .register ("https", sslsocketfactorycopy) .build (), null, null, dnsresolver, contimetolive, conntimeToliveTimeUnit! = nulo? // omita alguns códigos}}O código acima cria um SSLConnectionSocketFactory SSLConnectionSocketFactory e o registra no Gerenciador de pool de conexão para a produção posterior de conexões SSL. Referência para o pool de conexão: http://www.vevb.com/article/141015.htm
Aqui, vários componentes -chave são usados ao configurar o SSLConnectionSocketFactory, o nome do validador de nome de domínio NameSverriFier e o contexto SSLContext.
Entre eles, o HostNameVerriFier é usado para verificar se o certificado do servidor corresponde ao nome do domínio. Existem muitas implementações. DeFaultThostNameVerriFier usa as regras de verificação padrão, substituindo o BrowSerCompathostNameVerriFier e o StricThostNameVerriFier na versão anterior. NoophostNameVerriFier substitui AllowallHostNameVerriFier, usando uma estratégia de não verificar o nome do domínio.
Observe que existem algumas diferenças aqui, o BrowSerCompathostNameVerriFier pode corresponder aos nomes de subdomínio de vários níveis e "*.foo.com" pode corresponder "abfoo.com". O StricThostNameVerriFier não pode corresponder aos nomes de subdomínio de vários níveis, apenas com "a.foo.com".
Após 4.4, o HTTPClient usou o novo deformado para substituir as duas estratégias acima, e manteve apenas uma estratégia estrita e o StricThostNameRifier. Como estratégias estritas são as estratégias do IE6 e do próprio JDK, as estratégias não rigíveis são as estratégias de Curl e Firefox. Ou seja, a implementação padrão HTTPClient não suporta a estratégia de correspondência de nomes de subdomínio de vários níveis.
A SSLContext armazena as principais informações relacionadas à chave, que estão diretamente relacionadas ao negócio e é muito importante. Isso deve ser analisado separadamente mais tarde.
4.2 Como obter uma conexão SSL
Como obter uma conexão de um pool de conexão? Esse processo foi analisado em artigos anteriores. Não vou analisá -lo aqui. Consulte a conexão: //www.vevb.com/article/141015.htm.
Depois de obter uma conexão do pool de conexão, se a conexão não estiver no estado estabelecido, você precisará estabelecer a conexão primeiro.
O código da parte de DefaultTttpClientConnectionOperator é:
Public void Connect (Final ManagementHttpClientConnection Conn, host final HttphoSt, Final INetSocketAddress LocalAddress, Final Int ConnectTimeout, SocketConfig final SocketConfig, contexto final HTTPContext) lança IoException {//, a implementação de conexão HTTPRPS foi registrada em httptppcent. Aqui, a pesquisa obtém a implementação de HTTPS, ou seja, SSLConnectionSocketFactory Final Lookup <CnectionSocketFactory> Registry = getSocketFactoryRegistry (Contexto); Final ConnectionsocketFactory sf = Registry.lookup (host.getSchemename ()); if (sf == null) {lança novo não suportadoSchemeexception (host.getschemename () + "protocolo não é suportado"); } // Se o endereço estiver na forma de IP, ele poderá ser usado diretamente; caso contrário, use o analisador DNS para analisar o IP correspondente ao nome do domínio para obter o final inetaddress [] endereços = host.getAddress ()! = NULL? new inetaddress [] {host.getAddress ()}: this.dnsResolver.resolve (host.gethostName ()); final int porta = this.schemeportresolver.resolve (host); // Um nome de domínio pode corresponder a múltiplos IP e tentar conectar -se para (int i = 0; i <endereço.length; i ++) {final inetaddress endereço = endereços [i]; final booleano last = i == endereços.Length - 1; // Este é apenas um soquete gerado e não há conexão com soquete de soquete = sf.createSocket (contexto); // Defina alguns parâmetros da camada TCP SOCK.SETSOTimeout (SocketConfig.getSotimeout ()); sock.setReUseadDress (SocketConfig.issoreUseAddress ()); Sock.SettcpNodelay (SocketConfig.istcpnodelay ()); Sock.setKeepalive (SocketConfig.ISSOKEEPALIVE ()); if (SocketConfig.GetRCVBufSize ()> 0) {Sock.SetReceiveBuffersize (SocketConfig.GetRCVBufSize ()); } if (SocketConfig.getsndbufsize ()> 0) {sock.setsendBuffersize (SocketConfig.getsndbufsize ()); } final int linger = soketconfig.getSolinger (); if (permanecem> = 0) {sock.setsolinger (true, linger); } Conn.Bind (SOCK); Final inetSocketAddress remoteaddress = new inetSocketAddress (endereço, porta); if (this.log.isdebugenabled ()) {this.log.debug ("conectando -se a" + remoteaddress); } tente {// estabeleça uma conexão através do SSLConnectionSocketFactory e Bind a Conn Sock = SF.ConnectSocket (ConnectTimeout, Sock, Host, Remoteaddress, LocalAddress, Context); Conn.Bind (Sock); if (this.log.isdebugenabled ()) {this.log.debug ("conexão estabelecida" + conn); } retornar; } // omita algum código}}No código acima, vemos que o trabalho de preparação é antes de estabelecer uma conexão SSL. Este é um processo geral e o mesmo é verdadeiro para conexões HTTP comuns. Onde o processo especial de conexão SSL reflete?
O código -fonte do SSLConnectionSocketFactory é o seguinte:
@Override Public Socket ConnectSocket (Final Int ConnectTimeout, soquete final, host final HttphoSt, INetSocketDress final, Remoteaddress, Final InetSocketAddress LocalAddress, contexto final httpContext) lança IoException {args.notNull ("host http host"); Args.NotNull (remoteaddress, "endereço remoto"); soquete final soquete = soquete! = nulo? soquete: createSocket (contexto); if (localAddress! = NULL) {SOCK.BIND (LocalAddress); } tente {if (ConnectTimeout> 0 && sock.getSotimeout () == 0) {sock.seSTimeout (ConnectTimeout); } if (this.log.isdebugenabled ()) {this.log.debug ("conectando soquete a" + remoteaddress + "com timeout" + ConnectTimeout); } // Crie conexão SOCK.Connect (Remoteaddress, ConnectTimeout); } catch (final ioexception ex) {try {sock.close (); } catch (ioexception final ignore) {} tiro ex; } // Se atualmente estiver SSLSocket, execute o handshake SSL e a verificação do nome do domínio se (sock instanceof sslsocket) {final sslsocket sslsock = (sslsocket) sok; this.log.debug ("handshake de partida"); SSLSOCK.STARTHANDSHAKE (); verifyHostName (sslsock, host.gethostName ()); meia de retorno; } else {// Se não for sslsocket, embrulhe -o como sslsocket retornar createlayeredsocket (sok, host.gethostName (), remoteaddress.getport (), context); }} @Override Public Socket CreateLayedSocket (soquete final, alvo final da string, porta INT final, contexto final httpContext) lança a ioexception {// envolve um soquete normal como sslsocket, o SocketFactory é gerado com base no sslContext em httpslsl (que contém as informações de sslslslslsl (sloketsl), que contém as informações de sslslingslslingslingslingslingslingslingsl). this.socketFactory.createsocket (soquete, destino, porta, true); // Se a versão do protocolo da camada SSL e o algoritmo de criptografia forem formulados, use o especificado, caso contrário, o padrão if (supportedProtocols! = Null) {sslsock.setEnabledProtocols (suporteprotocols); } else {// Se protocolos suportados não forem definidos explicitamente, remova todas as versões do protocolo SSL final string [] allProtocols = sslsock.getEnabledprotocols (); LISTA FINAL <String> EnabledProtocols = new ArrayList <String> (allProtocols.length); para (Final String Protocol: allProtocols) {if (! protocol.startswith ("ssl")) {EnabledProtocols.add (protocolo); }} if (! EnabledProtocols.isEmpty ()) {sslsock.setEnabledProtocols (EnabledProtocols.toArray (new String [HabiledProtocols.size ()])); }} if (supportedCipherSuesuses! = null) {sslsock.setEnabledCipherSuesuses (suporte de suporte); } if (this.log.isdebugenabled ()) {this.log.debug ("Protocolos habilitados:" + Arrays.asList (sslsock.getEnabledprotocols ()); this.log.debug ("Ativado cifra suítes:" + Arrays.asList (sslsock.getEnabledCipheruSuses ()); } prepareSocket (sslsock); this.log.debug ("handshake de partida"); // conexão SSL Handshake sslsock.StarthandShake (); // Depois que o handshake for bem -sucedido, verifique se o certificado retornado é consistente com o nome de domínio VerifyHostName (SSLSock, Target); retornar SSLSOCK; }Como pode ser visto, para uma comunicação SSL. Primeiro, estabeleça uma conexão de soquete normal, depois execute um aperto de mão SSL e verifique a consistência do certificado e o nome do domínio. A operação subsequente deve se comunicar através do SSLSocketImpl. Os detalhes do protocolo são refletidos na classe SSLSocketImpl, mas essa parte do código JDK não é de código aberto. Aqueles que estão interessados podem baixar o código -fonte OpenJDK correspondente para continuar a análise.
5. Resumo deste artigo
Ok, o acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.