Dokumentacja
↗ Otwórz demo
Wprowadzenie

Czym jest Secret Key?

Kryptograficzny system awaryjnego dostępu do bazy haseł — dzieli hasło główne między zaufane osoby algorytmem Shamira, zabezpieczony 2FA i bcrypt.

Każdy z nas przechowuje dziesiątki haseł — do banków, poczty, mediów społecznościowych. Co się z nimi stanie po naszej śmierci? Rodzina zostaje odcięta od kont, nie może anulować subskrypcji, odzyskać pieniędzy.

Secret Key rozwiązuje ten problem, tworząc bezpieczny plan awaryjny — bez kompromisów wobec bezpieczeństwa za życia właściciela.

#Dwa cele systemu

🔐
Ochrona za życia
Każda próba dostępu wymaga unikalnego hasła i kodu SMS — nawet przy posiadaniu karty logowanie bez przypisanego telefonu jest niemożliwe.
💀
Awaryjny dostęp po śmierci
Wyznaczone osoby zbierają wymaganą liczbę fragmentów klucza i rekonstruują hasło algorytmem Shamira — lokalnie w przeglądarce, bez serwera.

#Stack technologiczny

WarstwaTechnologiaOpis
BackendPHP 8+Pliki flat (JSON + PHP config), zero SQL
Autoryzacjabcrypt + SMScost=10, 2FA via SMSPlanet API
Kryptografiasecrets.jsShamir Secret Sharing, kompatybilny z iancoleman.io
FrontendHTML + JSZero zewnętrznych zależności, działa offline
HostingSelf-hostedDane nigdy nie opuszczają własnego serwera

#Jak to działa — procedura awaryjna

01
Zebranie wyznaczonych osób

Minimalna wymagana liczba osób (np. 3 z 5) zbiera się razem. Każda ma kartę Secret Key z fragmentem klucza kryptograficznego.

02
Logowanie do systemu

Każda osoba loguje się danymi z karty (login + hasło) i weryfikuje tożsamość kodem SMS wysłanym na przypisany numer telefonu.

03
Wprowadzanie udziałów

Każda osoba wpisuje swój udział Shamira (ciąg hex) lub skanuje kod QR z karty. Eliminuje to błędy przepisywania.

04
Rekonstrukcja hasła

Hasło odtwarzane jest lokalnie w przeglądarce algorytmem Shamira — nie trafia na serwer. System wyświetla hasło główne do bazy KeePassXC (lub innego menedżera).

💡
Self-hosted = pełna kontrola
Dane nigdy nie opuszczają Twojego serwera. Żadnej centralnej bazy danych, żadnej chmury, żadnych zewnętrznych zależności poza wysyłką SMS przez SMSPlanet.
Wprowadzenie

Szybki start

Od zera do działającego systemu w czterech krokach. Konfigurację generujesz w pełni offline — samo wdrożenie wymaga własnego hostingu z PHP.

ℹ️
Narzędzia działają całkowicie offline
Otwórz dashboard.html lokalnie w przeglądarce (protokół file://). Żadne dane nie opuszczają Twojego komputera podczas konfiguracji.

#Krok 1 — Generowanie konfiguracji

Otwórz dashboard.html i przejdź do zakładki Konfiguracja.

1
Dodaj wyznaczone osoby

Dla każdej osoby: login, silne hasło, imię, nazwisko, numer telefonu (do weryfikacji SMS) i czy ma być widoczna na liście posiadaczy w panelu. System obsługuje od 2 do 7+ osób.

2
Uzupełnij pliki, instrukcję i powiadomienia

W tym samym formularzu: pliki do pobrania (baza haseł, 2FA, instalator programu), kroki instrukcji dla panelu i dane do powiadomienia email o logowaniu. Szczegóły każdego pola — patrz Konfiguracja treści i danych.

3
Generuj konfigurację

Kliknij „Generuj konfigurację" — otrzymasz plik secret-key.php z zahashowanymi hasłami bcrypt i całą resztą configu w jednym miejscu.

4
Pobierz plik

Zachowaj secret-key.php — trafi na serwer poza katalogiem publicznym (/private/).

#Krok 2 — Podział hasła algorytmem Shamira

Przejdź do zakładki Szyfrowanie w dashboard.html.

1
Wpisz hasło główne

Wprowadź hasło do bazy KeePassXC (lub innego menedżera haseł). Hasło jest przetwarzane wyłącznie lokalnie.

2
Ustaw parametry podziału

Wybierz łączną liczbę udziałów i wymagane minimum. Zalecane: 5 udziałów, próg 3.

3
Pobierz udziały

Kliknij „Generuj udziały" i pobierz plik secret-key-shares.txt. Każdy udział to jedna linia hex — przypisz je osobom z listy.

#Krok 3 — Wgranie na serwer

# Struktura katalogów na serwerze /home/user/ ├── public_html/ │ └── app/ ← system logowania │ └── decrypt/ ← panel użytkownika └── private/ ← POZA public_html ! └── secret-key.php ← plik konfiguracyjny
Plik konfiguracyjny musi być poza public_html
Umieszczenie secret-key.php w katalogu publicznym grozi ujawnieniem zahashowanych haseł i numerów telefonów. Folder /private/ musi być niedostępny przez HTTP.

Zaktualizuj ścieżkę w auth.php:

php
// auth.php — ścieżka do konfiguracji
require_once '/home/user/private/secret-key.php';

#Krok 4 — Generowanie i dystrybucja kart

Otwórz zakładkę Generator kart w dashboard.html. Wprowadź dane każdej osoby i udziały Shamira z pliku secret-key-shares.txt, a następnie pobierz karty jako PDF — gotowe do wydruku lub laminowania.

System gotowy
Po rozdaniu kart system jest aktywny. Każda wyznaczona osoba ma własne dane logowania, udział Shamira i adres instancji. Bez wymaganej liczby kart odtworzenie hasła jest matematycznie niemożliwe.
Kryptografia

Algorytm Shamira

Shamir Secret Sharing — matematycznie gwarantuje, że mniejsza niż wymagana liczba fragmentów nie ujawnia żadnej informacji o sekrecie.

#Jak działa podział sekretu

Sekret kodowany jest jako wyraz wolny wielomianu nad ciałem GF(2⁸). Każdy udział to punkt na tym wielomianie — znając wymaganą liczbę punktów, można go jednoznacznie odtworzyć interpolacją Lagrange'a:

matematyka
f(x) = a₀ + a₁x + a₂x² + ... + aₖ₋₁xᵏ⁻¹  (mod p)

gdzie: a₀ = sekret (hasło główne)
       k   = wymagane minimum udziałów
       a₁..aₖ₋₁ = losowe współczynniki

#Przykład — podział 5/3

Hasło główne: "MojeHasloDoKeePass2024!" │ ▼ Podział na 5 udziałów (próg: 3) │ ┌────┴──────────────────────────────────┐ │ │ │ S1: 801a3f9c2e4b7d1... → Osoba A │ │ S2: 802c8f1a5e9b3d7... → Osoba B │ Każdy udział │ S3: 803e2a7f4c1b9d5... → Osoba C │ jest bezużyteczny │ S4: 804b6d3e8f2a1c9... → Osoba D │ bez wymaganej │ S5: 805d9f7b2e4c3a1... → Osoba E │ liczby pozostałych │ │ └────┬──────────────────────────────────┘ │ ▼ Rekonstrukcja — wystarczą dowolne 3 z 5 │ S1 + S2 + S3 → "MojeHasloDoKeePass2024!" ✓ S2 + S4 + S5 → "MojeHasloDoKeePass2024!" ✓ S1 → brak informacji o sekrecie
⚠️
Information-theoretic security
Posiadanie mniejszej niż wymagana liczby udziałów nie daje żadnej informacji o sekrecie — to własność matematyczna algorytmu, nie zależna od mocy obliczeniowej atakującego. Dodany padding 1024 bitów uniemożliwia ataki na małe sekrety.

#Parametry implementacji

ParametrWartośćUwagi
Bibliotekasecrets.jsgrempe/secrets.js (fork amper5and/secrets.js), zwendorowany przez iancoleman.io/shamir — stąd pełna kompatybilność formatu udziałów
Format udziałów8 + 2-hex x-coord + dataPrefiks 8 oznacza 8-bitowe ciało GF
minPad1024 bitówIdentyczny jak iancoleman — zapewnia kompatybilność
KodowanieUTF-8 (str2hex)Obsługa polskich znaków
RekonstrukcjaJavaScript (przeglądarka)Hasło nie trafia na serwer

#Dobór parametrów

Łączna liczba udziałówWymagane minimumScenariusz
32Mała rodzina
53Standardowy (zalecany) — toleruje niedostępność 2 osób zalecany
74Większa rodzina lub firma
ℹ️
Niezależna weryfikacja
Format udziałów jest w pełni kompatybilny z iancoleman.io/shamir — możliwa jest niezależna weryfikacja odtworzenia hasła poza systemem Secret Key.
Kryptografia

Karty Secret Key

Spersonalizowany nośnik dla każdej wyznaczonej osoby — drukowany PDF w formacie ISO ID-1 (85.6×54mm), jak karta kredytowa.

#Elementy karty

🔑
Login i hasło
Unikalne dane dostępowe — każda osoba ma własne konto z przypisanym numerem telefonu do weryfikacji SMS.
🔢
Udział Shamira
Fragment klucza w formacie hex — jeden z N, bezużyteczny bez pozostałych. Stanowi główny element kryptograficzny karty.
Kod QR
Ten sam udział zakodowany jako QR — skanowanie eliminuje błędy ręcznego przepisywania długiego ciągu hex.
🌐
Adres systemu
URL własnej instancji Secret Key — prowadzi bezpośrednio do panelu logowania bez konieczności znajomości adresu na pamięć.

#Generator kart

Generator dostępny jest jako zakładka Generator kart w dashboard.html. Wymaga plików card-front.js i card-back.js w tym samym folderze.

OpcjaOpis
Spad 2mmDodaje 2mm spadu drukarskiego z każdej strony (standard drukarni)
Linie cięciaZnaczniki drukarskie do precyzyjnego cięcia
Pobierz PDF kartyEksport pojedynczej karty (przód + tył = 2 strony)
Pobierz PDF wszystkichEksport wszystkich kart naraz

#Specyfikacja techniczna

ParametrWartość
FormatISO ID-1, 85.6 × 54 mm
SVG viewBox0 0 325.03964 204.09428
Spad2mm z każdej strony (standard)
PDFKażda karta = osobna strona (przód + tył oddzielnie)
TechnologiaCzyste SVG (<path>, <rect> z gradientami — brak <image>)
💡
Brak elementów rastrowych
Wszystkie elementy graficzne karty to czyste ścieżki SVG. Gwarantuje to ostrość wydruku przy dowolnej rozdzielczości i eliminuje problemy z drukowaniem obrazów rastrowych w Chrome.
System

Architektura systemu

Wielowarstwowy przepływ weryfikacji — od wpisania loginu do odtworzenia hasła w przeglądarce.

#Przepływ autoryzacji

Użytkownik → wpisuje login + hasło │ System PHP → weryfikuje hasło (bcrypt), sprawdza rate limiting i CSRF │ SMS / 2FA → wysyła jednorazowy kod na przypisany numer telefonu │ Użytkownik → wpisuje kod SMS │ System PHP → weryfikuje kod, tworzy sesję, opcjonalnie zapamiętuje urządzenie │ Przeglądarka → przyjmuje udziały Shamira, odtwarza sekret lokalnie w JS

#Warstwy systemu

Warstwa autoryzacji (/app/)

PlikRola
login.phpFormularz logowania z ochroną CSRF
auth.phpGłówna logika: bcrypt, 2FA, brute-force, trusted devices
verify.phpWeryfikacja kodu SMS
resend.phpPonowna wysyłka SMS (cooldown 60s, max 3/sesję)
logout.phpWylogowanie i zniszczenie sesji

Warstwa dostępu (/app/decrypt/)

PlikRola
index.phpPanel odszyfrowania z rekonstrukcją Shamira w JavaScript
download.phpBramkowane pobieranie plików — wymaga sesji, biała lista budowana z $downloads, log po stronie serwera (patrz Konfiguracja treści i danych)
log.phpLogowanie zdarzeń (próby dostępu, kody SMS, błędy)
devtools-log.phpLogowanie prób otwarcia DevTools (z rate-limitingiem per IP)
timelock.phpBiblioteka logiki stanu blokady czasowej — patrz Ochrona przed zmową powierników
arm-timelock.phpUzbraja 48h blokadę pobierania po pierwszej udanej rekonstrukcji hasła + wysyła mail alarmowy z linkiem Panic Button
panic.phpObsługa jednorazowego linku blokującego (Panic Button) — bez wymogu logowania, autoryzacja przez 256-bitowy token
tl-status.phpOdpytywanie stanu blokady na żywo (co 10s) — panel wykrywa zmianę stanu bez odświeżania strony
card-secret-key.webpGrafika karty Secret Key wyświetlana w panelu
key.svgLogo systemu wyświetlane w panelu

Warstwa danych (/private/ — poza public_html)

PlikZawartość
secret-key.php$people, $downloads, $instructions, $email_notify — cały config danych i treści
lang.phpTeksty interfejsu — jeden język, patrz Teksty interfejsu (lang.php)
rate-limit.phpTrwały rate-limiting (liczniki prób logowania niezależne od sesji)
rate_limits.jsonLiczniki prób logowania per IP/konto (tworzy się automatycznie)
trusted_devices.jsonTokeny zaufanych urządzeń (SHA-256, TTL 7 dni) — tworzy się automatycznie
timelock.jsonStan blokady czasowej (48h) i Panic Button — tworzy się automatycznie po pierwszej udanej rekonstrukcji hasła, patrz Ochrona przed zmową powierników
secret-key.logLogi zdarzeń (logowania, błędy, 2FA)
moja-baza-hasel.kdbx (i inne pliki do pobrania)Pliki serwowane wyłącznie przez download.php — nigdy bezpośrednio przez HTTP, bo leżą poza public_html
⚠️
Wrażliwe pliki poza katalogiem publicznym
Wszystkie pliki konfiguracyjne przechowywane są poza public_html — błędna konfiguracja serwera WWW nie grozi ich ujawnieniem przez HTTP.
System

Bezpieczeństwo

Dziewięć niezależnych warstw ochrony — kompromitacja jednej nie daje dostępu do systemu.

#Warstwy ochrony

01 🔒 Hasła (bcrypt) cost=10, format $2y$, weryfikacja odporna na ataki czasowe (hash_equals())
02 🛡️ CSRF Token 64 hex znaków z bin2hex(random_bytes(32)), weryfikowany na wszystkich endpointach zmieniających stan (logowanie, weryfikacja 2FA, ponowna wysyłka kodu, log zdarzeń)
03 🚫 Brute-force 3 próby/IP + 3 próby/konto w oknie 15 min; 3 błędne kody SMS/godz. Liczniki trwałe po stronie serwera (plik, niezależny od sesji/cookies klienta) — nie da się ich zresetować bez odczekania okna czasowego
04 📱 2FA SMS Kod 6-cyfrowy z random_int(), ważny 10 min, cooldown 60s między wysyłkami
05 💻 Trusted devices SHA-256, HttpOnly + Secure + SameSite=Strict, plik poza public_html, TTL 7 dni
06 ⏱️ Sesja Ciasteczko sesji z jawnie ustawionymi flagami HttpOnly + Secure + SameSite=Strict (session_set_cookie_params()). Auto-logout 30 min, session_regenerate_id() po każdej weryfikacji, dwuetapowa: pending_2falogged_in
07 🖥️ Ochrona interfejsu Detekcja otwarcia DevTools (embedded devtools-detector, bez CDN). Po wykryciu — fizyczne usunięcie węzłów DOM z drzewa (podmiana na komentarze, nie ukrycie przez CSS) i pełnoekranowy ekran ostrzegawczy z licznikiem czasu; każde otwarcie/zamknięcie rejestrowane w logu z IP, numerem referencyjnym (REF#) i czasem trwania. Dodatkowo: blokada zaznaczania/kopiowania/przeciągania obrazów, kontekstowego menu na grafikach i czyszczenie schowka po PrintScreen
08 📥 Bramkowane pobieranie download.php + biała lista budowana z configu. Pliki do pobrania leżą poza public_html i nie mają bezpośredniego URL — wymagana aktywna sesja, log zawsze po stronie serwera, niezależna twarda walidacja stanu blokady czasowej (patrz warstwa 09)
09 ⏳ Blokada czasowa (Timelock) 48h blokada fizycznego pobierania plików po pierwszej udanej rekonstrukcji hasła w panelu + jednorazowy link „Panic Button" do natychmiastowego, trwałego zablokowania — patrz Ochrona przed zmową powierników

#Gwarancja kryptograficzna Shamira

Niezależnie od siły wszystkich pozostałych warstw — bez wymaganej liczby udziałów odtworzenie hasła jest matematycznie niemożliwe. Nawet pełny dostęp do serwera, pliku konfiguracyjnego i logów nie ujawnia hasła głównego.

Co nie jest chronione przez system
Ataki wymagające fizycznego dostępu do serwera, ataki socjotechniczne na posiadaczy kart, błędna konfiguracja serwera WWW po stronie użytkownika — leżą poza zakresem systemu.

#Dobre praktyki wdrożeniowe

PraktykaSzczegóły
📁 Katalog /private/ poza public_htmlBezwzględnie wymagane — patrz sekcja instalacji
🔒 HTTPSSSL/TLS na całym serwerze — dane logowania i kody SMS w sieci
🔄 Aktualizacje PHPRegularnie aktualizuj do najnowszej PHP 8.x
🔑 Silne hasłaUnikalne, długie hasła dla każdego konta na kartach
🚫 secret-key.php nie publicznieNigdy nie udostępniaj pliku konfiguracyjnego
📄 Liczniki brute-force w pliku, nie w sesjiTrzymanie limitów prób logowania w $_SESSION jest pozorną ochroną — atakujący resetuje licznik po prostu nie odsyłając ciasteczka. Liczniki muszą żyć w trwałym magazynie po stronie serwera (plik z flock() lub baza), kluczowane po IP i po koncie
📥 Pliki do pobrania poza public_htmlNie linkuj bezpośrednio do bazy haseł czy innych wrażliwych plików w katalogu publicznym — nawet zaszyfrowane, ich bezpośredni URL pozwala ominąć logowanie i 2FA, a pobranie nie trafia do logu. Serwuj je przez skrypt PHP z requireLogin(), białą listą nazw i logowaniem po stronie serwera (patrz „Konfiguracja index.php" → „Bramkowane pobieranie plików")
⚠️ filesize() + fread() zamiast stream_get_contents()Przy odczycie plików pod blokadą (flock()) unikaj filesize() — pod PHP-FPM (workery wielokrotnego użytku) wynik bywa cache'owany między żądaniami i może zwrócić nieaktualny rozmiar po modyfikacji pliku przez inny request, ucinając odczyt. Użyj stream_get_contents($fp), które czyta bezpośrednio z uchwytu aż do EOF, niezależnie od cache'u stat()
🌐 Wyjątek w CDN dla cache'u całej stronyJeśli domena ma włączony cache na poziomie CDN dla całej strony (np. „Cache Everything" w Cloudflare), dodaj regułę „bypass" dla ścieżki systemu przed ogólną regułą cache — taki tryb domyślnie ignoruje nagłówki Cache-Control z originu. Patrz „Instalacja krok po kroku" → „Wyjątek w CDN/cache"
🔐 Klucz sprzętowy (YubiKey/FIDO2) jako 2FA bazy hasełNiezależnie od Timelocka i Panic Buttona — dla maksymalnego poziomu ochrony zabezpiecz samą bazę haseł (KeePassXC i inne menedżery to wspierają) kluczem sprzętowym jako drugim składnikiem. Wtedy sama znajomość hasła głównego, nawet odzyskanego przez powierników, nie wystarcza do otwarcia bazy
System

Ochrona przed zmową powierników

48-godzinna blokada czasowa pobierania plików z alertem e-mail i przyciskiem awaryjnym („Panic Button"), chroniąca przed zmową wyznaczonych powierników za życia właściciela.

#Dlaczego to potrzebne

Algorytm Shamira matematycznie pozwala wyznaczonym osobom odtworzyć samo hasło główne, jeśli się w tym celu zmówią — to nieunikniona własność każdego progowego systemu dzielenia sekretu, nie tylko tego. Do wersji z tym mechanizmem jedyną barierą przed nadużyciem były powiadomienia email wysyłane po fakcie — właściciel dowiadywał się o logowaniu, ale nic nie stało na przeszkodzie natychmiastowemu pobraniu bazy haseł. System dodatkowo blokuje fizyczny dostęp do plików na 48 godzin od momentu odzyskania hasła, dając właścicielowi realny czas na reakcję.

⚠️
Nie da się zablokować samego odzyskania hasła
System nie walczy z matematyką Shamira — zakłada, że powiernicy mogą odzyskać samo hasło, nawet poza panelem (np. na iancoleman.io/shamir). Bramkowany jest za to fizyczny plik z bazą haseł: dopiero pobranie pliku jest tym, co ta funkcja realnie opóźnia i o czym alarmuje właściciela.

#Silniejsza weryfikacja odzyskanego hasła

Wcześniejszy warunek sukcesu sprawdzał tylko, czy rekonstrukcja Shamira nie rzuciła wyjątku — czyste dzielenie sekretu nie ma wbudowanej integralności, więc błędne lub niewystarczające udziały i tak niemal zawsze zwracają jakiś niepusty, losowy ciąg zamiast błędu. System nie odróżniał więc „poprawnie odzyskanego hasła" od kryptograficznego śmiecia — a to właśnie ten sygnał uzbraja blokadę czasową opisaną niżej. Dodane zostały dwie niezależne metody weryfikacji:

MetodaJak działa
Spójność podzbiorówWłasność matematyczna Shamira: każdy podzbiór udziałów o rozmiarze ≥ próg z tego samego podziału musi zrekonstruować identyczny sekret. Jeśli powiernicy podali więcej udziałów niż ściśle wymagane minimum, sprawdzane jest empirycznie (leave-one-out) — pominięcie dowolnego pojedynczego udziału nadal musi dać ten sam wynik. Matematycznie pewne, nie heurystyka.
Test formatu wynikuFallback stosowany dla dokładnie progowej liczby udziałów (brak nadmiaru do porównania metodą wyżej). Wymaga, żeby wszystkie znaki złożonego sekretu należały do „sensownych" kategorii Unicode (litera/cyfra/interpunkcja/symbol/spacja) — obsługuje pełny zakres języków (polskie znaki diakrytyczne, cyrylica, CJK), nie tylko ASCII. Im dłuższy sekret, tym mocniejszy test.

#Cztery stany panelu pobierania

StanKiedyZachowanie panelu
APrzed odszyfrowaniemPrzyciski pobierania wyszarzone, bez href (żeby przeglądarka nie pokazywała podglądu URL), z dymkiem po najechaniu wyjaśniającym dlaczego są nieaktywne
BTrwa odliczanie 48hCała karta (ikona + tytuł + opis + przyciski) rozmyta, z wyśrodkowanym, żywym licznikiem (00h 00m 00s, aktualizowanym co sekundę). Aktywuje się natychmiast po udanym odszyfrowaniu, bez odświeżania strony
CZablokowane przez Panic ButtonTa sama rozmyta karta, zamiast licznika czerwony komunikat o trwałej blokadzie
DMinęło 48hNormalne, aktywne przyciski, jak przed wprowadzeniem tego mechanizmu

Panel na żywo wykrywa zmianę stanu (tl-status.php, odpytywanie co 10s) — Panic Button kliknięty w innej karcie, upływ czasu czy ręczny reset configu odświeżają widok automatycznie, bez akcji użytkownika.

#Panic Button — alert e-mail i jednorazowy link blokujący

Pierwsza potwierdzona udana rekonstrukcja sekretu w panelu automatycznie: tworzy stan blokady (private/timelock.json, 48h okno), loguje zdarzenie TIMELOCK ARMED i wysyła e-mail alarmowy do właściciela (patrz Konfiguracja treści i danych → Powiadomienie email). Mail zawiera jednorazowy link panic.php?token=... (token 256-bitowy, bez wymogu logowania — właściciel może nie mieć jak się szybko zalogować w sytuacji kryzysowej). Kliknięcie natychmiast i trwale blokuje pobieranie, niezależnie od tego, czy 48h już minęło, czy nie.

download.php waliduje stan niezależnie od panelu
Bezpośrednie wejście na URL z pominięciem interfejsu też respektuje blokadę — kod 423 w trakcie odliczania, 403 po Panic Button — z pełną, stylistycznie spójną stroną błędu zamiast surowego kodu HTTP.
💡
Odpowiedź do przeglądarki leci przed wysyłką maila
Uzbrojenie licznika (odpowiedź JSON do panelu) wysyłane jest przed wysyłką maila — przez fastcgi_finish_request() (dostępne pod PHP-FPM na większości hostingów) z fallbackiem opartym o flush(). Dzięki temu czas wysyłki przez SMTP nie opóźnia startu licznika na ekranie.

#Reset po Panic Button — automatyczny, bez ręcznej ingerencji

Ważność timelock.json jest powiązana z odciskiem (hashem) aktualnego $people w secret-key.php. Gdy właściciel po incydencie zmieni hasło główne i wygeneruje nowy config (co i tak musi zrobić), stary timelock automatycznie traci ważność przy najbliższym odświeżeniu panelu — bez potrzeby ręcznego kasowania pliku przez FTP/SSH w stresującej, powypadkowej sytuacji.

Nic do zrobienia ręcznie po incydencie
Wystarczy standardowa procedura: wygeneruj nowe hasło główne w zakładce Szyfrowanie, nowe udziały, nowy secret-key.php w zakładce Konfiguracja i wgraj go na serwer. Stary timelock.json (razem ze starą blokadą) przestaje obowiązywać automatycznie, bo nie pasuje już do odcisku nowej konfiguracji.
System

Struktura plików

Kompletne drzewo plików projektu — zarówno pliki serwera jak i lokalnych narzędzi offline.

#Pliki na serwerze

app/ # Publiczny — system logowania ├── decrypt/ # Chroniony — panel użytkownika │ ├── .htaccess │ ├── arm-timelock.php ← Uzbraja blokadę 48h + mail alarmowy │ ├── card-secret-key.webp │ ├── devtools-log.php ← Logowanie prób otwarcia DevTools │ ├── download.php ← Bramkowane pobieranie plików │ ├── favicon.ico │ ├── index.php ← Panel odszyfrowania + Shamir JS │ ├── key.svg │ ├── log.php ← Logowanie zdarzeń │ ├── panic.php ← Obsługa linku Panic Button │ ├── timelock.php ← Biblioteka logiki stanu blokady │ └── tl-status.php ← Odpytywanie stanu na żywo (co 10s) ├── .htaccess ├── auth.php ← Autoryzacja + t(), md_lite(), empty_state_box() ├── favicon.ico ├── key.svg ├── login.php ← Formularz logowania ├── logout.php ← Wylogowanie ├── resend.php ← Ponowna wysyłka SMS └── verify.php ← Weryfikacja kodu SMS

#Pliki poza public_html

/home/user/private/ ← POZA public_html ! ├── secret-key.php ← $people, $downloads, $instructions, $email_notify... ├── lang.php ← Teksty interfejsu — jeden język, patrz t() ├── rate-limit.php ← Trwały rate-limiting (niezależny od sesji) ├── rate_limits.json ← Liczniki prób logowania (tworzy się automatycznie) ├── trusted_devices.json ← Tokeny zaufanych urządzeń (tworzy się automatycznie) ├── timelock.json ← Stan blokady czasowej (tworzy się automatycznie po pierwszej rekonstrukcji) ├── secret-key.log ← Logi zdarzeń └── moja-baza-hasel.kdbx ← Pliki do pobrania — serwowane tylko przez download.php
ℹ️
secret-key.php trzyma teraz cały config, nie tylko hasła
Poza loginami i hashami haseł ($people), ten sam plik trzyma listę plików do pobrania ($downloads), kroki instrukcji ($instructions), teksty sekcji pobierania i ustawienia powiadomienia email ($email_notify). Wszystko generuje dashboard — patrz Konfiguracja treści i danych.
ℹ️
rate-limit.php i rate_limits.json
rate-limit.php udostępnia funkcje rateLimitCheckAndIncrement() i rateLimitReset(), wymagane przez auth.php do trwałego liczenia prób logowania (plik, nie sesja). Katalog private/ musi mieć prawo zapisu — rate_limits.json tworzy się automatycznie przy pierwszej próbie logowania, nie trzeba go zakładać ręcznie.
ℹ️
timelock.json
Nie istnieje dopóki nikt nie odzyska hasła w panelu — tworzy się automatycznie przy pierwszej udanej rekonstrukcji sekretu. Ważność jest powiązana z odciskiem (hashem) aktualnego $people, więc regeneracja secret-key.php automatycznie unieważnia stary plik. Szczegóły: Ochrona przed zmową powierników.

#Pliki lokalne (narzędzia offline)

# Otwierasz lokalnie w przeglądarce (file://) ├── dashboard.html ← Główny panel (iframe z zakładkami) ├── generate-hash.html ← Generator konfiguracji bcrypt ├── generate-shamir.html ← Generator udziałów Shamira ├── generate-card.html ← Generator kart PDF ├── card-front.js ← Szablon SVG przodu karty (~553KB) ├── card-back.js ← Szablon SVG tyłu karty (~41KB) └── favicon.ico
ℹ️
card-front.js i card-back.js
Muszą znajdować się w tym samym folderze co generate-card.html. Zawierają szablony SVG kart jako zmienne JS (CARD_FRONT_TPL, CARD_BACK_TPL) ładowane przez <script src="...">.
Instalacja

Wymagania

Co potrzebujesz, żeby uruchomić Secret Key na własnym serwerze.

#Serwer

WymaganieMinimalna wersjaUwagi
PHP8.0+Wymagane funkcje: password_hash(), random_bytes(), random_int()
Serwer WWWApache lub NginxMusi wspierać .htaccess lub odpowiednik dla ochrony /private/
HTTPSTLS 1.2+Obowiązkowe — dane logowania i kody SMS
Katalog poza public_htmlNiezbędny dla pliku secret-key.php

#Konto SMSPlanet

System wysyła kody 2FA przez SMSPlanet API. Wymagane:

ElementOpis
Konto SMSPlanetAktywne konto z saldem na wysyłkę SMS
Token APIWpisywany do secret-key.php podczas konfiguracji
Numer telefonuJeden unikalny numer per wyznaczona osoba

Treść SMS-a i autouzupełnianie kodu (WebOTP)

Treść wiadomości SMS składa się z dwóch części z osobnych źródeł: sam tekst kodu pochodzi z lang.php (klucz twofa_sms_body, przez t() — dokładnie ten sam mechanizm vsprintf co przy verify_attempts_left_*), a przyrostek do autouzupełniania WebOTP budowany jest w auth.php ze stałej ustawionej w secret-key.php:

php
// secret-key.php
define('SMS_AUTOFILL_DOMAIN', '@moja-domena.pl');

// private/lang.php — %s = kod, %d = ważność w minutach
'twofa_sms_body' => 'Kod weryfikacyjny: %s. Wazny %d min. Nie udostepniaj go nikomu.',

// auth.php — treść z lang.php + przyrostek WebOTP z secret-key.php
$msg = t('twofa_sms_body', $code, $ttlMin) . "\n\n" . SMS_AUTOFILL_DOMAIN . " #$code";

Ostatni fragment (@domena #kod) to nie literówka ani ozdobnik — to format wymagany przez WebOTP API. Dzięki niemu przeglądarka na telefonie (Chrome/Android, Safari/iOS) sama rozpoznaje kod w przychodzącym SMS-ie i automatycznie wypełnia pole na stronie logowania, bez kopiowania go ręcznie z aplikacji SMS.

⚠️
Bez polskich znaków w treści SMS-a
Wartość twofa_sms_body w lang.php jest celowo zapisana bez polskich znaków diakrytycznych (ż/ą/ę/ć...) — SMS zawierający choćby jeden znak spoza zestawu GSM-7 jest rozliczany przez operatora jako droższy/krótszy limit znaków na wiadomość (czasem podział na kilka SMS-ów). Tłumacząc ten klucz na inny język, zachowaj tę samą zasadę — unikaj znaków diakrytycznych w treści SMS-a, nawet jeśli reszta lang.php ich używa bez ograniczeń.
Zmień domenę na swoją — inaczej autouzupełnianie nie zadziała
SMS_AUTOFILL_DOMAIN musi być dokładną domeną, pod którą faktycznie hostujesz system (bez https://, bez ścieżki). WebOTP porównuje tę wartość z domeną strony, na której użytkownik jest zalogowany — jeśli się nie zgadzają, przeglądarka po prostu zignoruje SMS i nie zaproponuje autouzupełnienia. Kod nadal dotrze i zadziała po ręcznym wpisaniu, ale wygoda automatycznego wypełnienia zniknie. Szczegóły — patrz Konfiguracja treści i danych.

#Narzędzia lokalne (offline)

WymaganieOpis
Nowoczesna przeglądarkaChrome / Firefox / Edge — do otwierania dashboard.html przez file://
Brak połączenia z internetemWszystkie narzędzia działają offline (poza fontami Google przy pierwszym załadowaniu)
💡
Chrome zalecany do generowania PDF
Generator kart używa window.open() + window.print() — jedynej metody działającej przez file:// bez ograniczeń CORS. Chrome obsługuje to najlepiej.
Instalacja

Instalacja krok po kroku

Kompletny przewodnik od pobrania plików do działającego systemu na własnym serwerze.

#1. Konfiguracja offline

Pobierz pliki projektu i otwórz dashboard.html lokalnie w Chrome.

Zakładka Konfiguracja

1
Dodaj wszystkie wyznaczone osoby

Dla każdej osoby podaj: unikalny login, silne hasło, imię, nazwisko, numer telefonu komórkowego (wybierz kraj z rozwijanej listy — flaga i kierunkowy — a obok wpisz sam numer lokalny, bez kierunkowego) i czy ma być widoczna na liście posiadaczy w panelu.

2
Uzupełnij pliki, instrukcję i powiadomienia

Klucz/etykietę/nazwę pliku dla każdego pliku do pobrania, kroki instrukcji panelu (z formatowaniem **pogrubienie**/*kursywa*), oraz adres i domenę powiadomienia email.

3
Ustaw domenę SMS i wygeneruj

Wpisz domenę do autouzupełniania SMS (własna domena z @ na początku), a następnie kliknij „Generuj konfigurację" — hasła są hashowane bcrypt w przeglądarce. Pobierz secret-key.php.

Zakładka Szyfrowanie

1
Podziel hasło główne

Wpisz hasło do bazy KeePassXC, ustaw parametry (zalecane: 5 udziałów, próg 3), wygeneruj i pobierz secret-key-shares.txt.

#2. Wgranie plików na serwer

bash
# Wgraj folder /app/ do public_html (zawiera też /decrypt/ wewnątrz)
scp -r ./app/ user@serwer:~/public_html/

# Wgraj konfigurację i teksty interfejsu POZA public_html
scp ./secret-key.php ./lang.php user@serwer:~/private/
⚠️
lang.php musi trafić na serwer razem z secret-key.php
auth.php ładuje lang.php przez require_once — jeśli plik nie trafi do /private/, cały system przestanie się ładować (fatal error na każdej stronie), nie tylko przestaną działać tłumaczenia. Wgrywaj oba pliki w tej samej operacji.

#2a. Wyjątek w CDN/cache (jeśli używasz Cloudflare lub podobnego)

Jeśli Twoja domena ma włączony cache na poziomie całej strony (np. reguła typu „Cache Everything" w Cloudflare, albo podobny mechanizm u innego dostawcy CDN), musisz dodać wyjątek dla katalogu, w którym stoi system, zanim zaczniesz cokolwiek testować.

Dlaczego to nie wystarczy załatwić samym .htaccess
System wysyła własne nagłówki Cache-Control: no-store (patrz app/.htaccess i app/decrypt/.htaccess), ale tryb „Cache Everything" w Cloudflare domyślnie ignoruje nagłówki Cache-Control z originu — cache'uje odpowiedź niezależnie od tego, co wysyła serwer. Bez osobnej reguły „bypass" na poziomie CDN, panel logowania, kod 2FA i strona z odszyfrowanym hasłem mogą zostać zapisane w cache'u i serwowane innym odwiedzającym.

Przykład dla Cloudflare (Caching → Cache Rules):

1
Utwórz nową regułę cache

Nazwa np. „Bypass cache — Secret Key". Warunek: URI Path contains /app/ (podmień na rzeczywistą ścieżkę, pod którą wgrałeś system).

2
Ustaw akcję na „Bypass cache"

To wyłącza cache CDN dla wszystkiego pod tą ścieżką — niezależnie od nagłówków wysyłanych przez origin.

3
Ustaw regułę jako pierwszą w kolejności

Jeśli masz też ogólną regułę cache'ującą całą domenę, reguła „bypass" musi być wyżej na liście (kolejność „First") — reguły cache wykonują się po kolei i wygrywa pierwsze trafienie.

💡
Jak sprawdzić, czy zadziałało
Otwórz panel logowania w przeglądarce, zakładka Sieć/Network w narzędziach deweloperskich, sprawdź nagłówki odpowiedzi. Cloudflare dodaje nagłówek cf-cache-status — dla ścieżek objętych wyjątkiem powinien pokazywać BYPASS lub DYNAMIC, nigdy HIT.

#3. Konfiguracja ścieżki

W pliku app/auth.php zaktualizuj ścieżkę do pliku konfiguracyjnego:

php
// Zmień na rzeczywistą ścieżkę do /private/ na Twoim serwerze
require_once '/home/twoj_uzytkownik/private/secret-key.php';

#3a. Tryb testowy SMS

Przed wdrożeniem warto sprawdzić czy system działa poprawnie — bez wysyłania prawdziwych SMS-ów i zużywania tokenów SMSPlanet. Plik app/auth.php zawiera wbudowany tryb testowy.

php
// ── TRYB TESTOWY — usuń przed wdrożeniem na produkcję! ──
// $smsResult = true; $code = '123456';
// ────────────────────────────────────────────────────────

$smsResult = sendSmsCode($phoneFull, $code);

Aby włączyć tryb testowy — odkomentuj linię z $smsResult = true i zakomentuj wywołanie sendSmsCode():

php
// ── TRYB TESTOWY — usuń przed wdrożeniem na produkcję! ──
$smsResult = true; $code = '123456';  // ← odkomentuj
// ────────────────────────────────────────────────────────

// $smsResult = sendSmsCode($phoneFull, $code);  // ← zakomentuj

Po włączeniu trybu testowego — kod weryfikacyjny SMS będzie zawsze wynosił 123456, niezależnie od numeru telefonu. Możesz spokojnie przetestować cały przepływ logowania bez kosztów.

Usuń tryb testowy przed wdrożeniem na produkcję
Pozostawienie odkomentowanego $smsResult = true na produkcji całkowicie wyłącza weryfikację 2FA — każdy będzie mógł zalogować się wpisując kod 123456. Przed udostępnieniem systemu wyznaczonym osobom przywróć oryginalne wywołanie sendSmsCode().
💡
Schemat testowania
Zalecana kolejność: włącz tryb testowy → przetestuj logowanie wszystkimi kontami z kart → sprawdź czy rekonstrukcja Shamira zwraca poprawne hasło → wyłącz tryb testowy → rozdaj karty wyznaczonym osobom.

#4. Generowanie i dystrybucja kart

1
Otwórz Generator kart

W dashboard.html przejdź do zakładki Generator kart. Upewnij się, że card-front.js i card-back.js są w tym samym folderze.

2
Uzupełnij dane

Wpisz adres instancji, loginy, hasła i udziały Shamira z pliku secret-key-shares.txt dla każdej osoby.

3
Generuj i drukuj

Kliknij „Generuj karty", następnie „Pobierz PDF wszystkich kart". Wydrukuj i rozdaj wyznaczonym osobom. Zalecane: laminowanie kart.

Instalacja zakończona
System jest aktywny. Przetestuj logowanie jedną z kart — wejdź na adres instancji, zaloguj się danymi z karty i zweryfikuj kod SMS. Następnie sprawdź rekonstrukcję hasła w panelu odszyfrowania.
Instalacja

Konfiguracja treści i danych

Wszystko na tej stronie generuje dashboard.html (zakładka Konfiguracja) do jednego pliku — secret-key.php. Poniżej opisane jest, co faktycznie trafia do configu i po co, żebyś wiedział, co wypełniasz w formularzu (albo co zmieniasz, jeśli wolisz poprawić secret-key.php ręcznie).

Jedno źródło prawdy
Cała treść i dane panelu — osoby, pliki do pobrania, instrukcja, powiadomienia — trzyma jeden plik: secret-key.php. Zmieniasz dane w jednym miejscu, a panel, przyciski pobierania i biała lista w download.php aktualizują się automatycznie.

#1. Osoby ($people)

$people to jedna tablica z jednym rekordem na osobę — login, hasło, dane kontaktowe i widoczność w panelu w jednym miejscu.

php
$people = [
  [
    'login'         => 'anna',
    'password'      => '$2y$10$...',  // hash bcrypt — generowany przez dashboard
    'first_name'    => 'Anna',
    'last_name'     => 'Nowak',
    'phone_cc'      => '+48',          // kierunkowy — osobno, dowolny kraj
    'phone'         => '123456789',   // sam numer lokalny, bez kierunkowego
    'show_in_panel' => true,
  ],
  // kolejne osoby...
];
PoleOpis
loginLogin do panelu — unikalny, wpisywany przez osobę na karcie
passwordHash bcrypt hasła. Dashboard hashuje w przeglądarce — nigdy nie wpisujesz tu hasła jawnego
first_name / last_nameWyświetlane w powitaniu panelu i na liście posiadaczy
phone_ccKierunkowy kraju w formacie +XX (np. +48, +49, +31) — osobne pole, niezależne od phone. Dzięki temu system działa poprawnie dla dowolnego kraju, nie tylko Polski
phoneSam numer lokalny do kodu 2FA, bez kierunkowego (ten jest w phone_cc). Z obu tych pól liczone są oba warianty wyświetlania (pełny na liście posiadaczy — bez kierunkowego, zamaskowany na ekranie logowania — z prawdziwym kierunkowym)
show_in_panelfalse = konto loguje się normalnie, ale nie pojawia się na liście posiadaczy. Przydatne dla kont bez fizycznej karty (np. administracyjnych)
⚠️
Migracja ze starszej wersji configu
Wcześniejsze wersje trzymały cały numer w jednym polu phone razem z kierunkowym (np. 'phone' => '+48123456789'). Jeśli aktualizujesz istniejący secret-key.php, rozdziel to ręcznie na dwa pola: 'phone_cc' => '+48' i 'phone' => '123456789' — dla każdej osoby osobno. Bez tej migracji logowanie 2FA nie zadziała poprawnie (kod SMS pójdzie na niekompletny numer).
ℹ️
Liczba osób w $people nie musi się zgadzać z liczbą udziałów Shamira
$people to lista kont logowania do panelu — kto może wejść i kto widnieje na liście posiadaczy do skontaktowania. To osobna sprawa od liczby udziałów Shamira (kart), które odtwarzają hasło główne. Można mieć np. 6 kont w $people (w tym jedno administracyjne z show_in_panel => false) i 5 udziałów Shamira dla 5 z nich.
⚠️
Pusta lista posiadaczy = czerwony alert w panelu
Jeśli wszystkie osoby mają show_in_panel => false (albo $people jest puste), panel nie zostaje po prostu pusty — pokazuje widoczny, czerwony komunikat z podpowiedzią, co skonfigurować. To celowe: łatwiej złapać błąd configu na etapie testowania niż domyślać się, dlaczego lista jest pusta.

#2. Pliki do pobrania ($downloads)

$downloads to jedno źródło zarówno dla przycisków pobierania w panelu, jak i białej listy w download.php — to dosłownie ta sama tablica, więc oba miejsca są zawsze zgodne ze sobą.

php
$downloads = [
  ['key' => 'baza-hasel', 'label' => 'Pobierz bazę haseł',     'filename' => 'moja-baza-hasel.kdbx'],
  ['key' => 'aegis',      'label' => 'Pobierz bazę Aegis 2FA', 'filename' => 'moja-baza-aegis.json'],
  ['key' => 'program',    'label' => 'Pobierz KeePassXC',      'filename' => 'KeePassXC-2.7.9-Win64.msi', 'name' => 'KeePassXC'],
];
PoleOpis
keyIdentyfikator w adresie pobierania (download.php?file=klucz). Musi być unikalny
labelTekst na przycisku w panelu
filenameRzeczywista nazwa pliku leżącego w /private/
nameOpcjonalne. Obecność tego klucza oznacza „to jest instalator programu do odszyfrowywania" — jego wartość trafia do nagłówka panelu („Hasło do bazy X")
Sam plik nadal wgrywasz ręcznie
$downloads trzyma tylko metadane — klucz, etykietę i nazwę pliku. Sama zawartość (baza haseł, baza 2FA, instalator programu) nigdy nie przechodzi przez dashboard ani przez ten config. Musisz osobno wgrać każdy plik na serwer do /private/, pod dokładnie taką nazwą jak w polu filename.

Obok samej listy plików, trzy dodatkowe zmienne kontrolują teksty tej sekcji panelu:

php
$download_heading = 'Pliki i program do odzyskania dostępu';  // zwykły tekst, nagłówek <h3>
$download_intro = 'Pobierz bazę haseł, bazę kodów 2FA oraz program...';  // zwykły tekst, akapit nad przyciskami
$alert_box_text = 'Pamiętaj: bez fizycznego klucza YubiKey...';  // zwykły tekst, niebieski box z ikoną
⚠️
Nagłówek i przynajmniej jeden plik są wymagane razem
Jeśli $download_heading jest puste albo $downloads jest puste, cała siatka przycisków zamienia się w czerwony alert z instrukcją, co uzupełnić — nawet jeśli tylko jedna z tych dwóch rzeczy brakuje. To zamierzone: sekcja bez tytułu albo bez plików nie ma sensu wyświetlać częściowo. $download_intro zostawiony pusty po prostu nie pokaże akapitu, a $alert_box_text pusty całkowicie ukrywa niebieski box — żadne z tych dwóch nie blokuje przycisków.
ℹ️
Dopasuj $download_heading do liczby plików
Polska odmiana liczebników nie jest automatyzowana — jeśli masz tylko jeden plik do pobrania (np. samą bazę haseł + program, bez osobnej bazy 2FA), wpisz „Plik i program do odzyskania dostępu" zamiast „Pliki i program...". To zwykłe pole tekstowe, w pełni pod Twoją kontrolą.

#3. Instrukcja ($instructions)

Kroki instrukcji w panelu też są danymi, nie kodem HTML. Każdy krok to jedno pole tekstowe z prostym formatowaniem — markdown-lite.

php
$instructions = [
  ['num' => '01', 'text' => '**Zbierzcie się razem.** Skontaktujcie się z osobami z listy — potrzebujecie minimum **3 osoby z 5**.'],
  ['num' => '02', 'text' => '**Wejdźcie na tę stronę razem.** Każda osoba potrzebuje swojej karty (np. *8015c7c4...*).'],
  // kolejne kroki...
];
FormatowanieEfekt
**tekst**Pogrubienie (<strong>) — użyj np. na tytuł kroku na początku zdania
*tekst*Kursywa (<em>) — użyj np. na przykładowy kod czy nazwę pliku
ℹ️
To wyłącznie markdown-lite, nic więcej
Obsługiwane są tylko te dwa znaczniki — żadnych list, nagłówków ani linków. Renderowanie robi funkcja md_lite() w auth.php: najpierw htmlspecialchars() całego tekstu (więc wpisany przez pomyłkę znacznik HTML wyrenderuje się jako martwy tekst, nie jako kod), dopiero potem podmiana **/* na tagi.
⚠️
Puste $instructions = czerwony alert zamiast listy kroków
Ten sam wzorzec co przy liście posiadaczy i plikach do pobrania — brak choćby jednego kroku pokazuje widoczne ostrzeżenie w panelu zamiast po prostu pustego miejsca.

#4. Powiadomienie email ($email_notify)

Panel może wysyłać email po każdym udanym logowaniu — z adresem IP, przeglądarką i czasem. Wszystkie dane nadawcy/odbiorcy trzyma jedna tablica, żeby domena nie powtarzała się osobno w nagłówkach From, Reply-To, Return-Path, X-Sender i Message-ID.

php
$email_notify = [
  'enabled'    => true,
  'to'         => 'jan@moja-domena.pl',
  'from_email' => 'no-reply@moja-domena.pl',
  'from_name'  => 'Secret Key',
  'panel_url'  => 'https://moja-domena.pl',
];
PoleOpis
enabledfalse wyłącza wysyłkę całkowicie — bez ruszania kodu
toAdres, na który przychodzi powiadomienie o każdym logowaniu
from_emailAdres nadawcy. Domena stąd trafia też automatycznie do Message-ID
from_nameNazwa nadawcy widoczna w kliencie pocztowym
panel_urlLink do panelu wklejany w treści wiadomości
text
Temat: 🔐 Logowanie do Secret Key Panel — Jan Kowalski

Nowe logowanie do panelu Secret Key.

─────────────────────────────
Użytkownik:   Jan Kowalski (jan)
Data i czas:  01.05.2026 14:32:17
Adres IP:     89.123.45.67
Przeglądarka: Mozilla/5.0 (Windows NT 10.0...)
─────────────────────────────

Panel: https://moja-domena.pl
ℹ️
Email wysyłany jest raz po zalogowaniu
Powiadomienie jest wysyłane jednorazowo po poprawnym przejściu weryfikacji 2FA — nie blokuje przekierowania do panelu ani nie spowalnia logowania. Jeśli enabled => false, nic nie jest wysyłane ani logowane — to świadome wyłączenie. Jeśli enabled => true, ale brakuje to lub from_email, w logu pojawi się MAIL SKIPPED — to sygnał błędu configu, nie cichej awarii. Jeśli wysyłka się faktycznie nie powiedzie (np. serwer bez funkcji mail()), zapisywane jest MAIL FAILED.
enabled => false wyłącza też alert Panic Button
Te same dane nadawcy/odbiorcy z $email_notify (to, from_email, from_name) są używane przez arm-timelock.php do wysyłki alertu bezpieczeństwa z linkiem Panic Button, gdy ktoś po raz pierwszy odzyska hasło w panelu — patrz Ochrona przed zmową powierników. Ta wysyłka respektuje to samo pole enabled co zwykłe powiadomienie o logowaniu (log TIMELOCK MAIL SKIPPED, jeśli wyłączone) — jeśli wyłączysz powiadomienia email, nie dostaniesz też alertu o odzyskaniu hasła ani linku blokującego. Blokada czasowa (48h) sama w sobie uzbraja się zawsze, niezależnie od enabled — traci się tylko mail z linkiem Panic Button do natychmiastowego, wcześniejszego zablokowania.
⚠️
Aby uniknąć folderu spam
Użyj adresu no-reply@ na tej samej domenie co serwer. Jeśli Twój serwer ma skonfigurowane rekordy SPF i DKIM — emaile będą trafiać do skrzynki głównej.

#5. Autouzupełnianie kodu SMS

Domena widoczna w treści SMS-a (do automatycznego wypełniania kodu na Android/iOS — patrz strona Wymagania) też jest teraz configiem, nie ręczną edycją auth.php:

php
define('SMS_AUTOFILL_DOMAIN', '@moja-domena.pl');
Musi zaczynać się od @ i być dokładną domeną
Bez https://, bez ścieżki — sama domena, na której faktycznie hostujesz system. WebOTP porównuje tę wartość z domeną strony logowania; jeśli się nie zgadzają, przeglądarka po prostu nie zaproponuje autouzupełnienia (kod nadal zadziała po ręcznym wpisaniu).

#6. Wymagany próg odzyskiwania (SHARES_REQUIRED)

Panel odzyskiwania (/decrypt/index.php) musi wiedzieć, ile kodów z kart wymaganych jest do złożenia hasła — czyli ten sam próg (K), który ustawiasz w zakładce dashboardu „Szyfrowanie" przy dzieleniu hasła głównego na udziały:

php
define('SHARES_REQUIRED', 4); // np. 4 z 7
⚠️
Musi być zgodne z progiem z zakładki Szyfrowanie
Dashboard nie ma wspólnego stanu między zakładkami — nikt automatycznie nie sprawdzi, czy wartość tutaj zgadza się z tym, co wpisałeś przy generowaniu udziałów. Jeśli się rozjadą, panel będzie żądać innej liczby kodów niż realnie potrzeba do złożenia hasła. Najłatwiej ustawić to pole w zakładce Konfiguracja dashboardu (sekcja „Ustawienia podstawowe") zamiast edytować plik ręcznie.
ℹ️
Brak tej stałej w starym configu? Nic się nie dzieje
Domyślny fallback to 3 — configi sprzed wprowadzenia tego pola działają dokładnie tak jak wcześniej, bez żadnej migracji.
Instalacja

Teksty interfejsu (lang.php)

Cały widoczny tekst interfejsu — przyciski, etykiety, komunikaty błędów — jest wydzielony z kodu do jednego pliku, private/lang.php.

#Zasada działania

⚠️
To nie jest system wielojęzyczny z przełącznikiem
System celowo nie ma przełącznika języka ani osobnych plików pl.php/en.php. Jest jeden plik lang.php z jednym językiem naraz (domyślnie polski). Powód: treść, którą wpisujesz w $instructions czy $alert_box_text, jest osobista — piszesz ją dla konkretnych, znanych sobie ludzi. Tłumaczenie samego interfejsu bez przepisania tej treści na nowo nie miałoby większego sensu.

Jeśli chcesz uruchomić system w innym języku niż polski, edytujesz lang.php ręcznie — całość, jednorazowo — oraz przepisujesz treść w secret-key.php ($instructions, $download_intro, itd.) na ten sam język. To świadomy wybór prostoty zamiast rozbudowanej warstwy i18n, której i tak nikt by tu nie przełączał w locie.

#Klucze i funkcja t()

lang.php definiuje tablicę $lang — klucz w stylu snake_case na wartość tekstową. Kod odczytuje ją przez funkcję t() zdefiniowaną w auth.php:

php
// private/lang.php
$lang = [
  'login_submit_btn'   => 'Zaloguj się',
  'login_empty_fields' => 'Wpisz login i hasło.',
  // ...
];
php
// auth.php
function t(string $key, ...$args): string {
    global $lang;
    $text = $lang[$key] ?? $key;
    return $args ? vsprintf($text, $args) : $text;
}
Brakujący klucz nie wywala strony
Jeśli t('cos') odwoła się do klucza, którego nie ma w $lang, funkcja po cichu zwraca sam klucz jako widoczny tekst zastępczy — np. zobaczysz na stronie napis login_submit_btn zamiast „Zaloguj się". Nie dostaniesz błędu PHP na produkcji, a literówka w kluczu jest od razu widoczna gołym okiem, więc łatwo ją złapać przy testowaniu.

t() wspiera też placeholdery w stylu sprintf — np. do poprawnej polskiej odmiany liczby pozostałych prób:

php
// w lang.php
'verify_attempts_left_1'    => 'Pozostało %d próba.',
'verify_attempts_left_few'  => 'Pozostało %d próby.',
'verify_attempts_left_many' => 'Pozostało %d prób.',

// wywołanie
t('verify_attempts_left_few', $remaining);
⚠️
Kilka kluczy renderuje się bez htmlspecialchars()
Większość tekstów jest escapowana przy renderowaniu (bezpiecznie, jak każdy inny input). Kilka kluczy — te zawierające celowe znaczniki <strong> czy encje &nbsp; (np. tekst wprowadzający na ekranie logowania) — renderuje się bez escapowania, żeby formatowanie działało. Są jasno oznaczone komentarzem w lang.php. Edytując je, zachowaj tagi i encje w całości — dosłowny znak & wpisany jako zwykły tekst wyrenderuje się jako widoczny &amp;, nie jako spacja czy pogrubienie.

#Zmiana języka systemu

Żeby uruchomić system w innym języku:

1
Przetłumacz wszystkie wartości w lang.php

Same klucze (lewa strona =>) zostają bez zmian — tłumaczysz tylko wartości tekstowe po prawej.

2
Zmień atrybut _html_lang

$lang['_html_lang'] steruje atrybutem <html lang="..."> na obu stronach (logowanie i panel) — zmień np. z 'pl' na 'en'.

3
Przepisz własną treść w secret-key.php

$instructions, $download_heading, $download_intro, $alert_box_text — to Twoja treść, nie generyczny UI, więc lang.php jej nie obejmuje. Przepisujesz ją osobno, tym samym wzorcem co resztę configu (patrz Konfiguracja treści i danych).

Inne

FAQ

Najczęściej zadawane pytania dotyczące bezpieczeństwa, działania i wdrożenia systemu.

#Co jeśli zgubię kartę Secret Key?

Sama karta jest bezużyteczna bez dostępu do telefonu przypisanego do danego konta — logowanie wymaga weryfikacji SMS. Ryzyko jest ograniczone, jednak właściciel powinien zostać poinformowany i rozważyć wygenerowanie nowej konfiguracji z nowym zestawem udziałów. Po zastąpieniu pliku secret-key.php na serwerze, stare karty przestają działać.

#Czy posiadając kartę mogę samodzielnie odczytać hasło?

Nie. Jest to matematycznie niemożliwe. Pojedynczy udział nie ujawnia żadnej informacji o sekrecie — to własność algorytmu Shamira zwana information-theoretic security. Dopiero zebranie wymaganej liczby udziałów pozwala na odtworzenie hasła głównego.

#Co jeśli jedna z wyznaczonych osób umrze lub będzie niedostępna?

System jest zaprojektowany z nadmiarowością — wystarczy zebrać minimalną wymaganą liczbę udziałów (np. 3 z 5). Niedostępność jednej lub dwóch osób nie blokuje procedury awaryjnej.

#Czy hasło trafia na serwer podczas odszyfrowania?

Nie. Rekonstrukcja hasła z udziałów Shamira odbywa się całkowicie po stronie przeglądarki (JavaScript). Serwer służy tylko do uwierzytelnienia użytkownika — sam sekret nigdy go nie opuszcza.

#Co jeśli powiernicy zmówią się i odzyskają hasło za mojego życia, bez mojej wiedzy?

Samo hasło im nie wystarczy. Pliki bazy danych leżą poza katalogiem publicznym serwera, a dostęp do nich kontroluje download.php. W momencie pierwszej udanej rekonstrukcji hasła w panelu system automatycznie blokuje pobieranie plików na 48 godzin i wysyła Ci e-mail z alertem oraz jednorazowym linkiem „Panic Button" — jedno kliknięcie trwale odcina dostęp, dając Ci czas na zmianę hasła głównego w spokoju. Szczegóły: Ochrona przed zmową powierników. Jeśli dodatkowo zabezpieczasz bazę haseł kluczem sprzętowym (np. YubiKey), sama znajomość hasła nie wystarczy do jej otwarcia nawet po odblokowaniu plików.

#Czy mogę używać systemu z innym menedżerem haseł niż KeePassXC?

Tak. Secret Key przechowuje i odtwarza dowolne hasło główne — niezależnie od używanego menedżera. Kompatybilny z KeePassXC, Bitwarden, 1Password i każdym innym programem obsługującym hasło główne.

#Jak wybrać próg — ile udziałów jest wymaganych?

Im wyższy próg, tym większe bezpieczeństwo — ale też trudniejsze zebranie się w sytuacji kryzysowej. Rekomendowany kompromis to 3 z 5 — pozwala na niedostępność dwóch osób przy zachowaniu dobrego poziomu ochrony. Wybrany próg wpisz też jako SHARES_REQUIRED w configu (pole „Wymagane kody" w zakładce Konfiguracja dashboardu) — patrz Konfiguracja treści i danych — inaczej panel odzyskiwania będzie żądał innej liczby kodów niż realnie potrzeba.

#Jak długo ważne są dane logowania z karty?

Bezterminowo — o ile właściciel nie wygeneruje nowej konfiguracji. Po zastąpieniu pliku secret-key.php na serwerze stare karty przestają działać i konieczne jest rozdanie nowych wszystkim wyznaczonym osobom.

#Dlaczego mail z alertem bezpieczeństwa (Panic Button) czasem trafia do spamu?

Ten alert wysyłany jest automatycznie po pierwszej udanej rekonstrukcji hasła w panelu — patrz Ochrona przed zmową powierników. Taki mail zawiera jednorazowy link z długim, losowym tokenem (panic.php?token=...) — automatyczne filtry antyspamowe często oznaczają linki tego kształtu, niezależnie od treści maila, bo pod względem struktury przypominają linki phishingowe. To dotyczy tylko maila z alertem — zwykłe powiadomienia o logowaniu (bez klikalnego linku z tokenem) zwykle trafiają do odebranych bez problemu.

Rozwiązanie: dodaj adres nadawcy (domyślnie no-reply@twoja-domena, skonfigurowany w $email_notify['from_email']) do białej listy w panelu poczty, na który przychodzą powiadomienia — w cPanel jest to sekcja „Biała lista" (Email → Biała lista), gdzie można dodać zaufanego nadawcę osobno dla wybranej skrzynki. Po dodaniu wyjątku mail z Panic Buttonem trafia już bezpośrednio do odebranych.

Inne

Polityka bezpieczeństwa

Jak zgłaszać luki bezpieczeństwa i czego możesz oczekiwać w odpowiedzi.

Zwykłe błędy, literówki w dokumentacji czy propozycje ulepszeń zgłaszaj normalnie jako Issue na GitHubie — to standardowa droga i nie ma w tym nic tajnego. Ta strona dotyczy wyłącznie luk bezpieczeństwa (patrz zakres zgłoszeń niżej) — tych proszę nie zgłaszać publicznie.

Luki bezpieczeństwa zgłaszaj tylko prywatnie
Wyłącznie na dev@secretkey.website — nigdy jako publiczne Issue na GitHubie. Publiczne ujawnienie exploita przed opublikowaniem łatki naraża wszystkich, którzy mają wdrożony system.

#Jak zgłosić lukę

Wyślij szczegółowy opis na: dev@secretkey.website

Element zgłoszeniaOpis
📝 Opis lukiOpis i potencjalny wpływ na bezpieczeństwo systemu
🔁 Kroki do odtworzeniaProof of concept — minimalne kroki umożliwiające reprodukcję
🏷️ Wersja systemuKtórej wersji dotyczy luka
💡 Propozycja naprawyOpcjonalnie — sugestia rozwiązania

#Czas odpowiedzi

CzasOdpowiedź
48hPotwierdzenie otrzymania zgłoszenia
7 dniInformacja o postępach i planowanym terminie naprawy
Po naprawiePubliczne podziękowanie (jeśli sobie życzysz)

#Zakres zgłoszeń

W zakresie
Obejście uwierzytelnienia (bcrypt, 2FA) · Podatności CSRF mimo zabezpieczeń · Odtworzenie sekretu bez wymaganej liczby udziałów · Ujawnienie danych z /private/ · Obejście rate limitingu · XSS
Poza zakresem
Ataki wymagające fizycznego dostępu do serwera · Ataki socjotechniczne na posiadaczy kart · Błędna konfiguracja serwera po stronie użytkownika · Raporty skanerów bez PoC
Narzędzia offline

Przegląd dashboard

Dashboard to lokalny panel konfiguracyjny Secret Key — plik HTML otwierany bezpośrednio w przeglądarce bez serwera. Zawiera trzy zakładki: Konfiguracja, Szyfrowanie i Generator kart.

ℹ️
Działa całkowicie offline
Otwórz plik dashboard.html lokalnie w Chrome (Plik → Otwórz plik lub przeciągnij na okno przeglądarki). Żadne dane nie są wysyłane przez sieć — wszystkie operacje odbywają się wyłącznie w pamięci przeglądarki.

#Interfejs główny

Widok główny dashboard.html z widocznymi czterema zakładkami nawigacyjnymi
Widok główny dashboard.html z widocznymi czterema zakładkami nawigacyjnymi
1
Pasek zakładek
Cztery przyciski nawigacyjne u góry ekranu: Home, Konfiguracja, Szyfrowanie, Generator kart. Aktywna zakładka jest podświetlona gradientem fioletowym.
2
Obszar roboczy
Główna część ekranu renderowana jako <iframe> — każda zakładka ładuje osobny plik HTML (generate-hash.html, generate-shamir.html, generate-card.html).
3
Przełącznik języka (PL/EN)
Przypięty po prawej stronie paska zakładek, w formie animowanego suwaka (pill). Przełącza dashboard i zapamiętuje wybór w localStorage (klucz sk_lang). Wszystkie cztery pliki dashboardu — łącznie ze stroną Home (dashboard.html) — mają własny, niezależny przełącznik; nie są ze sobą synchronizowane. Zmiana języka przebudowuje już wygenerowane wyniki (hashe, podgląd configu, karty) bez ponownego przeliczania.
4
Strona Home
Ekran startowy z logo Secret Key, krótkim opisem narzędzia i dwoma skrótami — Konfiguracja i Szyfrowanie — przenoszącymi bezpośrednio do tych zakładek.

#Wymagane pliki

Wszystkie pliki muszą znajdować się w tym samym folderze:

dashboard/ ├── dashboard.html ← otwórz ten plik w przeglądarce ├── generate-hash.html ← zakładka Konfiguracja ├── generate-shamir.html ← zakładka Szyfrowanie ├── generate-card.html ← zakładka Generator kart ├── card-front.js ← szablon SVG przodu karty └── card-back.js ← szablon SVG tyłu karty
⚠️
Nie przenoś plików osobno
Dashboard używa <iframe src="..."> do ładowania zakładek. Jeśli pliki generate-*.html lub card-*.js nie znajdą się w tym samym folderze, zakładki nie załadują się poprawnie.

#Zalecana przeglądarka

PrzeglądarkaObsługaUwagi
Chrome / ChromiumZalecanaNajlepsza obsługa PDF przez window.print(), pełna obsługa file://
EdgeDziałaBazuje na Chromium — identyczna obsługa
FirefoxCzęściowaMoże wymagać zezwolenia na ładowanie lokalnych iframe
SafariNiezalecanaOgraniczona obsługa file:// i lokalnych iframe
Narzędzia offline

Zakładka: Konfiguracja

Generuje plik secret-key.php — serce systemu. Zawiera zahashowane hasła bcrypt, dane wszystkich wyznaczonych osób, pliki do pobrania, instrukcję panelu i ustawienia powiadomienia email. Operacja w pełni offline — nic nie jest nigdzie wysyłane.

#Widok zakładki

Zakładka Konfiguracja z formularzem danych wyznaczonych osób, plików do pobrania, instrukcji i powiadomień email
Zakładka Konfiguracja — formularz danych wyznaczonych osób, plików do pobrania, instrukcji i powiadomień email

#Elementy interfejsu

1
Ustawienia podstawowe
Token API SMSPlanet, nazwa nadawcy SMS, domena do autouzupełniania kodu na Android/iOS (musi zaczynać się od @ i być Twoją rzeczywistą domeną — patrz Konfiguracja treści i danych) oraz pole Wymagane kody — próg (K) udziałów Shamira wymaganych w panelu odzyskiwania, ze stepperem +/− i podpowiedzią przypominającą o zgodności z zakładką Szyfrowanie.
2
Użytkownicy systemu
Każda osoba to karta z polami: Login i Hasło (oba mają obok siebie ikony do automatycznego generowania oraz kopiowania do schowka — hasło generowane jest jako 12 znaków, duże i małe litery), Imię, Nazwisko, kierunkowy kraju (przeszukiwalna lista 186 krajów z flagami) i Telefon (sam numer lokalny, do 2FA) oraz przełącznik Widoczny w panelu — wyłącz go dla kont bez fizycznej karty (np. administracyjnych). Przycisk „+ Dodaj kolejnego użytkownika" dodaje kolejne karty, „✕ Usuń" na nagłówku karty ją zdejmuje.
3
Pliki do pobrania
Nagłówek sekcji, tekst wprowadzający i tekst ostrzeżenia (wszystkie trzy — zwykły tekst, dopasuj gramatykę do liczby plików). Dla każdego pliku: klucz (identyfikator w adresie), etykieta przycisku i pole na nazwę pliku — wskazujesz plik lokalnie tylko po to, by wpisać jego nazwę, żadna zawartość nigdzie nie jest wysyłana. Przełącznik „To instalator programu" dodaje pole „Nazwa programu", które zasila nagłówek „Hasło do bazy X" w panelu.
4
Instrukcja
Jedno pole tekstowe na krok (bez osobnych pól „tytuł"/„treść"). Wspiera formatowanie **pogrubienie** i *kursywa* — numer kroku liczy się automatycznie z kolejności kart.
5
Powiadomienie e-mail o logowaniu
Przełącznik włącz/wyłącz, adres odbiorcy, adres i nazwa nadawcy, oraz link do panelu wklejany w treści wiadomości. Wyłącznik nie kasuje wypełnionych pól — można wyłączyć i włączyć bez ponownego wpisywania danych.
6
Przycisk „Generuj konfigurację"
Hashuje wszystkie hasła algorytmem bcrypt (cost=10) w przeglądarce i generuje plik PHP z gotową konfiguracją. Przed wygenerowaniem sprawdza też, czy loginy, hasła i numery telefonów są unikalne dla każdej osoby — jeśli coś się powtarza, pokazuje błąd i podświetla konfliktujące pola zamiast utworzyć plik. Operacja może potrwać kilka sekund przy wielu osobach.
7
Podgląd i pobieranie pliku
Po generowaniu pojawia się podgląd wygenerowanych hashy oraz pełny kod PHP. Przycisk „Pobierz secret-key.php" zapisuje plik na dysk.

#Krok po kroku

1
Wpisz token SMSPlanet i domenę autouzupełniania

Zaloguj się na smsplanet.pl, przejdź do ustawień konta → API i skopiuj token. Wklej go w pierwsze pole, a w polu domeny wpisz własną domenę z @ na początku.

2
Dodaj wyznaczone osoby

Dla każdej osoby wypełnij login (unikalny, bez spacji), silne hasło (min. 12 znaków), imię, nazwisko, kierunkowy kraju (wybierz z przeszukiwalnej listy 186 krajów — flaga, nazwa, kod) i sam numer telefonu, bez kierunkowego. Zdecyduj, czy dana osoba ma się pojawić na liście posiadaczy w panelu.

3
Uzupełnij pliki do pobrania

Dodaj wpis dla każdego pliku (baza haseł, baza 2FA, instalator programu). Klucze muszą być unikalne — trafiają do adresu pobierania.

4
Napisz instrukcję i skonfiguruj powiadomienia

Dodaj kroki instrukcji dla panelu oraz — jeśli chcesz dostawać maila o każdym logowaniu — uzupełnij sekcję powiadomień email.

5
Generuj i pobierz plik

Kliknij „Generuj konfigurację". Po zakończeniu hashowania pobierz secret-key.php i zachowaj go bezpiecznie — trafi na serwer poza katalogiem publicznym.

Nie zamykaj przeglądarki podczas hashowania
Bcrypt cost=10 przy wielu osobach może trwać kilkanaście sekund. Zamknięcie karty lub odświeżenie strony podczas operacji przerwie generowanie i będziesz musiał zacząć od nowa.
⚠️
Formularz generuje od zera, nie edytuje istniejącego pliku
Ten dashboard nie wczytuje istniejącego secret-key.php do edycji — każde uruchomienie „Generuj konfigurację" tworzy nowy plik od podstaw, na podstawie tego, co aktualnie wypełniłeś w formularzu. Drobne poprawki (np. zmianę jednego tekstu) często szybciej wprowadzić ręczną edycją samego secret-key.php na serwerze niż wypełniać cały formularz od nowa.
Narzędzia offline

Zakładka: Szyfrowanie

Dzieli hasło główne do bazy haseł na udziały algorytmem Shamira. Generuje plik secret-key-shares.txt z fragmentami klucza do rozdania wyznaczonym osobom.

#Widok zakładki

Zakładka Szyfrowanie z polem hasła, suwakami parametrów i podglądem wygenerowanych udziałów
Zakładka Szyfrowanie z polem hasła, suwakami parametrów i podglądem wygenerowanych udziałów

#Elementy interfejsu

1
Pole hasła głównego
Wpisz hasło do bazy KeePassXC (lub innego menedżera). Pole ma przycisk podglądu — kliknij oko żeby sprawdzić czy nie ma literówki przed podziałem.
2
Łączna liczba udziałów
Ile osób otrzyma fragment klucza. Musi odpowiadać liczbie osób w zakładce Konfiguracja. Zalecane: 5.
3
Wymagane udziały do odszyfrowania
Minimalna liczba osób potrzebna do odtworzenia hasła. Musi być mniejsza lub równa łącznej liczbie udziałów. Zalecane: 3.
4
Podgląd udziałów
Po wygenerowaniu każdy udział wyświetla się jako długi ciąg hex. Każda linia = jeden udział dla jednej osoby. Kolejność odpowiada kolejności osób z zakładki Konfiguracja.
5
Przycisk pobierania
Pobiera plik secret-key-shares.txt z nagłówkiem „Secret Key Sharing — udziały" i wszystkimi udziałami ponumerowanymi od 1 do N.

#Krok po kroku

1
Wpisz hasło główne

Wprowadź dokładnie to samo hasło które używasz do otwarcia bazy haseł. Użyj przycisku podglądu żeby upewnić się że nie ma błędu — po podziale nie ma możliwości weryfikacji bez zebrania udziałów.

2
Ustaw parametry podziału

Ustaw łączną liczbę udziałów (np. 5) i wymagane minimum (np. 3). Parametry muszą być spójne z liczbą osób w zakładce Konfiguracja.

3
Generuj i pobierz udziały

Kliknij „Generuj udziały". Pobierz plik secret-key-shares.txt — każdy udział (jedna linia) przypisz konkretnej osobie z listy z zakładki Konfiguracja.

4
Zweryfikuj odtworzenie

Przed rozdaniem kart warto przetestować: wpisz dowolne 3 udziały z pliku w zakładkę Szyfrowanie (tryb odtwarzania) i sprawdź czy hasło wraca poprawnie.

💡
Kompatybilność z iancoleman.io
Udziały są w pełni kompatybilne z iancoleman.io/shamir — możesz tam niezależnie zweryfikować odtworzenie hasła, wklejając dowolne wymagane minimum udziałów.
Narzędzia offline

Zakładka: Generator kart

Tworzy spersonalizowane karty Secret Key w formacie ISO ID-1 (85.6×54mm) gotowe do wydruku lub laminowania. Każda karta zawiera dane logowania, udział Shamira i kod QR.

#Widok zakładki

Generator kart z ustawieniami globalnymi, listą osób i podglądem przodu i tyłu karty
Generator kart z ustawieniami globalnymi, listą osób i podglądem przodu i tyłu karty

#Elementy interfejsu

1
Ustawienia globalne
Adres instancji (URL) Twojego serwera Secret Key oraz imię i nazwisko właściciela — oba pola są wspólne dla wszystkich kart i wykorzystywane w treści wiadomości na tylnej stronie.
2
Parametry podziału
Łączna liczba udziałów (N) i Wymagane udziały do odszyfrowania (K) — muszą być spójne z tym, co ustawiłeś w zakładkach Konfiguracja i Szyfrowanie. Wpływają na domyślną treść wiadomości na karcie.
3
Wiadomość na karcie (tył)
Edytowalne pole tekstowe (licznik na żywo) z treścią widoczną na tylnej stronie każdej karty. Limit 320 znaków to wskazówka, nie blokada — licznik zmienia kolor na czerwony po przekroczeniu, ale pisanie dalej jest możliwe (miejsca na karcie jest jednak niewiele). Puste pole = domyślny tekst (widoczny jako podpowiedź). Dostępne żetony — {{N}} (liczba udziałów), {{N_MINUS_1}} (pozostałe osoby), {{K}} (wymagany próg), {{URL}} (adres instancji) — można wpisać ręcznie albo kliknąć odpowiednią pigułkę pod polem, żeby wstawić żeton dokładnie w miejscu kursora.
4
Opcje PDF — Spad i Linie cięcia
Spad 2mm — powiększa obszar druku o 2mm z każdej strony (standard drukarni, eliminuje białe krawędzie). Linie cięcia — drukuje znaczniki do precyzyjnego cięcia nożyczkami.
5
Lista kart (osób)
Każda karta to wiersz z polami: Login, Hasło i Udział Shamira (hex) — dane logowania z zakładki Konfiguracja, udział z pliku secret-key-shares.txt. Imię i nazwisko nie są tu wpisywane per osoba — pojawiają się raz, w „Ustawieniach globalnych" (dotyczą właściciela systemu, nie odbiorcy karty).
6
Podgląd karty
Po kliknięciu „Generuj karty" pojawia się podgląd przodu i tyłu każdej karty. Sprawdź czy dane są poprawne przed drukiem.
7
Pobieranie PDF
Dwa przyciski: ↓ Pobierz PDF karty (jedna karta, 2 strony) i ↓ Pobierz PDF wszystkich kart (wszystkie karty naraz). PDF generowany przez window.print() — Chrome obsługuje to najlepiej.

#Krok po kroku

1
Wpisz adres instancji i dane właściciela

Podaj adres URL swojego serwera Secret Key — bez https://, np. secretkey.moja-domena.pl — oraz imię i nazwisko właściciela. Oba pola pojawią się w treści karty.

2
Ustaw parametry podziału

Liczba udziałów (N) i próg (K) muszą być spójne z tym, co ustawiłeś w zakładkach Konfiguracja i Szyfrowanie. Wpływają też na domyślną treść wiadomości na karcie.

3
Dostosuj wiadomość na karcie (opcjonalnie)

Zostaw pole puste, żeby użyć domyślnego tekstu, albo napisz własny. Licznik zmienia kolor na czerwony po 320 znakach jako wskazówka (nie blokuje pisania) — miejsca na karcie jest niewiele, więc warto się streszczać. Żetony {{N}}, {{N_MINUS_1}}, {{K}}, {{URL}} możesz wpisać ręcznie albo kliknąć ich pigułki pod polem, żeby wstawić je dokładnie w miejscu kursora.

4
Wybierz opcje druku

Jeśli zlecasz druk w drukarni — włącz Spad i Linie cięcia. Jeśli drukujesz samodzielnie na zwykłej drukarce — możesz zostawić oba wyłączone.

5
Uzupełnij dane każdej osoby

Dla każdej osoby wpisz login i hasło (z zakładki Konfiguracja) oraz udział Shamira (odpowiednia linia z pliku secret-key-shares.txt). Udziały przypisuj w tej samej kolejności co osoby.

6
Generuj podgląd i sprawdź

Kliknij „Generuj karty". Sprawdź dokładnie każdą kartę w podglądzie — zweryfikuj dane logowania, udział (pierwsze i ostatnie znaki hex) oraz adres instancji.

7
Pobierz i wydrukuj

Kliknij „Pobierz PDF wszystkich kart". W oknie druku Chrome: ustaw Marginesy: Brak, włącz Grafika w tle, rozmiar strony ustaw na niestandardowy zgodny z wybraną opcją spadu. Każda karta = 2 strony (przód + tył).

Po wydrukowaniu — zalaminuj karty
Laminowanie chroni kartę przed zalaniem, zabrudzeniem i mechanicznym uszkodzeniem. Polecane folie: 80–125 µm. Nie używaj folii grubszych niż 150 µm — mogą powodować bąble na kolorowych gradientach SVG.