ukladani a nacitani certifikatu + doplneni readme
This commit is contained in:
parent
5faea39889
commit
098b973d19
148
README.md
148
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`).
|
* 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
90
main.go
@ -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()
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user