Wprowadzenie do certyfikatów i HTTPS#

Autor: Bartosz Naskręcki

Cel sesji: w przystępny sposób zrozumieć po co są certyfikaty, jak budują zaufanie w HTTPS i gdzie spotykamy je na co dzień.
Techniczne szczegóły ukryliśmy w sekcjach „Dla ciekawskich” — możesz je rozwinąć tylko wtedy, gdy będzie czas lub padnie pytanie.

Plan 60 minut#

Czas

Temat

Wstęp: dlaczego szyfrowanie i zaufanie?

10»

Podstawy: szyfrowanie symetryczne vs asymetryczne

15»

Certyfikaty X.509 i łańcuch zaufania

15»

HTTPS/TLS w praktyce: co się dzieje podczas połączenia

10»

Zastosowania + mini‑demo (podpis cyfrowy, analiza certyfikatu)

(Opcjonalnie) Przygotowanie środowiska#

Jeśli chcesz uruchomić ćwiczenia w Pythonie, potrzebny będzie pakiet cryptography.

W wierszu poleceń (lub w komórce notebooka) możesz wykonać:

pip install cryptography

Jeśli nie masz internetu lub nie chcesz instalować — nadal możesz prowadzić zajęcia, korzystając z samych slajdów/sekcji markdown.

# Szybki test środowiska (uruchom tę komórkę)
from importlib.util import find_spec
has_crypto = find_spec("cryptography") is not None
print("Pakiet 'cryptography' dostępny:" , has_crypto)
if not has_crypto:
    print("Aby uruchomić ćwiczenia kodowe, zainstaluj pakiet: pip install cryptography")

1) Dlaczego potrzebujemy szyfrowania i zaufania w sieci?#

  • Bez HTTPS dane płynęłyby jawnie (podsłuch, podmiana treści).

  • HTTPS zapewnia poufność, integralność i uwierzytelnienie serwera.

  • Wbudowane listy zaufanych Urzędów Certyfikacji (CA) w przeglądarkach pozwalają zweryfikować, że „to naprawdę ta strona”.

Dla ciekawskich: SSL vs TLS w jednym akapicie

Dziś mówimy o TLS (następca przestarzałego SSL). „HTTPS” to po prostu HTTP przez TLS. W praktyce nazwa „SSL” bywa używana potocznie na określenie całej warstwy szyfrowania.

2) Podstawy kryptografii — bardzo krótko#

  • Symetryczne: jeden wspólny sekret (szybkie; dobre do transmisji danych).

  • Asymetryczne: para klucz publiczny / prywatny (wolniejsze; świetne do wymiany sekretów i podpisów).

  • Podpis cyfrowy: nadawca podpisuje skrót wiadomości kluczem prywatnym; każdy może zweryfikować podpis jego kluczem publicznym.

Dla ciekawskich: intuicja RSA i podpisu

W RSA bezpieczeństwo opiera się o trudność faktoryzacji dużych liczb. Podpis polega na zaszyfrowaniu skrótu wiadomości kluczem prywatnym; odbiorca weryfikuje go kluczem publicznym. W HTTPS algorytmy asymetryczne służą głównie do ustalenia klucza sesyjnego, a sama treść leci już szybko dzięki szyfrowaniu symetrycznemu.

Mini‑demo: podpis i weryfikacja (RSA)#

Uruchom, jeśli masz zainstalowany cryptography.

# Podpis i weryfikacja RSA
try:
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives import hashes
    # generowanie kluczy
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    public_key = private_key.public_key()

    message = b"Hello, Cyber AMU!"

    # podpisanie
    signature = private_key.sign(
        message,
        padding.PKCS1v15(),
        hashes.SHA256()
    )

    # weryfikacja (rzuci wyjątek, jeśli niepoprawne)
    public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA256())
    print("Podpis zweryfikowany poprawnie!")
except Exception as e:
    print("Nie udało się wykonać demonstracji:", e)
Dla ciekawskich: krótki przykład szyfrowania symetrycznego (Fernet/AES)

Poniższe ćwiczenie pokaże, że ten sam klucz służy do zaszyfrowania i odszyfrowania danych.

# (Opcjonalnie) Szyfrowanie symetryczne z Fernet
try:
    from cryptography.fernet import Fernet
    key = Fernet.generate_key()
    f = Fernet(key)
    token = f.encrypt(b"tajny komunikat")
    plain = f.decrypt(token)
    print("Szyfrogram:", token[:32], "...")
    print("Odszyfrowano:", plain)
except Exception as e:
    print("Aby uruchomić ten przykład, zainstaluj 'cryptography' (pip install cryptography).")

3) Certyfikaty X.509 — co, po co i jak?#

  • X.509 wiąże tożsamość (np. nazwę domeny) z kluczem publicznym i jest podpisany przez CA.

  • Przeglądarka sprawdza ważność i łańcuch zaufania — od certyfikatu serwera aż do zaufanego „roota”.

  • Znajdziesz tam m.in. okres ważności, nazwy domen (SAN), dopuszczalne zastosowania klucza.

Ćwiczenie bez kodu (na żywo): otwórz dowolną stronę → ikona kłódki → „Certyfikat” → odczytaj Issuer, Validity, Subject Alternative Name.

Mini‑demo: odczyt pól z certyfikatu (wklej PEM)#

Skopiuj certyfikat w formacie PEM (np. z narzędzia openssl s_client -showcerts -connect example.com:443) i wklej go do zmiennej cert_pem niżej, a następnie uruchom dwie komórki.

# Wklej tu swój certyfikat PEM (włącznie z liniami -----BEGIN CERTIFICATE----- / -----END CERTIFICATE-----)
cert_pem = """
-----BEGIN CERTIFICATE-----
(Wklej tu certyfikat)
-----END CERTIFICATE-----
"""
# Odczyt podstawowych informacji z certyfikatu
try:
    from cryptography import x509
    from cryptography.x509.oid import ExtensionOID
    import textwrap

    if "BEGIN CERTIFICATE" not in cert_pem:
        raise ValueError("Nie wklejono certyfikatu PEM do zmiennej 'cert_pem'.")

    cert = x509.load_pem_x509_certificate(cert_pem.encode())

    print("Temat (Subject):", cert.subject.rfc4514_string())
    print("Wystawca (Issuer):", cert.issuer.rfc4514_string())
    print("Ważny od:", cert.not_valid_before)
    print("Ważny do:", cert.not_valid_after)
    try:
        san = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value
        dns = san.get_values_for_type(x509.DNSName)
        print("SAN (DNS):", ", ".join(dns) if dns else "—")
    except x509.ExtensionNotFound:
        print("SAN: —")
    print("Algorytm podpisu:", getattr(cert.signature_algorithm_oid, "_name", cert.signature_algorithm_oid.dotted_string))
except Exception as e:
    print("Nie udało się sparsować certyfikatu:", e)

4) HTTPS/TLS — co naprawdę się dzieje?#

  1. Klient mówi „Cześć” i podaje listę obsługiwanych wersji/szyfrów.

  2. Serwer wybiera parametry i przesyła certyfikat.

  3. Klient weryfikuje certyfikat i uzgadnia tajny klucz sesyjny.

  4. Od tej chwili dane lecą szyfrowane symetrycznie — szybko i bezpiecznie.

Dla ciekawskich: TLS 1.3 vs 1.2 w pigułce

W TLS 1.3 uproszczono „handshake”, wymuszono krzywe (EC)DHE i usunięto słabe szyfry. Zyskujemy krótki i nowoczesny start sesji oraz tzw. Perfect Forward Secrecy.

Mini‑demo: wspólny sekret (ECDH → klucz sesyjny)#

# Wspólny sekret z ECDH i wyprowadzenie klucza sesyjnego
try:
    from cryptography.hazmat.primitives.asymmetric import ec
    from cryptography.hazmat.primitives.kdf.hkdf import HKDF
    from cryptography.hazmat.primitives import hashes

    # Klient i serwer generują swoje pary kluczy
    sk_client = ec.generate_private_key(ec.SECP256R1())
    sk_server = ec.generate_private_key(ec.SECP256R1())

    # Wymiana kluczy publicznych -> obie strony liczą tajny wspólny sekret
    shared_client = sk_client.exchange(ec.ECDH(), sk_server.public_key())
    shared_server = sk_server.exchange(ec.ECDH(), sk_client.public_key())
    assert shared_client == shared_server

    # Z tajnego sekretu wyprowadzamy 32‑bajtowy klucz sesyjny (HKDF+SHA256)
    session_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b"tls-handshake").derive(shared_client)
    print("Ustalony klucz sesyjny (hex, początek):", session_key.hex()[:32], "...")
except Exception as e:
    print("Aby uruchomić ten przykład, zainstaluj 'cryptography'. Błąd:", e)

5) Sprzętowe klucze i karty: PIV / YubiKey (opcjonalnie)#

  • PIV: standard kart tożsamości (z certyfikatami, kluczami i PIN‑em) — przykład silnego, wieloskładnikowego uwierzytelniania.

  • YubiKey (interfejs PIV): działa jak karta inteligentna; klucz prywatny nigdy nie opuszcza urządzenia.

Dla ciekawskich: do czego to się przydaje?

Podpisywanie i odszyfrowywanie (RSA/ECC) po stronie sprzętu, integracja z PKCS#11, użycie do logowania, S/MIME, SSH, podpisywania kodu itp.

6) C2PA i „Content Credentials” — skąd wiesz, że zdjęcie jest prawdziwe?#

C2PA to otwarty standard, który dodaje do treści wiarygodne metadane o pochodzeniu i edycjach.
Dzięki podpisom kryptograficznym możesz sprawdzić „kto i czym dotykał” materiał — pomocne w walce z dezinformacją.

Mini‑demonstracje „na żywo” (bez instalacji)#

  1. Przeglądarka → kłódka → Certyfikat: pokaż Issuer, Validity, SAN.

  2. Porównanie stron: z certyfikatem „zielonym” (ważny) i przeterminowanym (ostrzeżenie).

  3. Pytanie do sali: dlaczego nie widzimy już kłódki EV (Extended Validation)? Czy to zmieniło użyteczność?

Jeśli masz terminal i OpenSSL:
openssl s_client -showcerts -connect example.com:443 | openssl x509 -noout -issuer -subject -dates -ext subjectAltName

Ćwiczenia dla uczestników (proste)#

  • Znajdź w certyfikacie ulubionej strony Issuer i Subject.

  • Sprawdź, czy data ważności „Not After” jest przyszła.

  • Oszacuj, do czego mógłbyś użyć klucza sprzętowego (PIV/YubiKey) w swojej pracy.

Dla ciekawskich: samodzielne utworzenie self‑signed certyfikatu w Pythonie

Poniższa komórka tworzy parę kluczy i lokalny certyfikat „self‑signed” z nazwą CN=example.com.

# (Opcjonalnie) generowanie self-signed cert w Pythonie
try:
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography import x509
    from cryptography.x509.oid import NameOID
    from datetime import datetime, timedelta

    # klucz
    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"PL"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Demo"),
        x509.NameAttribute(NameOID.COMMON_NAME, u"example.com"),
    ])

    cert = (
        x509.CertificateBuilder()
        .subject_name(subject)
        .issuer_name(issuer)
        .public_key(key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.utcnow() - timedelta(minutes=1))
        .not_valid_after(datetime.utcnow() + timedelta(days=30))
        .add_extension(x509.SubjectAlternativeName([x509.DNSName(u"example.com")]), critical=False)
        .sign(private_key=key, algorithm=hashes.SHA256())
    )

    pem_key = key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption()
    )
    pem_cert = cert.public_bytes(serialization.Encoding.PEM)

    # Zapis do plików roboczych
    from pathlib import Path
    Path("demo_key.pem").write_bytes(pem_key)
    Path("demo_cert.pem").write_bytes(pem_cert)

    print("Zapisano: demo_key.pem oraz demo_cert.pem")
    print(pem_cert.decode()[:200], "...")
except Exception as e:
    print("Aby uruchomić ten przykład, zainstaluj 'cryptography'. Błąd:", e)

Notatki dla prowadzącego#

  • Główna narracja: „zaufanie i prosty model mentalny” zamiast matematyki.

  • Przeplataj opowieść krótkimi pytaniami do grupy („co się stanie, jeśli…?”).

  • Jeśli coś nie zadziała — przejdź do sekcji bez kodu; cały materiał jest autonomiczny.

FAQ (podchwytliwe pytania)
  • Czy certyfikat „szyfruje” stronę? — Nie. Certyfikat uwierzytelnia serwer i pozwala uzgodnić klucz; szyfrowanie robi TLS i szyfry symetryczne.

  • Czy EV daje więcej bezpieczeństwa? — Weryfikuje organizację, ale praktycznie nie wpływa na UI/UX przeglądarek; większość użytkowników i tak patrzy na kłódkę/domenę.

  • Po co PIV/YubiKey? — Chronią klucz prywatny sprzętowo; idealne do podpisów, logowania, SSH, S/MIME.

Linki i materiały do dalszej nauki#

  • Dokumentacja cryptography: https://cryptography.io/

  • Przykładowe polecenia OpenSSL: man x509, man s_client

  • Standard X.509 (wprowadzenie) i TLS — materiały popularnonaukowe znajdziesz w sieci oraz dokumentacji przeglądarek.