Як працює HTTPS та відмінності від HTTP
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
Швидкий приклад
// 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 мережі перехоплює їх за секунду. Рішення:
// 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 днів. Браузер показує жорсткий блок, а не попередження. Треба автоматизувати оновлення:
certbot renew --dry-run
# Додай у cron: 0 3 * * * certbot renew --quietSelf-signed сертифікат у production. Браузер показує NET::ERR_CERT_AUTHORITY_INVALID і більшість користувачів просто йде. Self-signed підходить для локальної розробки. У production потрібен сертифікат від CA. Let's Encrypt безкоштовний.
Відсутність заголовка HSTS. Без нього запит до example.com спочатку іде через HTTP, даючи зловмиснику вікно для downgrade-атаки. Рішення:
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
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
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 у браузері
// 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).
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.