
Show HN: Secret Key – Shamir-based emergency access to a password vault
Co się stanie z Twoimi kontami po śmierci?
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
🇵🇱 Polski • 🇬🇧 English • 🇪🇸 Español • 🇩🇪 Deutsch • 🇧🇷 Português (Brasil) • 🇫🇷 Français • 🇨🇳 简体中文 • 🇸🇦 العربية • 🇮🇳 हिन्दी • 🇯🇵 日本語 • 🇷🇺 Русский • 🇺🇦 Українська
Co się stanie z Twoimi kontami po śmierci?
Secret Key dzieli hasło główne do bazy haseł kryptograficznie między zaufane osoby.
Nikt nie zna go samodzielnie — dopiero razem, w uzgodnionym momencie, mogą je odtworzyć.
→ Zobacz demo · Strona projektu · Dokumentacja
01 · 🔑 Idea
02 · ⚙️ Jak to działa
03 · 🎬 Prezentacja systemu
04 · 💳 Karta Secret Key
05 · 🔐 Algorytm Shamira
06 · 🏗️ Architektura systemu
07 · 🛡️ Bezpieczeństwo
08 · 🚀 Instalacja
09 · 📁 Struktura plików
10 · ❓ FAQ
Idea
Każdy z nas ma dziesiątki haseł — do poczty, chmury ze zdjęciami, kodów 2FA, kryptowalut. Co się z nimi stanie po naszej śmierci? Banki i spadek mają własną drogę prawną, ale poczta, chmura czy krypto trzymane na własnym kluczu często zostają zamknięte na zawsze.
Secret Key niczego nie przechowuje i nie odszyfrowuje sam: dzieli hasło główne do Twojej bazy haseł (np. KeePass, Bitwarden) między zaufane osoby. Plik bazy leży na Twoim serwerze, a bliscy mogą go pobrać dopiero po zebraniu kworum, potwierdzeniu SMS-em i 48-godzinnej blokadzie. Co jest w bazie, decydujesz Ty. Secret Key nie jest narzędziem prawnym i nie zastępuje testamentu ani postępowania spadkowego; uzupełnia je tam, gdzie prawo nie pomoże.
Secret Key rozwiązuje ten problem, tworząc bezpieczny plan awaryjny z dwoma gwarancjami:
System jest w pełni self-hosted — dane nigdy nie opuszczają Twojego serwera. Żadnej centralnej bazy danych, żadnej chmury, żadnych zewnętrznych zależności poza wysyłką SMS.
Backend PHP 8+, pliki flat (JSON + PHP config), zero SQL Autoryzacja bcrypt cost=10, 2FA SMS via SMSPlanet API Kryptografia Shamir Secret Sharing (secrets.js) Frontend Czysty HTML + JS, zero zewnętrznych zależności (offline)
Jak to działa
W sytuacji kryzysowej wyznaczone osoby wykonują cztery kroki:
┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 01 ZEBRANIE Min. 3 z 5 wyznaczonych osób │ │ ───────── Każda ma kartę z fragmentem klucza │ │ │ │ 02 LOGOWANIE Dane logowania z karty Secret Key │ │ ────────── + kod SMS na przypisany numer telefonu │ │ │ │ 03 UDZIAŁY Każda osoba wpisuje swój fragment lub │ │ ───────── skanuje kod QR z karty │ │ │ │ 04 DOSTĘP Hasło odtworzone lokalnie w przeglądarce — │ │ ──────── przy SECRET_HASH weryfikowane na Twoim serwerze │ │ │ └──────────────────────────────────────────────────────────────────────┘
Bez telefonu przypisanego do konta — logowanie niemożliwe, nawet przy znajomości danych z karty.
Bez wymaganej liczby udziałów — odtworzenie hasła matematycznie niemożliwe, nawet przy dostępie do serwera.
🔊 Film dostępny w wielu językach — kliknij ⚙️ w odtwarzaczu YouTube → Napisy/Dźwięk (Audio track), aby wybrać język dubbingu lub włączyć napisy.
Każda wyznaczona osoba otrzymuje spersonalizowany nośnik z czterema elementami:
Format nośnika jest dowolny — wydruk na kartce, PDF, karta plastikowa. Ważne, aby zawierał powyższe cztery elementy.
Secret Key używa biblioteki secrets.js (fork amper5and/secrets.js, zwendorowany przez iancoleman.io/shamir) do podziału i rekonstrukcji sekretu.
Użyta biblioteka jest identyczna z tą stosowaną przez iancoleman.io/shamir — format udziałów jest w pełni kompatybilny, co umożliwia niezależną weryfikację poza systemem.
Hasło główne: "MojeHasloDoKeePass2024!" │ ▼ Podział na 5 udziałów (próg: 3) │ ┌────┴──────────────────────────────────┐ │ │ │ S1: 801a3f9c2e4b7d1... → Osoba A │ Każdy udział │ S2: 802c8f1a5e9b3d7... → Osoba B │ jest bezużyteczny │ S3: 803e2a7f4c1b9d5... → Osoba C │ bez wymaganej │ S4: 804b6d3e8f2a1c9... → Osoba D │ liczby pozostałych │ S5: 805d9f7b2e4c3a1... → Osoba E │ │ │ └────┬──────────────────────────────────┘ │ ▼ Rekonstrukcja — wystarczą dowolne 3 z 5 │ S1 + S2 + S3 → "MojeHasloDoKeePass2024!" ✓ S2 + S4 + S5 → "MojeHasloDoKeePass2024!" ✓ S1 → brak informacji o sekrecie ✗
Właściwości kryptograficzne
Sekret jest zakodowany 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:
f(x) = a₀ + a₁x + a₂x² + ... + aₖ₋₁xᵏ⁻¹ (mod p)
Important
Posiadanie mniejszej niż wymagana liczby udziałów nie daje żadnej informacji o sekrecie (information-theoretic security). Dodany padding 1024 bitów uniemożliwia ataki na małe sekrety.
Parametr Wartość
Biblioteka secrets.js (fork grempe, via iancoleman.io/shamir)
Format udziałów 8 + 2-hex x-coord + data
minPad 1024 bitów
Kodowanie UTF-8 (str2hex)
Rekonstrukcja JavaScript (przeglądarka); hasło nie jest zapisywane — przy SECRET_HASH weryfikowane na serwerze przez HTTPS
Dobór parametrów podziału
Łączna liczba udziałów Wymagane minimum Scenariusz
3 2 Mała rodzina
5 3 Standardowy (zalecany)
7 4 Większa rodzina / firma
Architektura systemu
Użytkownik przechodzi przez kolejne warstwy weryfikacji, zanim uzyska dostęp do chronionych zasobów:
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/)
Warstwa danych (/private/ — poza public_html)
Wszystkie wrażliwe pliki konfiguracyjne przechowywane są poza katalogiem publicznym serwera — błędna konfiguracja webservera nie grozi ich ujawnieniem.
System łączy dziewięć niezależnych warstw ochrony — kompromitacja jednej nie daje dostępu do systemu.
Ochrona przed zmową powierników za Twojego życia. 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 systemu progowego dzielenia sekretu, nie tylko tego. System reaguje na to na trzech poziomach: (1) w przeglądarce sprawdza spójność złożonych udziałów (matematyczna spójność podzbiorów + analiza formatu wyniku) — to odsiewa literówki i przypadkowy szum, ale samo w sobie nie potwierdza, że złożony sekret to prawdziwe hasło (ktoś zalogowany mógłby wkleić w pełni spójne, ale zmyślone udziały z zupełnie innym „hasłem"); (2) jeśli w configu ustawisz opcjonalny, ale mocno zalecany SECRET_HASH (bcrypt hash prawdziwego hasła, liczony automatycznie w dashboardzie), serwer realnie weryfikuje złożony sekret przez password_verify(), zanim cokolwiek uzbroi; (3) dopiero po tej weryfikacji blokuje pobieranie plików na 48 godzin, wysyłając Ci e-mail z jednorazowym linkiem „Panic Button" — jedno kliknięcie trwale odcina dostęp, zanim ktokolwiek zdąży cokolwiek pobrać. Sama znajomość hasła nic nie daje bez fizycznego pliku bazy. Dla maksymalnego poziomu ochrony zalecane jest dodatkowo stosowanie klucza sprzętowego (np. YubiKey/FIDO2) jako drugiego składnika samej bazy haseł.
Wymagania
PHP 8.0+
Serwer WWW (Apache / Nginx)
Konto w SMSPlanet (wysyłka kodów 2FA)
Dostęp do katalogu poza public_html
Krok 1 — Konfiguracja (offline)
Otwórz dashboard.html lokalnie w przeglądarce. Zawiera trzy zakładki:
Konfiguracja — generuje plik secret-key.php:
Szyfrowanie — dzieli hasło główne na udziały Shamira:
Generator kart — tworzy spersonalizowane karty Secret Key gotowe do druku (format ISO ID-1, 85.6×54mm):
Jeśli nie korzystasz z weryfikacji po stronie serwera, usuń całą linię define('SECRET_HASH', …) z configu. W v3.1.0 pusta wartość (define('SECRET_HASH', '');) powodowała, że panel odrzucał każde hasło, nawet poprawne. Od v3.1.1 pusta wartość jest traktowana jak brak stałej, ale czytelniej jest usunąć linię. Generator w dashboardzie pomija ją, gdy pole „Prawdziwe hasło główne” jest puste.
Wszystkie trzy narzędzia działają całkowicie offline — żadne dane nie opuszczają przeglądarki. Formularz konfiguracji generuje secret-key.php od zera przy każdym uruchomieniu — nie wczytuje ani nie edytuje istniejącego pliku.
/home/user/ ├── public_html/ │ ├── app/ ← zawartość folderu /app/ │ └── decrypt/ ← zawartość folderu /decrypt/ └── private/ ← POZA public_html ├── secret-key.php ← wygenerowany plik konfiguracyjny ├── lang.php ← teksty interfejsu (jeden język, patrz Krok 1) ├── rate-limit.php ← plik systemowy z repozytorium (rate-limiting) └── moja-baza-hasel.kdbx ← Twoje pliki (serwowane przez download.php)
Warning
lang.php musi trafić na serwer razem z secret-key.php. auth.php ładuje go przez require_once — brak pliku wysypuje cały system (fatal error na każdej stronie), nie tylko tłumaczenia.
W pliku auth.php (folder app/) ścieżka do folderu /private/ występuje w 7 miejscach. Trzeba poprawić wszystkie, nie tylko pierwszy require_once:
// app/auth.php require_once DIR . '/../../../private/secret-key.php'; require_once DIR . '/../../../private/rate-limit.php'; require_once DIR . '/../../../private/lang.php';
define('LOG_FILE', DIR . '/../../../private/secret-key.log'); define('PRIVATE_DIR', DIR . '/../../../private');
// ×2 (rememberDevice() i sprawdzanie zaufanego urządzenia) $file = DIR . '/../../../private/trusted_devices.json';
Domyślnie w każdym z tych miejsc stoi fragment /../../../private. Ścieżka jest liczona od folderu, w którym leży auth.php, więc dopasuj ją do swojej struktury katalogów. Przykład: auth.php leży w /public_html/twoja-domena/, a /private/ obok /public_html/, więc zmieniasz /../../../private na /../../private we wszystkich siedmiu miejscach (w edytorze użyj „Zamień wszystko”).
Treść wysyłanego kodu SMS kończy się linijką @domena #kod — to format wymagany przez WebOTP API, dzięki któremu przeglądarka na telefonie sama wypełnia pole kodu, bez ręcznego przepisywania z SMS-a.
Domenę ustawiasz w Kroku 1, w formularzu dashboardu (pole „Domena do autouzupełniania SMS") — wpisujesz samą domenę, bez @ i bez https:// (dashboard dokleja @ automatycznie). Trafia do configu jako:
define('SMS_AUTOFILL_DOMAIN', '@twoja-domena.pl');
Warning
Domena musi dokładnie zgadzać się z tą, pod którą hostujesz system — inaczej WebOTP zignoruje SMS i autouzupełnianie nie zadziała. Kod nadal dotrze i zadziała po ręcznym wpisaniu, tylko bez tej wygody.
Dla każdej wyznaczonej osoby przygotuj nośnik z:
secret-key/ │ ├── 📁 app/ # Publiczny — system logowania │ ├── 📁 decrypt/ # Chroniony — panel użytkownika │ │ ├── .htaccess │ │ ├── arm-timelock.php │ │ ├── card-secret-key.webp │ │ ├── devtools-log.php │ │ ├── download.php │ │ ├── favicon.ico │ │ ├── index.php │ │ ├── key.svg │ │ ├── log.php │ │ ├── panic.php │ │ ├── timelock.php │ │ └── tl-status.php │ ├── .htaccess │ ├── auth.php │ ├── favicon.ico │ ├── key.svg │ ├── login.php │ ├── logout.php │ ├── resend.php │ └── verify.php │ ├── 📁 dashboard/ # Narzędzia konfiguracyjne (offline) │ ├── card-back.js │ ├── card-front.js │ ├── dashboard.html │ ├── favicon.ico │ ├── generate-card.html │ ├── generate-hash.html │ └── generate-shamir.html │ ├── 📁 img/ # Zasoby graficzne │ └── 📁 private/ # Poza public_html — pliki konfiguracyjne ├── .htaccess ├── demo-baza-hasel.txt # Przykładowy plik do pobrania (zastąp własnym) ├── lang.php # Teksty interfejsu (jeden język) ├── rate-limit.php └── secret-key.php
FAQ
Co się stanie 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.
Nie. Jest to matematycznie niemożliwe. Pojedynczy udział nie ujawnia żadnej informacji o sekrecie — to właściwość algorytmu zwana information-theoretic security. Dopiero zebranie wymaganej liczby udziałów pozwala na odtworzenie hasła głównego.
System jest zaprojektowany z nadmiarowością — wystarczy zebrać minimalną wymaganą licz
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。