Różnice między wybraną wersją a wersją aktualną.
Poprzednia rewizja po obu stronachPoprzednia wersjaNowa wersja | Poprzednia wersja | ||
narzedzia:secure_file_exchange [2025/05/14 11:27] – usunięto - edycja zewnętrzna (Nieznana data) 127.0.0.1 | narzedzia:secure_file_exchange [2025/05/16 18:38] (aktualna) – administrator | ||
---|---|---|---|
Linia 1: | Linia 1: | ||
+ | ====== PHP: Bezpieczne przesyłanie plików w PHP ====== | ||
+ | aplikacja dostępna pod linkiem: https:// | ||
+ | |||
+ | ===== Strona powitalna ===== | ||
+ | |||
+ | * Po wejściu na index.php użytkownik widzi dwa przyciski: „I’m Sender” i „I’m Receiver”. | ||
+ | * Wybierając jedną z opcji, przechodzi do odpowiedniego trybu — nadawcy lub odbiorcy. | ||
+ | |||
+ | ===== Generowanie kodu („Sender”) ===== | ||
+ | |||
+ | |||
+ | * Gdy klikniesz „I’m Sender”, aplikacja losuje przyjazny kod w postaci dwóch członów: przymiotnik‑zwierzę (np. brave-fox, calm-owl). | ||
+ | * Ten kod nie jest samą nazwą katalogu, a jedynie ziarnem (seedem) do dalszych kroków. | ||
+ | |||
+ | ===== Przypisanie katalogu ===== | ||
+ | |||
+ | |||
+ | * Na podstawie unikalnego kodu tworzone jest losowe ID katalogu (np. a3f1b5c7d9e2f0a1…), | ||
+ | * Jednocześnie powstaje fizyczny katalog uploads/< | ||
+ | |||
+ | ===== Derywacja klucza szyfrującego ===== | ||
+ | |||
+ | * Klucz AES‑256 do szyfrowania/ | ||
+ | * Dzięki temu klucz nigdy nie jest zapisywany na dysku. | ||
+ | |||
+ | ===== Wysyłanie pliku (upload) ===== | ||
+ | |||
+ | Nadawca wybiera plik i wysyła go formularzem. | ||
+ | |||
+ | **Aplikacja: | ||
+ | |||
+ | * Odczytuje oryginalny plik z tymczasowego miejsca. | ||
+ | * Generuje losowy wektor inicjalizujący (IV) 16 bajtów. | ||
+ | * Szyfruje zawartość pliku algorytmem AES‑256‑CBC (używając klucza i IV). | ||
+ | * Zapisuje do uploads/< | ||
+ | |||
+ | ===== Wyświetlenie kodu ===== | ||
+ | |||
+ | Po udanym uploadzie nadawca widzi komunikat ze swoim kodem (np. brave-fox) i może go przekazać odbiorcy. | ||
+ | |||
+ | ===== Usuwanie danych po zamknięciu (cleanup) ===== | ||
+ | |||
+ | W widoku nadawcy skrypt JavaScript nasłuchuje zdarzenia unload (zamykania/ | ||
+ | |||
+ | Jeśli użytkownik nie klika właśnie „Wyślij” (czyli naprawdę opuszcza kartę), wysyłany jest lekki sygnał (navigator.sendBeacon) z parametrem mode=cleanup& | ||
+ | |||
+ | Na serwerze obsługa tej akcji usuwa katalog uploads/< | ||
+ | |||
+ | ===== Odbieranie pliku (Receiver) ===== | ||
+ | |||
+ | |||
+ | W trybie odbiorcy pojawia się formularz, w który trzeba wpisać otrzymany od nadawcy kod (np. brave-fox). | ||
+ | |||
+ | **Po zatwierdzeniu: | ||
+ | |||
+ | * Aplikacja odczytuje z pliku mappings/ | ||
+ | * Przegląda zawartość uploads/< | ||
+ | * Ściąganie pliku (download) | ||
+ | |||
+ | Odbiorca klika „Download” przy wybranym zaszyfrowanym pliku. | ||
+ | |||
+ | **Serwer:** | ||
+ | |||
+ | * Odczytuje uploads/< | ||
+ | * Oddziela IV (pierwsze 16 bajtów) od szyfrogramu. | ||
+ | * Dekryptuje zawartość tym samym kluczem wyprowadzonym z kodu. | ||
+ | * Wysyła odeszyfrowany plik w odpowiedzi jako załącznik. | ||
+ | |||
+ | **Powtórne użycie i bezpieczeństwo** | ||
+ | |||
+ | * Odbiorca nie przechowuje kodu w sesji — za każdym razem musi go wpisać od nowa. | ||
+ | * Katalogi na serwerze mają losowe nazwy, niepowiązane jawnie z kodem, co zapobiega odgadnięciu klucza. | ||
+ | * Klucz szyfrujący nigdy nie trafia na dysk — jest zawsze generowany w pamięci. | ||
+ | |||
+ | **Dzięki tej procedurze: | ||
+ | |||
+ | * Bezpieczeństwo: | ||
+ | * Łatwość użycia: prosty, czytelny kod typu adjective-animal. | ||
+ | * Automatyczne czyszczenie: | ||
+ | |||
+ | ===== Kod Rozwiązania ===== | ||
+ | |||
+ | <code php index.php> | ||
+ | <?php | ||
+ | // index.php | ||
+ | // —————————————————————————————————————————————— | ||
+ | // CONFIGURATION | ||
+ | define(' | ||
+ | define(' | ||
+ | define(' | ||
+ | define(' | ||
+ | |||
+ | $adjectives = [' | ||
+ | // longer the better but still bruteforcable if you have 100 adjectives and animals | ||
+ | //you get 10k possiblities which is not that much, but anyway it is better then | ||
+ | //retyping 32 bit random codes or something ;-) | ||
+ | $animals | ||
+ | |||
+ | // Ensure dirs exist | ||
+ | foreach ([UPLOAD_DIR, | ||
+ | if (!is_dir($d)) mkdir($d, 0700, true); | ||
+ | } | ||
+ | |||
+ | // —————————————————————————————————————————————— | ||
+ | // HELPERS | ||
+ | |||
+ | function make_code(array $a, array $b): string { | ||
+ | return $a[array_rand($a)] . ' | ||
+ | } | ||
+ | |||
+ | function derive_key(string $code): string { | ||
+ | return hex2bin(hash(' | ||
+ | } | ||
+ | |||
+ | function encrypt_file(string $src, string $dst, string $code): bool { | ||
+ | $key = derive_key($code); | ||
+ | $iv = random_bytes(IV_LEN); | ||
+ | $pt = file_get_contents($src); | ||
+ | $ct = openssl_encrypt($pt, | ||
+ | return $ct === false | ||
+ | ? false | ||
+ | : file_put_contents($dst, | ||
+ | } | ||
+ | |||
+ | function decrypt_file(string $path, string $code) { | ||
+ | $key = derive_key($code); | ||
+ | $data = file_get_contents($path); | ||
+ | $iv = substr($data, | ||
+ | $ct = substr($data, | ||
+ | return openssl_decrypt($ct, | ||
+ | } | ||
+ | |||
+ | function rrmdir(string $dir) { | ||
+ | if (!is_dir($dir)) return; | ||
+ | foreach (scandir($dir) as $f) { | ||
+ | if ($f===' | ||
+ | $p = " | ||
+ | is_dir($p) ? rrmdir($p) : unlink($p); | ||
+ | } | ||
+ | rmdir($dir); | ||
+ | } | ||
+ | |||
+ | function valid_code(string $c): bool { | ||
+ | return preg_match('/ | ||
+ | } | ||
+ | |||
+ | // Lookup directory ID by code | ||
+ | function get_dir_by_code(string $code): ?string { | ||
+ | $map = MAP_DIR . $code; | ||
+ | if (!file_exists($map)) return null; | ||
+ | return trim(file_get_contents($map)); | ||
+ | } | ||
+ | |||
+ | // Create mapping code→random dir ID | ||
+ | function create_mapping(string $code): string { | ||
+ | // generate a random hex ID | ||
+ | $dirId = bin2hex(random_bytes(DIR_ID_LEN)); | ||
+ | // create upload dir | ||
+ | mkdir(UPLOAD_DIR . $dirId, 0700, true); | ||
+ | // write mapping file (only dirId) | ||
+ | file_put_contents(MAP_DIR . $code, $dirId); | ||
+ | return $dirId; | ||
+ | } | ||
+ | |||
+ | // Remove mapping + uploads | ||
+ | function cleanup(string $code) { | ||
+ | $dirId = get_dir_by_code($code); | ||
+ | if ($dirId) { | ||
+ | rrmdir(UPLOAD_DIR . $dirId); | ||
+ | unlink(MAP_DIR . $code); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // —————————————————————————————————————————————— | ||
+ | // ROUTING | ||
+ | $mode = $_REQUEST[' | ||
+ | $code = $_REQUEST[' | ||
+ | |||
+ | // 1) CLEANUP on unload | ||
+ | if ($mode===' | ||
+ | cleanup($code); | ||
+ | http_response_code(204); | ||
+ | exit; | ||
+ | } | ||
+ | |||
+ | // 2) DOWNLOAD (receiver) | ||
+ | if ($mode===' | ||
+ | $dirId = get_dir_by_code($code); | ||
+ | $file = basename($_GET[' | ||
+ | $path = UPLOAD_DIR . " | ||
+ | if (!$dirId || !file_exists($path)) { | ||
+ | http_response_code(404); | ||
+ | exit(' | ||
+ | } | ||
+ | $data = decrypt_file($path, | ||
+ | if ($data === false) { | ||
+ | http_response_code(500); | ||
+ | exit(' | ||
+ | } | ||
+ | header(' | ||
+ | header(' | ||
+ | echo $data; | ||
+ | exit; | ||
+ | } | ||
+ | |||
+ | // 3) SENDER UPLOAD | ||
+ | if ($_SERVER[' | ||
+ | && | ||
+ | && | ||
+ | ) { | ||
+ | $code = $_POST[' | ||
+ | if (!valid_code($code)) { | ||
+ | $error = " | ||
+ | } else { | ||
+ | // get or create a dir ID for this code | ||
+ | $dirId = get_dir_by_code($code) ?? create_mapping($code); | ||
+ | |||
+ | if (empty($_FILES[' | ||
+ | $error = " | ||
+ | } else { | ||
+ | $orig = $_FILES[' | ||
+ | $name = basename($_FILES[' | ||
+ | $dst = UPLOAD_DIR . " | ||
+ | if (encrypt_file($orig, | ||
+ | $success = "File encrypted & uploaded as < | ||
+ | } else { | ||
+ | $error = " | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // 4) RECEIVER ENTER CODE | ||
+ | if ($_SERVER[' | ||
+ | && | ||
+ | && | ||
+ | ) { | ||
+ | $code = $_POST[' | ||
+ | if (!valid_code($code)) { | ||
+ | $error = " | ||
+ | } | ||
+ | // else $code is used for this request only | ||
+ | } | ||
+ | |||
+ | // —————————————————————————————————————————————— | ||
+ | // PAGE | ||
+ | ?>< | ||
+ | <html lang=" | ||
+ | < | ||
+ | < | ||
+ | body { font-family: | ||
+ | .box { background:# | ||
+ | | ||
+ | input, | ||
+ | .error{color:# | ||
+ | </ | ||
+ | </ | ||
+ | <div class=" | ||
+ | < | ||
+ | |||
+ | <?php if (!$mode): // Landing ?> | ||
+ | <a href="? | ||
+ | <a href="? | ||
+ | |||
+ | <?php elseif ($mode===' | ||
+ | < | ||
+ | <?php if (!isset($code)): | ||
+ | <?php if (!empty($error)): | ||
+ | <?php if (!empty($success)): | ||
+ | |||
+ | < | ||
+ | <form id=" | ||
+ | <input type=" | ||
+ | <input type=" | ||
+ | <input type=" | ||
+ | <button type=" | ||
+ | </ | ||
+ | <a href=" | ||
+ | |||
+ | < | ||
+ | let submitting = false; | ||
+ | document.getElementById(' | ||
+ | .addEventListener(' | ||
+ | window.addEventListener(' | ||
+ | if (!submitting) { | ||
+ | navigator.sendBeacon('? | ||
+ | } | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | <?php elseif ($mode===' | ||
+ | < | ||
+ | <?php if (empty($code) || !empty($error)): | ||
+ | <form method=" | ||
+ | <input type=" | ||
+ | <input type=" | ||
+ | <button type=" | ||
+ | </ | ||
+ | <?php if (!empty($error)): | ||
+ | |||
+ | <?php else: | ||
+ | $dirId = get_dir_by_code($code); | ||
+ | $files = $dirId | ||
+ | ? array_diff(scandir(UPLOAD_DIR . $dirId), [' | ||
+ | : []; | ||
+ | if (empty($files)): | ||
+ | < | ||
+ | <?php else: ?> | ||
+ | <ul> | ||
+ | <?php foreach ($files as $f): ?> | ||
+ | <li> | ||
+ | <?= htmlspecialchars(pathinfo($f, | ||
+ | — <a href="? | ||
+ | </li> | ||
+ | <?php endforeach; ?> | ||
+ | </ul> | ||
+ | <?php endif; ?> | ||
+ | <a href=" | ||
+ | <?php endif; ?> | ||
+ | |||
+ | <?php else: // Invalid mode ?> | ||
+ | <p class=" | ||
+ | <a href=" | ||
+ | <?php endif; ?> | ||
+ | |||
+ | </ | ||
+ | </ | ||
+ | </ |