ukladani a nacitani certifikatu + doplneni readme

This commit is contained in:
Lukas Batelka 2025-09-08 22:16:53 +02:00
parent 5faea39889
commit 098b973d19
2 changed files with 187 additions and 51 deletions

148
README.md
View File

@ -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`). * Umožní sdílet veřejný klíč (`public.pem`) a self-signed cert (`identity.crt`).
* Šifruje hybridně: **RSA-OAEP(SHA-256) + AES-256-GCM**. * Š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. * 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 ## Požadavky
* Go 1.21+ * 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 ## Rychlý start
```bash ```bash
# stačí spustit # spustí server
go run . go run .
# otevři v prohlížeči # otevři v prohlížeči
http://localhost:8080/ http://localhost:8080/
``` ```
Na prvním startu se vytvoří soubory: Na prvním startu se vytvoří:
``` ```
identity_key.pem # RSA private key (PKCS#1) identity_key.pem # RSA private key (PKCS#1)
public.pem # veřejný klíč PEM (PKIX) 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íčů ## Regenerace klíčů
Chceš novou identitu? Pokud chceš vygenerovat novou identitu:
```bash ```bash
# přes flag # přes flag
@ -53,10 +54,13 @@ REGEN_KEYS=1 go run .
## Jak to používat (UI) ## Jak to používat (UI)
1. Otevři `http://localhost:8080/`. 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. 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íč** vlož svou zprávu a **cizí** public key/cert → klikni **Encrypt**. 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 ```json
{ {
@ -65,59 +69,129 @@ REGEN_KEYS=1 go run .
"ct": "base64(aes-gcm-ciphertext)" "ct": "base64(aes-gcm-ciphertext)"
} }
``` ```
4. Příjemce vloží payload do sekce **Dešifrovat** → dostane plaintext. * tlačítkem **Copy** zkopíruješ, **Clear** vymaže obsah.
5. Všude můžeš použít tlačítko **Copy**. 4. Příjemce vloží payload do sekce **Dešifrovat** a klikne **Decrypt** → objeví se plaintext.
--- ---
## HTTP endpointy ## HTTP endpointy
* `GET /` HTMX UI * `GET /` hlavní UI (HTMX)
* `GET /public.pem` veřejný klíč (PEM) * `GET /public.pem` veřejný klíč PEM
* `GET /public.crt` self-signed cert (PEM) * `GET /public.crt` self-signed cert PEM
* `POST /encrypt` form fields: * `POST /encrypt` form fields:
* `message` plaintext k zašifrování * `message` plaintext
* `pubkey` cílový klíč (PEM `PUBLIC KEY` **nebo** `CERTIFICATE`) * `pubkey` cílový veřejný klíč (PEM `PUBLIC KEY` nebo `CERTIFICATE`)
* `POST /decrypt` form field: * `POST /decrypt` form field:
* `payload` JSON s poli `ek`, `n`, `ct` (viz výše) * `payload` JSON se strukturou `{"ek","n","ct"}`
* `GET /static/style.css` styl * `GET /static/style.css` CSS
--- ---
## Struktura ## Struktura projektu
``` ```
. .
├─ main.go ├─ main.go
├─ templates/ ├─ templates/
│ ├─ index.html │ ├─ index.html # UI s Copy/Clear tlačítky
│ ├─ encrypt.html │ ├─ encrypt.html # výstup šifrování
│ └─ decrypt.html │ └─ decrypt.html # výstup dešifrování
└─ static/ └─ static/
└─ style.css └─ style.css # responzivní layout (1 sloupec mobile, 2 desktop)
``` ```
--- ---
## Bezpečnostní poznámky ## Bezpečnostní poznámky
* Klíče se ukládají **lokálně** vedle binárky. Chraň `identity_key.pem` (chmod 600 je nastaven). * Soukromý klíč se ukládá lokálně (`identity_key.pem`, chmod 600).
* Certifikát je jen pro sdílení veřejného klíče (nepoužívá se pro TLS). * Certifikát je jen vizitka (není pro TLS).
* RSA-OAEP se SHA-256 + AES-GCM (12B nonce) pro zprávy libovolné délky. * Použitý algoritmus: RSA-OAEP se SHA-256 + AES-GCM.
* Pro produkci řeš správu identit (HSM, password-protected export, rotace, audit). * 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 ...`). * „Copy“ používá **Clipboard API** (funguje na `localhost`).
* Payload musí být validní JSON, base64 hodnoty bez zalomení. * „Clear“ vyprázdní textareu.
* Pokud migruješ mezi stroji, přenes `identity_key.pem` + `public.pem` + `identity.crt`. * 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ší).
---

90
main.go
View File

@ -12,10 +12,13 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"fmt"
"html/template" "html/template"
"io/fs"
"log" "log"
"math/big" "math/big"
"net/http" "net/http"
"os"
"time" "time"
) )
@ -24,6 +27,10 @@ var (
pubPEM []byte pubPEM []byte
certPEM []byte // self-signed cert jen pro sdílení identity (volitelné) certPEM []byte // self-signed cert jen pro sdílení identity (volitelné)
tmpl *template.Template tmpl *template.Template
privPath = "identity_key.pem"
pubPath = "public.pem"
certPath = "identity.crt"
) )
type envelope struct { type envelope struct {
@ -34,30 +41,23 @@ type envelope struct {
} }
func main() { func main() {
var err error // 1) načti nebo vygeneruj klíče
// 1) identita: RSA klíče if err := loadOrGenerateKeys(); err != nil {
priv, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err) 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) // 2) šablony
certPEM = generateSelfSignedCert(&priv.PublicKey)
// 3) šablony
tmpl = template.Must(template.ParseGlob("templates/*.html")) tmpl = template.Must(template.ParseGlob("templates/*.html"))
// 4) routing // 3) routing
http.HandleFunc("/", indexHandler) http.HandleFunc("/", indexHandler)
http.HandleFunc("/public.pem", publicKeyHandler) http.HandleFunc("/public.pem", publicKeyHandler)
http.HandleFunc("/public.crt", publicCertHandler) http.HandleFunc("/public.crt", publicCertHandler)
http.HandleFunc("/encrypt", encryptHandler) // POST http.HandleFunc("/encrypt", encryptHandler)
http.HandleFunc("/decrypt", decryptHandler) // POST http.HandleFunc("/decrypt", decryptHandler)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) 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)) log.Fatal(http.ListenAndServe(":8080", nil))
} }
@ -268,3 +268,65 @@ func anyToString(v any) string {
return string(j) 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()
}