1. Latar belakang
HTTP adalah protokol publik yang memungkinkan keterbacaan mentransfer konten, dan data dari klien dan server sepenuhnya ditransmisikan melalui Plaintext. Di bawah latar belakang ini, seluruh data Internet yang bergantung pada protokol HTTP transparan, yang membawa risiko keamanan data yang hebat. Ada dua ide untuk menyelesaikan masalah ini:
Tipe pertama memiliki rentang aplikasi yang lebih luas pada kenyataannya daripada yang dibayangkan. Kedua pihak bertukar kunci secara offline, dan klien menggunakan ciphertext saat mengirim data, yang ditransmisikan di Internet melalui protokol HTTP transparan. Setelah menerima permintaan, server mendekripsi dan mendapatkan teks biasa dengan cara yang disepakati. Bahkan jika konten semacam ini dibajak, tidak masalah, karena pihak ketiga tidak tahu metode enkripsi dan dekripsi mereka. Namun, pendekatan ini terlalu istimewa, dan baik klien dan server perlu peduli dengan logika enkripsi dan dekripsi khusus ini.
Jenis kedua sisi C/S mungkin tidak peduli dengan logika khusus di atas. Mereka percaya bahwa pengiriman dan penerimaan adalah plaintext karena bagian enkripsi dan dekripsi telah diproses oleh protokol itu sendiri.
Menilai dari hasil, tampaknya tidak ada perbedaan antara kedua solusi, tetapi dari perspektif insinyur perangkat lunak, perbedaannya sangat besar. Karena tipe pertama membutuhkan sistem bisnis untuk mengembangkan fungsi enkripsi dan dekripsi untuk respons, dan kunci interaktif offline diperlukan, tipe kedua tidak memiliki volume pengembangan.
HTTPS adalah bentuk keamanan paling populer untuk HTTP, pertama kali dibuat oleh Netscape. Dalam https, URL mulai dengan https: // bukannya http: //. Ketika HTTPS digunakan, semua permintaan dan tanggapan HTTP dienkripsi sebelum dikirim ke jaringan, yang diimplementasikan pada lapisan SSL.
2. Metode enkripsi
Data teks biasa dienkripsi melalui lapisan SSL dan kemudian dikirim ke Internet untuk dikirim, yang memecahkan masalah keamanan data asli dari protokol HTTP. Secara umum, metode enkripsi data dibagi menjadi enkripsi simetris dan enkripsi asimetris.
2.1 Enkripsi Simetris
Enkripsi simetris mengacu pada penggunaan kunci yang sama dengan enkripsi dan dekripsi. Algoritma umum termasuk DES dan AES, dll., Dan waktu algoritma terkait dengan panjang kunci.
Kerugian terbesar dari kunci simetris adalah bahwa mereka perlu mempertahankan sejumlah besar kunci simetris dan memerlukan pertukaran offline. Untuk bergabung dengan jaringan dengan n entitas, tombol N (N-1) diperlukan.
2.2 Enkripsi asimetris
Enkripsi asimetris mengacu pada metode enkripsi berdasarkan kunci publik/pribadi. Algoritma umum termasuk RSA, yang umumnya enkripsi lebih lambat daripada enkripsi simetris.
Enkripsi simetris memiliki satu langkah lebih dari enkripsi asimetris, yaitu, untuk mendapatkan kunci publik server, daripada kunci yang dikelola oleh masing -masing.
Seluruh algoritma enkripsi didasarkan pada teori bilangan tertentu, dan efeknya adalah bahwa hasil enkripsi tidak dapat diubah. Artinya, hanya melalui kunci pribadi yang dapat dienkripsi ciphertext oleh kunci publik didekripsi.
Di bawah algoritma ini, jumlah kunci di seluruh jaringan sangat berkurang, dan setiap orang hanya perlu mempertahankan sepasang kunci perusahaan. Yaitu, dalam jaringan n entitas, jumlah kunci adalah 2n.
Kerugiannya adalah berjalan perlahan.
2.3 Enkripsi hibrida
Ada adegan dalam film Stephen Chow "God of Cookery" di mana dunia bawah berada di hot spot, berdebat tentang masalah divisi sasis antara udang kencing dan bola daging sapi. Dewa makanan berkata, "Ini benar -benar merepotkan. Bercampur bersama untuk membuat bola daging sapi kencing, bodoh!"
Keuntungan dari enkripsi simetris adalah kecepatannya yang cepat, dan kerugiannya adalah membutuhkan pertukaran kunci. Keuntungan dari enkripsi asimetris adalah tidak memerlukan kunci interaktif, dan kerugiannya adalah kecepatan lambat. Campur saja dan gunakan dengan baik.
Enkripsi hibrida adalah persis metode enkripsi yang digunakan oleh protokol HTTPS. Kunci simetris ditukar terlebih dahulu melalui enkripsi asimetris, dan kemudian data ditransmisikan melalui kunci simetris.
Karena jumlah transmisi data jauh lebih besar daripada jumlah data yang digunakan untuk menukar kunci pada tahap awal membangun koneksi, dampak kinerja enkripsi asimetris pada dasarnya dapat diabaikan, dan pada saat yang sama meningkatkan efisiensi.
3. Jabat tangan https
Dapat dilihat bahwa berdasarkan protokol HTTP asli, HTTPS telah menambahkan pemrosesan lapisan keamanan:
4. Dukungan httpclient untuk protokol https
4.1 Dapatkan Perangkat Verifikasi Pabrik dan Nama Domain SSL
Sebagai insinyur perangkat lunak, yang kita pedulikan adalah bagaimana "protokol https" diimplementasikan dalam kode? Untuk menjelajahi misteri kode sumber httpClient, semuanya dimulai dengan httpclientBuilder.
publik closeableHttpClient build () {// hilangkan beberapa kode httpClientConnectionManager ConnmanAgerCopy = this.connManager; // Jika Connection Pool Manager ditentukan, gunakan yang ditentukan, jika tidak buat default baru jika (connmanAgerCopy == null) {LayeredConnectionCocketFactory sslsocketFactoryCopy = this.sslsocketFactory; if (sslsocketFactoryCopy == null) {// Jika variabel lingkungan penggunaan diaktifkan, versi https dan kontrol kata sandi baca string final [] didukungProtocols = SystemProperties? split (System.getProperty ("https.protocols")): null; String akhir [] SupportedCipHerSuites = SystemProperties? split (System.getProperty ("https.ciphersuites")): null; // Jika tidak ditentukan, gunakan validator nama domain default, dan itu akan memverifikasi apakah nama domain cocok dengan sertifikat yang dikembalikan oleh server di SSL HostNeVerifier hostNeVerifiercopy = this.hostnameverifier; if (hostnameverifiercopy == null) {hostnameverifiercopy = new defaulthostnameverifier (publicSuffixMatcherCopy); } // Jika SSLContext diformulasikan, pabrik koneksi SSL yang disesuaikan dihasilkan, jika tidak, pabrik koneksi default digunakan IF (SSLContext! = NULL) {sslsocketFactoryCopy = SSLConnectionCockeTfactory (sslcontext, didukungprotokol, mendukung; sslconnectionCiphacHacHacHacHiPH (SSLContExhycopy, SSLCOPROPOLCOPHEIPH (SSLCONPOIDCOPHETNOPHETNOPHETCOPOPORE, SSLCOPOIPHIPHIPHIPSHIPOP, SSLCOPROPOLCOPHETCOPHETCOPOTE, } else {if (SystemProperties) {sslsocketFactoryCopy = SSLConnectionCockeTfactory baru ((sslsocketfactory) sslsocketfactory.getDefault (), didukungprotocols, supportedciphersuites, hostnamereviercopy); } else {sslsocketFactoryCopy = SSLConnectionCockFactory baru (sslcontexts.createdefault (), hostnameverifiercopy); }}} // Daftarkan pabrik koneksi SSL di Manajer Pool Connection. When an Https connection is needed, the SSL connection will be produced based on the above SSL connection factory @SuppressWarnings("resource") final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionCocketFactory.GetSocketFactory ()) .register ("https", sslsocketfactorycopy) .build (), null, null, dnsresolver, conntimetolive, conntimetolivetimeunit! = Nol? // hilangkan beberapa kode}}Kode di atas membuat SSLConnectionCocketFactory SSLConnectionCocketFactory dan mendaftarkannya di Connection Pool Manager untuk produksi koneksi SSL nanti. Referensi untuk pengumpulan koneksi: http://www.vevb.com/article/141015.htm
Di sini, beberapa komponen utama digunakan saat mengkonfigurasi SSLConnectionCocketFactory, hostNeVerifier validator nama domain dan konteks sslcontext.
Di antara mereka, HostNeMerifier digunakan untuk memverifikasi apakah sertifikat server cocok dengan nama domain. Ada banyak implementasi. DeFaulthostNeVerifier menggunakan aturan verifikasi default, mengganti browserCompathostnameverifier dan Stricthostnameverifier di versi sebelumnya. Noophostnameverifier menggantikan AllowHostNeMersifier, menggunakan strategi tidak memverifikasi nama domain.
Perhatikan bahwa ada beberapa perbedaan di sini, BrowserCompathostNeVerifier dapat mencocokkan nama subdomain multi-level, dan "*.foo.com" dapat cocok dengan "abfoo.com". Stricthostnameverifier tidak dapat cocok dengan nama subdomain multi-level, hanya dengan "a.foo.com".
Setelah 4.4, httpClient menggunakan defaulthostnamerifier baru untuk menggantikan dua strategi di atas, dan hanya mempertahankan satu strategi ketat dan stricthostnameverifier. Karena strategi yang ketat adalah strategi IE6 dan JDK itu sendiri, strategi yang tidak ketat adalah strategi Curl dan Firefox. Artinya, implementasi httpclient default tidak mendukung strategi pencocokan nama subdomain multi-level.
SSLContext menyimpan informasi utama yang terkait dengan kunci, yang secara langsung terkait dengan bisnis dan sangat penting. Ini akan dianalisis secara terpisah nanti.
4.2 Cara Mendapatkan Koneksi SSL
Bagaimana cara mendapatkan koneksi dari kumpulan koneksi? Proses ini telah dianalisis dalam artikel sebelumnya. Saya tidak akan menganalisisnya di sini. Silakan merujuk ke koneksi: //www.vevb.com/article/141015.htm.
Setelah mendapatkan koneksi dari kumpulan koneksi, jika koneksi tidak dalam keadaan yang ditetapkan, Anda perlu membuat koneksi terlebih dahulu.
Kode bagian defaulthttpClientConnectionoperator adalah:
public void connect (Final ManagedHTTPClientConnection Conn, host httphost akhir, final inetsocketaddress localAddress, final int connecttimeout, final socketConfig socketConfig, konteks httpcontext akhir) yang terdaftar di httpcly. Di sini, pencarian memperoleh implementasi HTTPS, yaitu, SSLConnectionCocketFactory Final Lookup <ConnectionCocketFactory> Registry = getSocketFactoryRegistry (konteks); Final ConnectionCocketFactory SF = Registry.LookUp (host.getschemename ()); if (sf == null) {lempar baru yang tidak didukung. Protokol tidak didukung "); } // Jika alamat dalam bentuk IP, itu dapat digunakan secara langsung, jika tidak gunakan parser DNS untuk menguraikan IP yang sesuai dengan nama domain untuk mendapatkan alamat inetaddress [] akhir = host.getAddress ()! = Null? inetaddress baru [] {host.getAddress ()}: this.dnsresolver.resolve (host.gethostname ()); final int port = this.schemeportresolver.resolve (host); // Nama domain mungkin sesuai dengan beberapa IP, dan mencoba untuk terhubung untuk (int i = 0; i <alamat alamat. final boolean terakhir = i == alamat. Panjang - 1; // Ini hanya soket yang dihasilkan, dan tidak ada koneksi ke socket sock = sf.createSocket (konteks); // atur beberapa parameter lapisan 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 = socketConfig.getSolinger (); if (linger> = 0) {sock.setsolinger (true, linger); } conn.bind (sock); inetsocketAddress final remoteAddress = inetsocketAddress baru (alamat, port); if (this.log.isdebugeNabled ()) {this.log.debug ("Menghubungkan ke" + remoteAddress); } coba {// buat koneksi melalui sslConnectionCocketFactory dan bind ke conn sock = sf.connectSocket (connecttimeout, sock, host, remoteAddress, localAddress, context); Conn.Bind (Sock); if (this.log.isdebugeNabled ()) {this.log.debug ("koneksi didirikan" + conn); } kembali; } // hilangkan beberapa kode}}Dalam kode di atas, kami melihat bahwa pekerjaan persiapan adalah sebelum membuat koneksi SSL. Ini adalah proses umum, dan hal yang sama berlaku untuk koneksi HTTP biasa. Di mana proses khusus koneksi SSL?
Kode sumber SSLConnectionCocketFactory adalah sebagai berikut:
@Override Public Socket ConnectSocket (final int connecttimeout, soket soket akhir, host httphost akhir, final inetsocketaddress remoteAddress, inetsocketaddress final localAddress, konteks httpcontext akhir); Args.notnull (RemoteAddress, "Remote Address"); Socket Socket terakhir = soket! = NULL? soket: createCocket (konteks); if (localAddress! = null) {sock.bind (localAddress); } coba {if (connectTimeout> 0 && sock.getSoTimeout () == 0) {sock.setsoTimeout (connecttimeout); } if (this.log.isdebugeNabled ()) {this.log.debug ("Menghubungkan soket ke" + remoteAddress + "dengan timeout" + connecttimeout); } // Buat sock. } catch (final ioException ex) {coba {sock.close (); } catch (IoException final abaikan) {} throw ex; } // Jika saat ini sslsocket, lakukan ssl jabat tangan dan verifikasi nama domain jika (sock instance dari sslsocket) {final sslsocket sslsock = (sslsocket) sock; this.log.debug ("start handshake"); sslsock.starthandshake (); verifikasihostname (sslsock, host.gethostname ()); Kaus kaki kembali; } else {// Jika bukan sslsocket, bungkus sebagai sslsocket return createlayeredsocket (sock, host.gethostname (), remoteaddress.getport (), konteks); } } @Override public Socket createLayeredSocket( final Socket socket, final String target, final int port, final HttpContext context) throws IOException { //Wrap a normal socket as SslSocket, socketfactory is generated based on SSLContext in HttpClientBuilder, which contains the key information final SSLSocket sslsock = (Sslsocket) this.socketfactory.createsocket (soket, target, port, true); // Jika versi protokol lapisan SSL dan algoritma enkripsi diformulasikan, gunakan yang ditentukan, jika tidak jika default (didukung protokol! = Null) {sslsock.setEnabledProtocols (didukungprotocols); } else {// Jika protokol yang didukung tidak diatur secara eksplisit, hapus semua string final versi protokol SSL [] allProtocols = sslsock.getEnabledProtocols (); Daftar Akhir <String> enabledProtocols = new ArrayList <String> (allProtocols.length); untuk (Final String Protocol: AllProtocols) {if (! Protocol.Startswith ("SSL")) {enabledProtocols.add (protocol); }} if (! enabledProtocols.isempty ()) {sslsock.setEnableDprotocols (enabledprotocols.toArray (string baru [enabledprotocols.size ()])); }} if (SupportedCipHerSuites! = null) {sslsock.setEnableDcipherSuites (SupportedCipHerSuites); } if (this.log.isdebugeNabled ()) {this.log.debug ("Protokol yang diaktifkan:" + arrays.aslist (sslsock.getEnabledProtocols ())); this.log.debug ("Enabled Cipher Suites:" + arrays.aslist (sslsock.getEnableDcipherSuites ())); } prepareSocket (sslsock); this.log.debug ("start handshake"); // ssl koneksi jabat tangan sslsock.starthandshake (); // Setelah jabat tangan berhasil, periksa apakah sertifikat yang dikembalikan konsisten dengan nama domain VerifyHostName (SSLSOCK, Target); mengembalikan sslsock; }Seperti yang dapat dilihat, untuk komunikasi SSL. Pertama, buat koneksi soket normal, kemudian lakukan jabat tangan SSL, dan kemudian verifikasi konsistensi sertifikat dan nama domain. Operasi selanjutnya adalah berkomunikasi melalui SSLSocketImpl. Detail protokol tercermin dalam kelas SSLSocketImpl, tetapi bagian dari kode JDK ini bukan open source. Mereka yang tertarik dapat mengunduh kode sumber OpenJDK yang sesuai untuk melanjutkan analisis.
5. Ringkasan artikel ini
Oke, di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.