Budovanie kryptografického toolkitu s dôrazom na lokálne spracovanie

Generátory hesiel, dekodéry JWT, hash nástroje – všetky požadujú dôveru v server. Germond Security spúšťa všetko vo vašom prehliadači a dodáva rovnaký kód ako CLI a importovateľná knižnica.

·5 min čítania

Väčšina kryptografických nástrojov na webe má rovnaký problém. Vložíš JWT do dekodéra a payload putuje cez sieť na cudzí server. Zadáš heslo do kontroly sily hesla a to heslo skončí niekde v HTTP logu. „HTTPS znamená, že je to bezpečné” míňa podstatu – server je stále hrozba.

Germond Security neodosiela nič nikam. Každá operácia beží v tvojom prehliadači cez node:crypto, polyfillované pre prehliadač cez WebCrypto. Jediný kľúč zapísaný do localStorage je prepínač témy. Obnovíš stránku a všetky vstupy analyzátora zmiznú.

Dodáva sa ako tri veci zdieľajúce jeden zdrojový kód: webová SPA, CLI (gsec) a TypeScript knižnica.

Čo pokrýva

KategóriaNástroje
GenerátoryHeslá, prístupové frázy (Diceware 2000+ slov), tajné kľúče, API kľúče
IDsUUID v1–v5/v7, NanoID, ULID, KSUID, CUID
HasheSHA-1/256/384/512, HMAC, scrypt, PBKDF2
ŠifryAES-256-GCM, JWT HS256, RSA/Ed25519/ECDSA kľúčové páry
Enkodérybase32, base58, base64, hex, oktal, binárna – obojsmerne
AnalyzátorySila hesla, entropia, odhady času prelomenia

Všetko je napísané od základov – žiadne externé knižnice pre enkodovanie, žiadny JWT balíček, žiadne externé generátory ID.

Jeden zdrojový kód, tri výstupy

Monorepo má dve aplikácie (web, cli) a jadrovú knižnicu (@germondai/security). Webová aplikácia konzumuje jadro cez Vite path alias, ktorý smeruje priamo na TypeScript zdrojový kód, nie na skompilovaný výstup:

// apps/web/vite.config.ts
"@germondai/security": fileURLToPath(
  new URL("../../packages/security/src/index.ts", import.meta.url)
)

Žiadny samostatný krok buildu medzi knižnicou a webovou aplikáciou. Turborepo riadi poradie závislostí pre CLI, ale webová aplikácia importuje priamo zo zdroja. Zmeníš funkciu v packages/security a oba výstupy to zachytia pri najbližšom uložení.

RNG s odmietaním vzoriek

Zjavný prístup k výberu náhodného znaku zo sady je charset[randomBytes(1)[0] % charset.length]. Funguje to, ale je to jemne skreslené. Keď dĺžka sady znakov nedelí 256 bezo zvyšku, znaky s nízkym indexom sa objavujú o niečo častejšie ako znaky s vysokým indexom.

function uniformInt(n: number): number {
  const limit = 256 - (256 % n)
  let x: number
  do { x = randomBytes(1)[0] } while (x >= limit)
  return x % n
}

Odmietavé vzorkovanie ťahá bajty, kým nezíska jeden pod najväčším násobkom n, ktorý sa zmestí do rozsahu bajtu, a potom vezme modulo. Takto sa vyberá každý znak hesla, každý znak NanoID, každé slovo prístupovej frázy.

Cross-environment crypto shim

Spustenie node:crypto v prehliadači cez vite-plugin-node-polyfills naráža na problém: createHash() z crypto-browserify padá v moderných Vite konfiguráciách (Cannot read properties of undefined (reading 'call')). Shim detekuje globalThis.crypto.subtle a namiesto toho smeruje na natívne WebCrypto:

async function digest(algorithm: string, data: Uint8Array): Promise<ArrayBuffer> {
  if (globalThis.crypto?.subtle) {
    return globalThis.crypto.subtle.digest(algorithm, data)
  }
  const { createHash } = await import('node:crypto')
  return createHash(algorithm.replace('-', '').toLowerCase())
    .update(data)
    .digest()
    .buffer as ArrayBuffer
}

Obe cesty vrátia rovnaké bajty. Prehliadač získa natívny výkon WebCrypto; Node a Bun dostanú node:crypto. Duálne synchrónne a asynchrónne varianty existujú pre AES-GCM a generovanie kľúčových párov z rovnakého dôvodu – Ed25519 cez WebCrypto je stále nestabilné (Chrome 113+, Safari 17+, Firefox 130+), takže asynchrónny variant zachytáva výnimky a padá späť na záložnú možnosť.

Efektívna entropia v analyzátore hesiel

Analyzátor sily oddeľuje dve čísla entropie. Naivná entropia je log2(charsetPool) × length – to, čo ti dáva matematika hrubej sily za predpokladu plne náhodných, nezávislých znakov. Efektívna entropia odpočítava za odhalené vzory:

  • Bežné slová: ~10 bitov odpočítaných za každú nájdenú zhodu v slovníku
  • Klávesnicové sekvencie: qwerty, 12345, zxcvbn
  • Opakujúce sa znaky a podreťazce
  • Vzory dátumov: štvorciferné roky, formáty MM/DD

Detekcia slov berie do úvahy CamelCase. MyDog'sNameIsRex sa rozdelí na základe prechodov veľkých a malých písmen a oddeľovačov na ["My", "Dog", "s", "Name", "Is", "Rex"] pred porovnaním so zoznamom slov. Slová kratšie ako 6 znakov sa ignorujú, aby sa predišlo falošným poplachom z náhodných sekvencií znakov, ktoré náhodou obsahujú krátke bežné slová.

Výstup mapuje na päť scenárov doby prelomenia:

ScenárRýchlosť pokusov
Online (obmedzená sadzba)100/hod
Offline pomalý hash (bcrypt)10 tis./s
Offline rýchly hash (MD5)10 mld./s
Distribuovaný GPU cluster100 mld./s
Kvantový počítač (Groverov algoritmus)sqrt(keyspace)/s

Rovnaké heslo, ktoré vyzerá silné na základe naivnej entropie, často klesne o dve alebo tri úrovne pri efektívnej entropii. MyDog'sNameIsRex!2024 má 82 naivných bitov, ale oveľa menej ako 40 efektívnych bitov, keď sa odpočíta päť zhôd v slovníku a vzor roku.

CLI

Spustenie bunx gsec bez argumentov otvorí interaktívneho sprievodcu pokrývajúceho všetkých päť kategórií. S argumentmi preskočí sprievodcu:

bunx gsec gen password -l 24 --no-symbols
bunx gsec gen id --type uuid-v7 -n 10
bunx gsec hash sha256 "hello world"
bunx gsec cipher aes-gcm encrypt --passphrase secret "message"
bunx gsec analyze strength "MyDog'sNameIsRex!2024"

Príznak -r pri generovaní hesiel zabezpečuje, že aspoň jeden znak z každej vybranej triedy sa objaví vo výstupe. -x odstraňuje nejednoznačné znaky (0Oo1lI|). Obe možnosti sú overované CI smoke testom – pipeline generuje skutočné heslá a kontroluje obmedzenia cez bash.

Nasadenie

Trojstupňový Docker build: turbo prune --docker orezá monorepo len na tranzitívne závislosti webovej aplikácie, Bun builder nainštaluje a skompiluje a nginx:alpine obsluhuje statickú SPA. Runtime image neobsahuje Node ani Bun.

Zdrojový kód je na github.com/germondai/security. CLI dnes funguje; webové rozhranie pokrýva väčšinu kategórií. Niekoľko vecí stále chýba v UI (podpora argon2, lepšia správa kľúčových párov) je na zozname.