syncova-backup/packages/platform/crypto/token.go
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
Enterprise-Backup-, Recovery-, Verification-, Security- und
Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme.

Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als
vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit
nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem
tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes
geht in keine Bewertung als "gut" ein.

Umfang (Phasen 0-23):

- Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll,
  Katalogaufbau allein aus den Manifesten — ohne Datenbank
- Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz
  Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck
- Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell)
- Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive
- Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit
- Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center,
  Ransomware-Heuristik (meldet, handelt nie)
- Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing
- Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository
- Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64

Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup
Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs.

Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die
systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine
wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md
und docs/release-candidate.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:10:54 +02:00

80 lines
3.0 KiB
Go

package crypto
import (
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"fmt"
)
// tokenByteLength ist die Länge eines erzeugten Tokens in Byte.
//
// 32 Byte entsprechen 256 Bit Entropie. Ein Erraten ist damit auch bei
// unbegrenzten Versuchen praktisch ausgeschlossen.
const tokenByteLength = 32
// GenerateToken erzeugt ein kryptografisch sicheres Token.
//
// Die Ausgabe ist URL-sicher kodiert und kann damit unverändert in Headern
// übertragen werden.
func GenerateToken() (string, error) {
tokenBytes := make([]byte, tokenByteLength)
if _, randomError := rand.Read(tokenBytes); randomError != nil {
return "", fmt.Errorf("es konnte kein sicheres token erzeugt werden: %w", randomError)
}
return base64.RawURLEncoding.EncodeToString(tokenBytes), nil
}
// HashToken bildet den Speicherwert eines Tokens.
//
// Gespeichert wird ausschließlich dieser Hash: ein Datenbankleck erlaubt damit
// keine Übernahme laufender Sitzungen. Anders als bei Passwörtern genügt hier
// SHA-256, weil ein Token bereits 256 Bit Zufall enthält und deshalb nicht
// erraten werden kann — die Rechenkosten von Argon2id hätten keinen Nutzen.
func HashToken(plainToken string) string {
tokenDigest := sha256.Sum256([]byte(plainToken))
return hex.EncodeToString(tokenDigest[:])
}
// TokensEqual vergleicht zwei Token-Hashes in konstanter Zeit.
//
// Ein zeichenweiser Vergleich würde über die Laufzeit verraten, wie viele
// Zeichen bereits übereinstimmen.
func TokensEqual(firstHash string, secondHash string) bool {
return subtle.ConstantTimeCompare([]byte(firstHash), []byte(secondHash)) == 1
}
// recoveryCodeByteLength ist die Länge eines Wiederherstellungscodes in Byte.
//
// 10 Byte ergeben 16 Zeichen in Base32 — kurz genug zum Abschreiben und mit
// 80 Bit Entropie nicht zu erraten.
const recoveryCodeByteLength = 10
// GenerateRecoveryCode erzeugt einen gut abschreibbaren Wiederherstellungscode.
//
// Verwendet wird ein Alphabet ohne leicht verwechselbare Zeichen (kein 0/O,
// kein 1/I), da diese Codes typischerweise ausgedruckt und abgetippt werden.
func GenerateRecoveryCode() (string, error) {
// Das Alphabet enthält 32 Zeichen, sodass 5 Bit je Zeichen kodiert werden.
const codeAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
randomBytes := make([]byte, recoveryCodeByteLength)
if _, randomError := rand.Read(randomBytes); randomError != nil {
return "", fmt.Errorf("es konnte kein sicherer wiederherstellungscode erzeugt werden: %w", randomError)
}
// Die Umwandlung nutzt jedes Byte modulo der Alphabetgröße. Da 256 kein
// Vielfaches von 32 ist, entstünde eine minimale Ungleichverteilung -
// bei 32 als Teiler von 256 ist sie jedoch exakt null.
codeCharacters := make([]byte, len(randomBytes))
for byteIndex, randomByte := range randomBytes {
codeCharacters[byteIndex] = codeAlphabet[int(randomByte)%len(codeAlphabet)]
}
// Die Gruppierung erleichtert das fehlerfreie Abtippen.
return fmt.Sprintf("%s-%s", codeCharacters[:5], codeCharacters[5:]), nil
}