Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як працює HTTPS та відмінності від HTTP». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**HTTPS** (HyperText Transfer Protocol Secure), це HTTP поверх TLS. Шифрує весь трафік між браузером і сервером та перевіряє особу сервера через сертифікат CA. ``` // HTTP порт 80 - відкритий текст, видно в мережі // HTTPS порт 443 - TLS 1.3, шифрування AES-256-GCM ``` **Ключове:** HTTPS для всього production-трафіку; overhead TLS 1.3 handshake менше 50мс.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**HTTPS** (HyperText Transfer Protocol Secure), це HTTP із шаром TLS зверху. Шифрує кожен байт між браузером і сервером та перевіряє особу сервера ще до першого байта даних. ## Теорія ### TL;DR - HTTP схожий на листівку (будь-хто на шляху читає). HTTPS як запечатаний конверт із перевіркою документів - Головна різниця: TLS handshake узгоджує ключі шифрування і перевіряє сертифікат сервера ще до передачі даних - HTTP: порт 80, HTTPS: порт 443 - TLS 1.3 скоротив handshake до одного round trip, затримка практично непомітна - Торкається користувачів або даних? HTTPS. Виняток тільки для localhost ### Швидкий приклад ```javascript // Node.js: HTTP vs HTTPS сервер const express = require('express'); const https = require('https'); const http = require('http'); const fs = require('fs'); const app = express(); app.get('/data', (req, res) => res.json({ token: 'secret-abc123' })); // Порт 8080: токен видно у Wireshark як відкритий текст http.createServer(app).listen(8080); // Порт 8443: токен зашифровано, Wireshark показує набір байтів https.createServer({ key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') }, app).listen(8443); ``` На порту 8080 кожен запит читається прямо в мережі. Wireshark покаже токен у вигляді звичайного JSON. На порту 8443 та сама відповідь, але зашифрована і без ключів сесії нечитабельна. ### Ключова різниця HTTP передає все відкритим текстом. Проксі, роутер у кав'ярні, будь-хто між клієнтом і сервером може прочитати паролі, сесійні токени і API-ключі. HTTPS загортає це в TLS: ECDHE узгоджує спільний секрет, а потім дані шифруються через AES-256-GCM. Сервер також підтверджує свою особу через сертифікат X.509, підписаний довіреним CA, тому клієнт знає, що спілкується зі справжнім сервером, а не самозванцем. ### Коли використовувати - Форми логіну, оплата, будь-який API з токенами: HTTPS без варіантів - Публічні сторінки без даних користувача: HTTPS все одно, бо Google знижує рейтинг HTTP-сайтів і HSTS не дозволить повернутись після переходу - Статичні ресурси (зображення, CSS, JS): HTTPS, бо mixed content на HTTPS-сторінці браузер блокує - Внутрішні інструменти: HTTPS із self-signed сертифікатом (браузер покаже попередження, але звичка зашифрованих з'єднань корисна) - localhost при розробці: HTTP нормально, браузери вважають localhost безпечним контекстом ### Порівняльна таблиця | Аспект | HTTP | HTTPS | |---|---|---| | Порт | 80 | 443 | | Шифрування | Немає (відкритий текст) | TLS 1.3, AES-256-GCM | | Автентифікація сервера | Немає | Сертифікат X.509 від CA (наприклад, Let's Encrypt) | | Ризики | Перехоплення, підміна даних, MITM | Мінімізовані (cert pinning додає ще один шар) | | Продуктивність | Без overhead handshake | +10-50мс на handshake; з TLS 1.3 майже непомітно | | Де використовувати | Тільки локальна розробка | Весь production-трафік | ### Як працює TLS handshake Браузер відкриває TCP-з'єднання до порту 443 і надсилає ClientHello зі списком підтримуваних шифрів і ключем. Сервер відповідає ServerHello, ланцюжком сертифікатів і підписаним ключем. Браузер перевіряє сертифікат через свій CA trust store (Chrome використовує BoringSSL, Node.js - OpenSSL) і обчислює спільний секрет через ECDHE. Після цього весь HTTP-трафік йде зашифрованим симетричними ключами. TLS 1.3 робить це за один round trip. TLS 1.2 потребував двох. Різниця відчутна на з'єднаннях із великою затримкою. Бачив внутрішні сервіси без TLS через «закриту мережу». За три тижні скомпрометований ноутбук розробника став тією самою «внутрішньою мережею». HTTPS усередині периметра не параноя, а дешева страховка. ### Типові помилки **Production API на HTTP.** Токени і куки йдуть відкритим текстом. Wireshark у тій самій Wi-Fi мережі перехоплює їх за секунду. Рішення: ```javascript // Express: редирект з HTTP на HTTPS app.set('trust proxy', 1); app.use((req, res, next) => { if (!req.secure) { return res.redirect(301, `https://${req.headers.host}${req.url}`); } next(); }); ``` **Ігнорування терміну дії сертифіката.** Сертифікати Let's Encrypt закінчуються кожні 90 днів. Браузер показує жорсткий блок, а не попередження. Треба автоматизувати оновлення: ```bash certbot renew --dry-run # Додай у cron: 0 3 * * * certbot renew --quiet ``` **Self-signed сертифікат у production.** Браузер показує `NET::ERR_CERT_AUTHORITY_INVALID` і більшість користувачів просто йде. Self-signed підходить для локальної розробки. У production потрібен сертифікат від CA. Let's Encrypt безкоштовний. **Відсутність заголовка HSTS.** Без нього запит до `example.com` спочатку іде через HTTP, даючи зловмиснику вікно для downgrade-атаки. Рішення: ```javascript res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); ``` **HTTP/2 без TLS.** Браузери вимагають TLS для HTTP/2 згідно з RFC 7540. Якщо увімкнути HTTP/2 в Nginx без сертифіката, браузери повернуться до HTTP/1.1 без жодного повідомлення. ### Де зустрічається на практиці - Express: `https.createServer({ key, cert }, app)` для будь-якого API з даними користувачів - Next.js: HSTS через секцію `headers` у `next.config.js` зі значенням `max-age=63072000` - Nginx: `ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;` з Certbot для авторенью - Stripe: реєструє тільки HTTPS-адреси для вебхуків, HTTP відхиляє одразу при реєстрації - AWS ALB: редирект 301 через listener rule на порту 80 ### Питання для поглиблення **Q:** Що змінилося між TLS 1.2 і TLS 1.3? **A:** TLS 1.3 скоротив handshake з 2 round trip до 1, прибрав RSA key exchange (без forward secrecy) і зробив AEAD-шифри обов'язковими. Менше варіантів узгодження означає меншу поверхню для атак. **Q:** Що таке forward secrecy і навіщо вона потрібна? **A:** ECDHE генерує нову пару ключів для кожної сесії. Якщо зловмисник записав зашифрований трафік сьогодні і пізніше вкрав приватний ключ сервера, він все одно не розшифрує старі сесії. TLS 1.2 з RSA key exchange такого захисту не дає. **Q:** Як насправді відбувається перевірка сертифіката? **A:** Браузер перевіряє підпис сертифіката публічним ключем CA, проходить ланцюжок до кореневого CA в trust store, перевіряє дату закінчення дії і звертається до OCSP або CRL, щоб переконатись що сертифікат не відкликано. **Q:** Що таке HSTS preloading? **A:** Браузери містять вбудований список доменів із hstspreload.org, для яких HTTPS примусовий ще до першого запиту. Google вимагає `max-age` не менше одного року та `includeSubDomains` перед прийняттям заявки. Видалення займає місяці і цикли релізів браузерів. Після додавання швидкого відступу немає. **Q:** Що таке mixed content? **A:** Ситуація, коли HTTPS-сторінка завантажує ресурс через HTTP. Скрипти та iframe браузер блокує. Зображення показує з попередженням. Рішення: відносні URL або HTTPS для всіх ресурсів. ## Приклади ### Базовий: HTTP vs HTTPS сервер на Node.js ```javascript const express = require('express'); const https = require('https'); const http = require('http'); const fs = require('fs'); const app = express(); app.get('/profile', (req, res) => { res.json({ user: 'alice', token: 'secret-abc123' }); }); // Порт 8080: токен видно у Wireshark як відкритий текст http.createServer(app).listen(8080, () => { console.log('HTTP на 8080 - трафік перехоплюється'); }); // Порт 8443: токен зашифровано, Wireshark показує тільки байти https.createServer({ key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') }, app).listen(8443, () => { console.log('HTTPS на 8443 - зашифровано'); }); ``` `curl http://localhost:8080/profile` повертає токен як звичайний JSON. На спільній мережі його може перехопити будь-хто. На порту 8443 та сама відповідь, але зашифрована наскрізь. ### Середній рівень: HTTPS редирект і Stripe webhook ```javascript const express = require('express'); const https = require('https'); const http = require('http'); const fs = require('fs'); const stripe = require('stripe')('sk_test_...'); const app = express(); app.use(express.raw({ type: 'application/json' })); // Redirect усього HTTP трафіку на HTTPS http.createServer((req, res) => { res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` }); res.end(); }).listen(80); // Stripe приймає тільки HTTPS для вебхуків app.post('/webhook', (req, res) => { const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent(req.body, sig, 'whsec_...'); res.json({ received: true }); } catch (err) { res.status(400).send(`Webhook error: ${err.message}`); } }); https.createServer({ key: fs.readFileSync('privkey.pem'), cert: fs.readFileSync('fullchain.pem') }, app).listen(443); ``` Stripe перевіряє заголовок `stripe-signature` на кожному вебхуку. По HTTP підпис можна зняти і замінити. HTTPS це унеможливлює. Stripe також відхиляє HTTP-адресу ще при реєстрації. ### Просунутий рівень: HSTS і mixed content у браузері ```javascript // Express: HSTS заголовок на кожну відповідь app.use((req, res, next) => { res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); next(); }); // React: fetch через HTTPS (HSTS блокує HTTP для preloaded доменів) async function fetchUserData(userId) { const response = await fetch(`https://api.example.com/users/${userId}`, { credentials: 'include', // куки тільки через TLS referrerPolicy: 'strict-origin-when-cross-origin' }); if (!response.ok) throw new Error('Fetch failed'); return response.json(); } // Mixed content: Chrome це блокує const img = new Image(); img.src = 'http://insecure.com/photo.jpg'; // Заблоковано console.log('Blocked: mixed content policy'); ``` HSTS заголовок говорить браузеру не використовувати HTTP для цього origin до одного року. Прапорець `preload` відправляє домен до глобального списку, вбудованого в Chrome. Заблокований ресурс видно у DevTools, вкладка Network, статус `(blocked:mixed-content)`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.