Certyfikaty i szyfrowanie
Po tej lekcji: kursant rozumie, jak działa HTTPS i certyfikat, sprawdza certyfikat dowolnej strony przez openssl s_client i openssl x509 -noout -dates, robi klucz i CSR, wie, co robi certbot, szyfruje i podpisuje pliki gpg oraz sprawdza integralność plików przez sha256sum -c.
Wstęp
Poniedziałek, 8:03. Telefon: „Strona sklepu pokazuje wielkie czerwone ostrzeżenie, że połączenie nie jest prywatne!”. Klienci uciekają, szef stoi za plecami. Przyczyna? Certyfikat wygasł w niedzielę o północy. Ktoś rok temu wgrał go ręcznie i zapomniał, a kalendarz się nie pomylił.
To jedna z najczęstszych awarii w internecie i jedna z najłatwiejszych do uniknięcia. Dziś zobaczysz, co jest w środku tej kłódki, sprawdzisz dowolną stronę jedną komendą i nauczysz się szyfrować pliki tak, żeby nawet admin, który je skopiuje, nic z nich nie miał.
Dwa rodzaje kłódek
Zanim certyfikat, potrzebujemy dwóch rodzajów szyfrowania.
Szyfrowanie symetryczne to zwykła skrzynka na klucz. Ten sam klucz zamyka i otwiera. Szybkie jak błyskawica, ale jest problem: jak przekazać klucz komuś na drugim końcu świata, żeby nikt go po drodze nie skopiował?
Szyfrowanie asymetryczne to genialny trik. Masz parę: kłódkę i kluczyk. Rozdajesz otwarte kłódki każdemu, kto chce, nawet wiesząc je na słupach ogłoszeniowych. Każdy może zatrzasnąć taką kłódkę na skrzynce. Otworzyć możesz ją tylko Ty swoim kluczykiem. Otwarta kłódka to klucz publiczny, kluczyk w kieszeni to klucz prywatny. Znasz ten układ z SSH. To dokładnie ta sama matematyka.
I działa też w drugą stronę: czymś, co zrobisz kluczem prywatnym, można sprawdzić kluczem publicznym, że to Ty. To jest podpis cyfrowy. Jak pieczątka, którą masz tylko Ty, ale odcisk każdy może porównać z wzorem.
Jak działa HTTPS
Wchodzisz na stronę banku. Skąd wiesz, że rozmawiasz z bankiem, a nie z kimś, kto podszył się pod niego w kawiarnianym Wi-Fi?
Bank pokazuje certyfikat, czyli dowód osobisty strony. Jest w nim nazwa (bank.pl), klucz publiczny banku, daty ważności i pieczątka urzędu, który ten dowód wydał. Ten urząd to CA, Certificate Authority, urząd certyfikacji. Twoja przeglądarka i system mają wbudowaną listę urzędów, którym ufają. Na Linuksie leży w /etc/ssl/certs [Ubuntu] albo /etc/pki/tls/certs [RHEL].
Połączenie wygląda tak:
- Przeglądarka: „Cześć, chcę rozmawiać z
bank.pl.” - Serwer wysyła certyfikat, a często też certyfikat urzędu pośredniego. To łańcuch: centrala, oddział wojewódzki, okienko.
- Przeglądarka sprawdza: pieczątka prawdziwa? Urząd na mojej liście? Nazwa się zgadza? Data ważności nie minęła?
- Strony wymieniają się sekretem tak, że podsłuchujący go nie pozna, a serwer udowadnia, że ma klucz prywatny pasujący do certyfikatu.
- Z tego sekretu powstaje zwykły klucz symetryczny i dalej rozmowa idzie szybko.
Asymetria do przedstawienia się, symetria do rozmowy. Wszystko w ułamku sekundy, zanim zdążysz mrugnąć. Ten protokół to TLS. Stara nazwa SSL wciąż krąży w nazwach programów i w języku handlowców, ale stare wersje SSL są od dawna wyłączone.
openssl s_client: zajrzyj w kłódkę
openssl to scyzoryk do kryptografii. s_client udaje przeglądarkę i pokazuje wszystko, co się dzieje:
openssl s_client -connect google.com:443 -servername google.com </dev/null-servername mówi serwerowi, o którą nazwę pytasz. Na jednym adresie IP może siedzieć sto stron i każda ma swój certyfikat. </dev/null zamyka rozmowę od razu, inaczej openssl czeka, aż coś napiszesz.
Do pokazu postawiłem lokalnie własny mały „urząd” i serwer sklep.lab. Dla prawdziwej strony wygląda to tak samo, tylko nazwy są inne:
depth=1 CN = Urzad Certyfikacji Chmuroholika
depth=0 CN = sklep.lab
...
subject=CN = sklep.lab
issuer=CN = Urzad Certyfikacji Chmuroholika
...
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Verify return code: 0 (ok)depth to poziomy łańcucha, subject to „czyj to dowód”, issuer to „kto go wydał”. TLSv1.3 to wersja protokołu. I najważniejsza linijka na końcu: Verify return code: 0 (ok). Zero znaczy, że wszystko się zgadza. Kod wyjścia jak kciuk w górę.
A teraz sedno dla admina: do kiedy certyfikat jest ważny? Przepuszczamy go rurą do drugiej komendy:
openssl s_client -connect google.com:443 -servername google.com </dev/null 2>/dev/null \
| openssl x509 -noout -dates -subject -issuernotBefore=Nov 3 21:08:58 2026 GMT
notAfter=Feb 1 21:08:58 2027 GMT
subject=CN = sklep.lab
(wynik z mojego lokalnego sklep.lab; dla google.com zobaczysz jego daty i nazwę)x509 to nazwa formatu certyfikatów. -noout znaczy „nie wypisuj całego certyfikatu w kodowaniu, tylko to, o co pytam”. notAfter to data, którą wpisujesz do kalendarza. Albo lepiej, do skryptu:
openssl x509 -in sklep.crt -noout -checkend 1209600 && echo OK || echo 'Wygasa w ciągu 14 dni!'Certificate will not expire
OK-checkend dostaje liczbę sekund (14 dni to 1209600) i kończy się kodem 0, jeśli certyfikat do tego czasu nie wygaśnie, albo 1, jeśli wygaśnie. Idealne dla if w skrypcie. Wrócimy do tego w projekcie z monitorem.
Pełny certyfikat po ludzku: openssl x509 -in plik.crt -noout -text. Nazwy, dla których jest ważny, są w polu Subject Alternative Name:
openssl x509 -in sklep.crt -noout -ext subjectAltNameX509v3 Subject Alternative Name:
DNS:sklep.lab, DNS:www.sklep.labKlucz i CSR: wniosek o dowód
Żeby dostać certyfikat, składasz wniosek. Robisz parę kluczy, a z klucza publicznego i nazwy strony robisz CSR, Certificate Signing Request, wniosek o podpisanie certyfikatu. Klucz prywatny zostaje u Ciebie. Zawsze. Do urzędu idzie tylko wniosek.
openssl req -new -newkey rsa:2048 -nodes -keyout sklep.key -out sklep.csr \
-subj "/CN=sklep.lab" -addext "subjectAltName=DNS:sklep.lab,DNS:www.sklep.lab"
openssl req -in sklep.csr -noout -subject -verify
ls -l sklep.*Certificate request self-signature verify OK
subject=CN = sklep.lab
-rw-r--r-- 1 root root 964 ... sklep.csr
-rw------- 1 root root 1704 ... sklep.key-nodes to „no DES”, czyli nie szyfruj klucza hasłem. Serwer WWW musi go przeczytać sam przy starcie, bez nikogo, kto wpisze hasło o trzeciej w nocy. Za to plik z kluczem ma uprawnienia 600. Popatrz, openssl sam je ustawił.
Do testów możesz zrobić certyfikat samopodpisany, czyli dowód, który wystawiłeś sam sobie:
openssl req -x509 -newkey rsa:2048 -nodes -keyout test.key -out test.crt -days 30 \
-subj "/CN=test.lab" -addext "subjectAltName=DNS:test.lab"Działa, szyfruje, ale przeglądarka wyświetli ostrzeżenie. Słusznie. To jak legitymacja szkolna wydrukowana w domu: technicznie dokument, ale bramkarz w klubie się nie nabierze.
I sztuczka, która ratuje godziny: czy ten klucz pasuje do tego certyfikatu? Porównaj odciski klucza publicznego:
openssl x509 -in sklep.crt -noout -pubkey | sha256sum
openssl pkey -in sklep.key -pubout | sha256sumafd3ae8d755860db696291e9f013f4720d33b4604235590b44f9d3bfae56f3da -
afd3ae8d755860db696291e9f013f4720d33b4604235590b44f9d3bfae56f3da -Te same sumy, para pasuje. Różne, to Apache nie wstanie i będzie miał rację.
certbot: darmowy urząd, który nie zapomina
Kiedyś certyfikat kosztował pieniądze i załatwiało się go mailami. Dziś jest Let's Encrypt: darmowy urząd certyfikacji, który wydaje certyfikaty automatycznie. Haczyk: są krótkie. Teraz 90 dni. Let's Encrypt zapowiedział skracanie: od 10 lutego 2027 domyślnie 64 dni, a od 16 lutego 2028 już 45 dni. To celowe. Krótki certyfikat wymusza automat, a automat nie zapomina jak człowiek z naszego haka.
Automatem jest certbot. Udowadnia urzędowi, że domena jest Twoja (kładzie plik na Twoim serwerze WWW, a urząd sprawdza, czy go widzi), odbiera certyfikat i sam go odnawia.
sudo apt install certbot python3-certbot-nginx # [Ubuntu]
sudo dnf install epel-release && sudo dnf install certbot # [RHEL], z EPEL
sudo certbot --nginx -d sklep.pl -d www.sklep.pl # zdobądź i wpisz do konfiguracji nginx
sudo certbot certonly --webroot -w /var/www/html -d sklep.pl # tylko zdobądź
sudo certbot certificates # co mam i do kiedy
sudo certbot renew --dry-run # próba generalna odnowieniaCertyfikaty lądują w /etc/letsencrypt/live/sklep.pl/: fullchain.pem to certyfikat z łańcuchem, privkey.pem to klucz prywatny. Odnawianie: [Ubuntu] pakiet zakłada timer systemd certbot.timer, który odpala się dwa razy dziennie i odnawia to, co jest blisko końca. Sprawdź systemctl list-timers | grep certbot. Pełną konfigurację z Apache zrobimy w lekcji 90. Certbot potrzebuje prawdziwej domeny i otwartego portu 80, więc na domowej maszynie wirtualnej zostaje Ci --dry-run i sucha teoria.
gpg: szyfrowanie i podpisy plików
HTTPS chroni rozmowę. A plik, który leży na dysku albo jedzie mailem? Do tego jest gpg, GNU Privacy Guard.
Najprościej: szyfrowanie hasłem, czyli symetryczne.
gpg -c tajne.txt
file tajne.txt.gpg
gpg -d tajne.txt.gpgtajne.txt.gpg: PGP symmetric key encrypted data - AES with 256-bit key salted & iterated - SHA512 .
gpg: AES256.CFB encrypted data
gpg: encrypted with 1 passphrase
Hasło do Wi-Fi w biurze: Kotlet123-c pyta o hasło i robi plik .gpg. -d odszyfrowuje na ekran (albo do pliku z -o). Oryginał zostaje, więc jak chcesz go się pozbyć, usuń go sam. Świetne do kopii zapasowych w chmurze: dostawca przechowuje Twoje pliki, ale ich nie przeczyta.
Wersja z parą kluczy, czyli asymetryczna:
gpg --quick-generate-key 'Eryk Admin <eryk@chmuroholik.lab>' default default 1y
gpg --list-keys
gpg --armor --export eryk@chmuroholik.lab > eryk.pub.ascpub ed25519 2026-11-03 [SC] [expires: 2027-11-03]
614E77AB146ED65EC93E984686D7846DCE624371
uid [ultimate] Eryk Admin <eryk@chmuroholik.lab>
sub cv25519 2026-11-03 [E]eryk.pub.asc to otwarta kłódka. Wysyłasz ją komu chcesz. Ktoś ją importuje (gpg --import eryk.pub.asc) i szyfruje plik dla Ciebie:
gpg -e -r eryk@chmuroholik.lab -o dla_eryka.gpg tajne.txt
gpg -d dla_eryka.gpggpg: encrypted with cv25519 key, ID 719AFB6C691DFEEF, created 2026-11-03
"Eryk Admin <eryk@chmuroholik.lab>"
Hasło do Wi-Fi w biurze: Kotlet123I podpis. Skrypt, który rozsyłasz na serwery, podpisujesz, żeby nikt go po drodze nie podmienił:
gpg --armor --detach-sign skrypt.sh
gpg --verify skrypt.sh.asc skrypt.sh
echo 'rm -rf /' >> skrypt.sh # ktoś „poprawia” skrypt (tylko pokaz, nie uruchamiaj!)
gpg --verify skrypt.sh.asc skrypt.shgpg: Good signature from "Eryk Admin <eryk@chmuroholik.lab>" [ultimate]
...
gpg: BAD signature from "Eryk Admin <eryk@chmuroholik.lab>" [ultimate]BAD signature i kod wyjścia 1. W skrypcie: nie uruchamiaj. To dokładnie ten mechanizm, którym apt i dnf sprawdzają pakiety z repozytorium, o czym było w module o pakietach.
sha256sum: odcisk palca pliku
Czasem nie chodzi o tajemnicę, tylko o pewność, że plik jest nienaruszony. Pobrałeś obraz ISO, skopiowałeś backup na inny serwer. Czy dojechał cały?
Funkcja skrótu, po angielsku hash, robi z pliku dowolnej wielkości krótki odcisk palca. Ten sam plik zawsze daje ten sam odcisk. Najmniejsza zmiana daje zupełnie inny:
echo 'Ala ma kota' > a.txt
echo 'Ala ma kota.' > b.txt
sha256sum a.txt b.txtc51bc001db0206126e1681ba88497ce583f077a92e427e4f62da96b691d28813 a.txt
10abd7ad6526ff8c94fcf94ad0e5f4209274254a770deee2e1567139ddd28fac b.txtJedna kropka i ani jeden znak się nie zgadza. A z odcisku nie da się odtworzyć pliku, tak jak z odcisku palca nie odtworzysz człowieka.
Praktyczny sposób pracy: zapisujesz sumy do pliku, a potem sprawdzasz:
sha256sum backup.tar.gz konfig.conf > SHA256SUMS
sha256sum -c SHA256SUMS
echo 'zmiana' >> konfig.conf
sha256sum -c SHA256SUMS; echo "kod: $?"backup.tar.gz: OK
konfig.conf: OK
...
backup.tar.gz: OK
konfig.conf: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match
kod: 1Tak samo sprawdzasz pobrany obraz Ubuntu albo Rocky: obok obrazu leży plik SHA256SUMS (u Rocky CHECKSUM). Robisz sha256sum -c SHA256SUMS --ignore-missing, żeby nie narzekał na obrazy, których nie pobrałeś. A sam plik z sumami jest podpisany gpg. Suma mówi „plik jest taki jak na liście”, podpis mówi „listę napisał ten, kto trzeba”.
Jak to działa pod spodem
Certyfikat to plik w formacie X.509. Na dysku zwykle w kodowaniu PEM, czyli tekst między linijkami -----BEGIN CERTIFICATE----- i -----END CERTIFICATE-----. W środku: nazwa, klucz publiczny, daty, rozszerzenia i podpis wystawcy. Podpis to skrót całej treści zaszyfrowany kluczem prywatnym urzędu. Przeglądarka liczy skrót sama i sprawdza podpis kluczem publicznym urzędu. Zmień jedną literę w nazwie i podpis przestaje pasować.
Łańcuch działa schodkami: certyfikat strony podpisał urząd pośredni, pośredni podpisał główny, a główny jest na liście zaufanych w systemie. Dlatego serwer musi wysłać nie tylko swój certyfikat, ale i pośredni. Głównego nie wysyła, bo klient i tak ma go u siebie.
A klucz prywatny? Nie opuszcza serwera. Podczas połączenia TLS 1.3 serwer tylko podpisuje nim fragment rozmowy, a klient sprawdza podpis kluczem z certyfikatu. Sam sekret sesji powstaje z wymiany kluczy jednorazowych. Dzięki temu nawet ktoś, kto kiedyś ukradnie klucz prywatny, nie odszyfruje starych, nagranych rozmów.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
openssl s_client | -connect HOST:443 -servername HOST | łączy się jak przeglądarka, pokazuje łańcuch |
openssl s_client | -CAfile PLIK | ufaj temu urzędowi (np. własnemu) |
openssl x509 | -noout -dates | daty ważności |
openssl x509 | -noout -subject -issuer | czyj i kto wydał |
openssl x509 | -noout -text | cały certyfikat po ludzku |
openssl x509 | -checkend SEKUNDY | kod 1, jeśli wygaśnie w tym czasie |
openssl req | -new -newkey rsa:2048 -nodes -keyout -out | klucz i CSR |
openssl req | -x509 -days N | certyfikat samopodpisany |
openssl verify | -CAfile CA -untrusted POŚREDNI | sprawdza łańcuch pliku |
certbot | --nginx / --apache / certonly --webroot | zdobywa certyfikat |
certbot | renew --dry-run / certificates | test odnowienia / lista |
gpg | -c / -d | szyfruj hasłem / odszyfruj |
gpg | -e -r ODBIORCA | szyfruj kluczem publicznym |
gpg | --detach-sign / --verify | podpisz / sprawdź podpis |
gpg | --import / --armor --export | wczytaj / wypisz klucz publiczny |
sha256sum | -c PLIK | sprawdza sumy z listy |
sha256sum | --ignore-missing | pomija pliki, których nie masz |
⚠ Typowy błąd
„W przeglądarce kłódka jest, a curl i skrypty krzyczą o błędzie certyfikatu.”
curl: (60) SSL certificate problem: unable to get local issuer certificateA s_client mówi:
verify error:num=20:unable to get local issuer certificate
verify error:num=21:unable to verify the first certificate
Verify return code: 21 (unable to verify the first certificate)Przyczyna: serwer wysyła tylko swój certyfikat, bez pośredniego. Ktoś wskazał w konfiguracji cert.pem zamiast fullchain.pem. Przeglądarki często mają pośredni w pamięci z innych stron albo same go dociągają, więc u Ciebie działa. Curl, skrypty, aplikacje mobilne nie. Odtworzyłem to lokalnie: ten sam certyfikat bez pośredniego daje kod 21, a z pośrednim:
0 s:CN = sklep.lab
i:CN = Posrednik Chmuroholika
1 s:CN = Posrednik Chmuroholika
i:CN = Urzad Certyfikacji Chmuroholika
Verify return code: 0 (ok)Dwa certyfikaty w łańcuchu, kod zero. Naprawa: w konfiguracji serwera WWW zawsze fullchain.pem. Inne kody, które zobaczysz: 62 (hostname mismatch), czyli certyfikat na inną nazwę, i 10 (certificate has expired), czyli nasz poniedziałek o 8:03.
✎ Zadanie
- Zrób klucz i CSR dla nazwy
test.labz polem Subject Alternative Name i obejrzyj CSR komendąopenssl req -noout -text. - Zrób certyfikat samopodpisany na 30 dni. Sprawdź jego daty i użyj
-checkend, żeby sprawdzić, czy wygaśnie w ciągu 14 dni, a czy w ciągu 31. - Zaszyfruj dowolny plik gpg hasłem, usuń oryginał i odzyskaj go z wersji zaszyfrowanej.
- Zrób plik
SHA256SUMSdla trzech plików, zmień jeden z nich i pokaż, który wykryłsha256sum -c.
Pokaż rozwiązanie
Klucz i wniosek z polem SAN:
openssl req -new -newkey rsa:2048 -nodes -keyout test.key -out test.csr \
-subj "/CN=test.lab" -addext "subjectAltName=DNS:test.lab"
openssl req -in test.csr -noout -text | grep -E 'Subject:|DNS:|Public-Key' Subject: CN = test.lab
Public-Key: (2048 bit)
DNS:test.labCertyfikat samopodpisany na 30 dni i test z -checkend:
openssl req -x509 -newkey rsa:2048 -nodes -keyout self.key -out self.crt -days 30 \
-subj "/CN=test.lab" -addext "subjectAltName=DNS:test.lab"
openssl x509 -in self.crt -noout -subject -issuer -dates
openssl x509 -in self.crt -noout -checkend $((14*86400))
openssl x509 -in self.crt -noout -checkend $((31*86400)); echo "kod: $?"subject=CN = test.lab
issuer=CN = test.lab
notBefore=Nov 3 21:10:12 2026 GMT
notAfter=Dec 3 21:10:12 2026 GMT
Certificate will not expire
Certificate will expire
kod: 1subject i issuer są takie same, czyli sam sobie wystawiłeś dowód. W ciągu 14 dni nie wygaśnie, w ciągu 31 tak, więc kod 1. Zauważ $((14*86400)): arytmetyka Basha z modułu o skryptach, żeby nie liczyć sekund na kalkulatorze.
gpg i sumy:
gpg -c notatki.txt && rm notatki.txt
gpg -o notatki.txt -d notatki.txt.gpg
sha256sum a.txt b.txt c.txt > SHA256SUMS
echo zmiana >> b.txt
sha256sum -c SHA256SUMSa.txt: OK
b.txt: FAILED
c.txt: OK
sha256sum: WARNING: 1 computed checksum did NOT matchJeśli gpg nie zapytał o hasło przy odszyfrowaniu, to nie błąd. Agent gpg pamięta je przez kilka minut, jak kelner, który pamięta, że już pokazałeś dowód.
Co dalej
Certyfikat ma datę ważności. A skąd serwer wie, która jest godzina? Jak zegar się spieszy, certyfikaty wyglądają na nieważne, logi kłamią, a cron odpala zadania nie wtedy, kiedy trzeba. W następnej lekcji czas i język systemu: strefy czasowe, synchronizacja zegara przez chrony i ustawienia regionalne.