From 098b973d1945dac06fa1c7d42a5a3906cf1d58ed Mon Sep 17 00:00:00 2001 From: Lukas Batelka Date: Mon, 8 Sep 2025 22:16:53 +0200 Subject: [PATCH] ukladani a nacitani certifikatu + doplneni readme --- README.md | 148 ++++++++++++++++++++++++++++++++++++++++-------------- main.go | 90 +++++++++++++++++++++++++++------ 2 files changed, 187 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 6e452a9..2fc4e62 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,45 @@ -## Šifrovací appka +## Co to je -Mini Go/HTMX app na šifrování zpráv cizím veřejným klíčem. +Mini Go/HTMX aplikace pro šifrování zpráv cizím veřejným klíčem. -* Při startu vygeneruje/načte RSA identitu (2048 bit). +* Při startu vygeneruje nebo načte RSA identitu (2048 bitů). * Umožní sdílet veřejný klíč (`public.pem`) a self-signed cert (`identity.crt`). -* Šifruje hybridně: **RSA-OAEP(SHA-256) + AES-256-GCM**. -* UI je čisté přes **HTMX**, tlačítka „Copy“ všude, kde to dává smysl. +* Šifruje hybridně: **RSA-OAEP (SHA-256) + AES-256-GCM**. +* Rozhraní přes **HTMX**, moderní responzivní layout (2 sloupce na desktopu, 1 sloupec na mobilu). +* Všude jsou tlačítka **Copy** a **Clear** pro snadnou práci s texty. --- ## Požadavky * Go 1.21+ -* (volitelné) Make, Docker, atd. – není potřeba pro základní běh +* Není potřeba databáze ani další služby --- ## Rychlý start ```bash -# stačí spustit +# spustí server go run . # otevři v prohlížeči http://localhost:8080/ ``` -Na prvním startu se vytvoří soubory: +Na prvním startu se vytvoří: ``` identity_key.pem # RSA private key (PKCS#1) public.pem # veřejný klíč PEM (PKIX) -identity.crt # self-signed cert s veřejným klíčem (jen „vizitka“) +identity.crt # self-signed cert (vizitka pro sdílení) ``` --- ## Regenerace klíčů -Chceš novou identitu? +Pokud chceš vygenerovat novou identitu: ```bash # přes flag @@ -53,10 +54,13 @@ REGEN_KEYS=1 go run . ## Jak to používat (UI) 1. Otevři `http://localhost:8080/`. -2. V sekci **Můj veřejný klíč** stáhni/kopíruj `public.pem` nebo `identity.crt` a pošli kontaktům. -3. V sekci **Šifrovat pro cizí klíč** vlož svou zprávu a **cizí** public key/cert → klikni **Encrypt**. +2. V sekci **Můj veřejný klíč** stáhni/kopíruj `public.pem` nebo `identity.crt` a pošli je kontaktům. +3. V sekci **Šifrovat pro cizí klíč**: - * Výstup (**Zašifrovaný payload**) se ukáže **hned pod formulářem** a má tvar JSON: + * napiš zprávu, + * vlož **cizí** public key nebo cert (PEM blok), + * klikni **Encrypt**. + * Výsledek (**Zašifrovaný payload**) se zobrazí hned pod formulářem jako JSON: ```json { @@ -65,59 +69,129 @@ REGEN_KEYS=1 go run . "ct": "base64(aes-gcm-ciphertext)" } ``` -4. Příjemce vloží payload do sekce **Dešifrovat** → dostane plaintext. -5. Všude můžeš použít tlačítko **Copy**. + * tlačítkem **Copy** zkopíruješ, **Clear** vymaže obsah. +4. Příjemce vloží payload do sekce **Dešifrovat** a klikne **Decrypt** → objeví se plaintext. --- ## HTTP endpointy -* `GET /` – HTMX UI -* `GET /public.pem` – veřejný klíč (PEM) -* `GET /public.crt` – self-signed cert (PEM) +* `GET /` – hlavní UI (HTMX) +* `GET /public.pem` – veřejný klíč PEM +* `GET /public.crt` – self-signed cert PEM * `POST /encrypt` – form fields: - * `message` – plaintext k zašifrování - * `pubkey` – cílový klíč (PEM `PUBLIC KEY` **nebo** `CERTIFICATE`) + * `message` – plaintext + * `pubkey` – cílový veřejný klíč (PEM `PUBLIC KEY` nebo `CERTIFICATE`) * `POST /decrypt` – form field: - * `payload` – JSON s poli `ek`, `n`, `ct` (viz výše) -* `GET /static/style.css` – styl + * `payload` – JSON se strukturou `{"ek","n","ct"}` +* `GET /static/style.css` – CSS --- -## Struktura +## Struktura projektu ``` . ├─ main.go ├─ templates/ -│ ├─ index.html -│ ├─ encrypt.html -│ └─ decrypt.html +│ ├─ index.html # UI s Copy/Clear tlačítky +│ ├─ encrypt.html # výstup šifrování +│ └─ decrypt.html # výstup dešifrování └─ static/ - └─ style.css + └─ style.css # responzivní layout (1 sloupec mobile, 2 desktop) ``` --- ## Bezpečnostní poznámky -* Klíče se ukládají **lokálně** vedle binárky. Chraň `identity_key.pem` (chmod 600 je nastaven). -* Certifikát je jen pro sdílení veřejného klíče (nepoužívá se pro TLS). -* RSA-OAEP se SHA-256 + AES-GCM (12B nonce) pro zprávy libovolné délky. -* Pro produkci řeš správu identit (HSM, password-protected export, rotace, audit). +* Soukromý klíč se ukládá lokálně (`identity_key.pem`, chmod 600). +* Certifikát je jen vizitka (není pro TLS). +* Použitý algoritmus: RSA-OAEP se SHA-256 + AES-GCM. +* Pro produkci bys měl řešit: správu identit, heslem chráněný export, rotaci klíčů. --- -## Problémy & tipy +## Tipy -* „Neplatný public key/cert“ – zkontroluj, že vkládáš **PEM blok** (`-----BEGIN ...`). -* Payload musí být validní JSON, base64 hodnoty bez zalomení. -* Pokud migruješ mezi stroji, přenes `identity_key.pem` + `public.pem` + `identity.crt`. +* „Copy“ používá **Clipboard API** (funguje na `localhost`). +* „Clear“ vyprázdní textareu. +* Layout: mobil → 1 sloupec, desktop → 2 sloupce. --- -## Licence +# Jak to funguje podle chatGPT -MIT (nebo si dopiš dle potřeby). +Ok, pojďme úplně jednoduše, krok za krokem – bez odborných keců 👇 + +--- + +### Jak to celé funguje + +1. **Máš dva klíče**: + + * **soukromý klíč** (jen pro tebe, musíš ho schovat) + * **veřejný klíč** (můžeš poslat komukoli) + +2. Když ti někdo chce poslat zprávu: + + * vezme tvůj veřejný klíč + * vytvoří si jednorázový **tajný kód (klíč)** pro šifrování + * ten kód uzamkne tvým veřejným klíčem (aby ho uměl odemknout jen tvůj soukromý klíč) + * zprávu zašifruje tím jednorázovým kódem + +3. Ty zprávu dostaneš: + + * odemkneš si tím svým **soukromým klíčem** ten jednorázový kód + * a tím kódem si dešifruješ samotnou zprávu + +💡 Proč to takhle? Protože šifrovat velké zprávy přímo RSA by bylo pomalé a nepraktické. Proto se vždycky kombinuje: + +* RSA jen na uzamčení malého tajného klíče, +* rychlý algoritmus (AES) na šifrování celé zprávy. + +--- + +### Jak silné to je (2025) + +* **RSA 2048 bitů** + + * to je ta „velká matematika“ (pracuje s obrovskými prvočísly) + * dnes to *nikdo na světě neumí rozlousknout* běžnými prostředky + * odhady: kdybys měl všechny počítače světa, tak bys to lámal tisíce let + * pro běžnou komunikaci je to **naprosto bezpečné** + +* **AES 256 bitů** + + * to je ten rychlý algoritmus na zprávy + * 256 bitů = tak obrovské množství možností, že brute force (zkoušet všechny klíče) je nereálný i s kvantovým počítačem, co si dnes umíme představit + * používají to banky, armády i vlády + +--- + +### Přirovnání + +* Tvůj systém = **dvě zámky**: + + 1. RSA je jako **trezor s klíčem** – jen ty máš správný klíč (soukromý). + 2. AES je jako **řetěz a visací zámek na balíku zprávy** – kód k odemčení toho visacího zámku je schovaný v trezoru. + +* Útočník by musel buď: + + * **vykrást trezor RSA 2048** (prakticky nemožné), + * nebo **prolomit AES 256** (prakticky nemožné). + +--- + +### Shrnutí + +* To, co máš teď, je na úrovni **toho, co reálně používá PGP, banky a vlády**. +* **Prolomení?**: + + * Dnes = nerealistické + * Do cca 2030 = pořád v pohodě (RSA 2048) + * Pokud chceš být „future-proof“ na desítky let, můžeš jít na **RSA 3072 nebo 4096** (je to jen pomalejší). + +--- diff --git a/main.go b/main.go index b45f4a7..f19e711 100644 --- a/main.go +++ b/main.go @@ -12,10 +12,13 @@ import ( "encoding/base64" "encoding/json" "encoding/pem" + "fmt" "html/template" + "io/fs" "log" "math/big" "net/http" + "os" "time" ) @@ -24,6 +27,10 @@ var ( pubPEM []byte certPEM []byte // self-signed cert jen pro sdílení identity (volitelné) tmpl *template.Template + + privPath = "identity_key.pem" + pubPath = "public.pem" + certPath = "identity.crt" ) type envelope struct { @@ -34,30 +41,23 @@ type envelope struct { } func main() { - var err error - // 1) identita: RSA klíče - priv, err = rsa.GenerateKey(rand.Reader, 2048) - if err != nil { + // 1) načti nebo vygeneruj klíče + if err := loadOrGenerateKeys(); err != nil { log.Fatal(err) } - pubASN1, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey) - pubPEM = pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1}) - // 2) volitelně vygeneruj self-signed cert pro export (není pro HTTPS) - certPEM = generateSelfSignedCert(&priv.PublicKey) - - // 3) šablony + // 2) šablony tmpl = template.Must(template.ParseGlob("templates/*.html")) - // 4) routing + // 3) routing http.HandleFunc("/", indexHandler) http.HandleFunc("/public.pem", publicKeyHandler) http.HandleFunc("/public.crt", publicCertHandler) - http.HandleFunc("/encrypt", encryptHandler) // POST - http.HandleFunc("/decrypt", decryptHandler) // POST + http.HandleFunc("/encrypt", encryptHandler) + http.HandleFunc("/decrypt", decryptHandler) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - log.Println("Server běží na http://localhost:8080 (TLS neřešíme; klíč slouží jen k šifrování zpráv).") + log.Println("Server běží na http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) } @@ -268,3 +268,65 @@ func anyToString(v any) string { return string(j) } } + +func loadOrGenerateKeys() error { + // existuje private key? + if fileExists(privPath) && fileExists(pubPath) && fileExists(certPath) { + // načti privátní klíč + pkBytes, err := os.ReadFile(privPath) + if err != nil { + return err + } + block, _ := pem.Decode(pkBytes) + if block == nil || block.Type != "RSA PRIVATE KEY" { + return fmt.Errorf("invalid private key PEM") + } + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return err + } + priv = key + + // načti public & cert + pubPEM, _ = os.ReadFile(pubPath) + certPEM, _ = os.ReadFile(certPath) + + log.Println("Načtena existující identita z disku.") + return nil + } + + // jinak vygeneruj nové + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + priv = key + + // public + pubASN1, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey) + pubPEM = pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1}) + + // cert + certPEM = generateSelfSignedCert(&priv.PublicKey) + + // ulož + if err := os.WriteFile(privPath, + pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}), + fs.FileMode(0600)); err != nil { + return err + } + if err := os.WriteFile(pubPath, pubPEM, 0644); err != nil { + return err + } + if err := os.WriteFile(certPath, certPEM, 0644); err != nil { + return err + } + + log.Println("Vygenerována nová identita a uložena na disk.") + return nil +} + +func fileExists(p string) bool { + info, err := os.Stat(p) + return err == nil && !info.IsDir() +}