Hlavní navigace

PHP sessions v databázi, šifrovaně

7. 6. 2015 14:25 (aktualizováno) | Tomas Matějíček

Sessions v PHP funguje defaultně tak, že se pro každý nový požadavek vygeneruje unikátní ID, které se pak posílá tam a zpět přes cookies. Na disku na serveru se pak obvykle v /tmp adresáři vytvoří soubor sess_$ID (takže třeba například /tmp/sess_vc816c8hj4o3ukpicaisa340d4) do kterého se ukládají data pro dané sezení. Při dalším přístupu toho stejného uživatele už se podle jeho session_id (z cookies) na disku načte ten správný soubor a z něj se data vytáhnou. Čas od času se pak tyhle dočasné soubory zase mažou.

Obecně je to univerzální a jednoduchý způsob, ovšem kdokoli může takovéto soubory číst (ať už uživatel pod kterým běží web, nebo i administrátor systému). Nemusím jistě také nikomu vysvětlovat, že přímý přístup na filesystém je jedna z nejhorších možností, kterou použít, zvlášť pokud má systém evidovat desetitisíce sessions najednou.

Je tedy vhodné si session handling napsat extra sám. Vlastní implementace sessions, kterou teď zvažuju použít já, vychází z následujících požadavků:
  • neukládat data do jednotlivých souborů, ale do databáze
  • vyřešit, aby nemohl kdokoli číst data ostatních, ani systém administrátor
  • přimět PHP, aby se takto o sessions staralo samo, bez změn ostatního kódu
Řešení:
PHP umožňuje programátorovi nadefinovat vlastní funkce pro čtení a zápis sezení, a to pomocí funkce session_set_save_handler(), takže stačí jen napsat pár vlastních funkcí a PHPčku říct, že je má používat. Tyto funkce pak data ukládají tam kam chci, tedy do databáze.
Uložené data je v databázi potřeba nějak šifrovat, ovšem šifrovací klíč není možné do databáze uložit, jinak by to celé postrádalo smysl. Je nasnadě šifrovat data pomocí session identifikátoru, který se posílá s každým požadavkem. Nemůžu pak ale tento identifikátor v databázi uložit, takže je pak nutné pro identifikaci správných dat pro session použít něco jiné – třeba hash toho session_id (s potencionálním rizikem kolize, které je sice titěrné ale nenulové), nebo zakódovanou verzi nějakého pevného stringu tím session_id (důležitá otázka, nemůže i toto vést ke kolizi?)
V databázi vytvořím tabulku sessions, se třemi sloupci:
  • id (binary 255, primární klíč)
  • sessionData (blob)
  • lastUpdate (datetime)
Implementaci funkcí v PHP uložím do souboru třeba lib_session.php. Tenhle blog na rootu neumí syntax highlighting ani formátování kódu, takže jsem výsledek pastnul na pastebin. Pro zjednodušení předpokládám existenci funkcí query(), escape(), fetch_assoc(), jejichž význam je evidentní. Stačí pak tento soubor includnout do všech PHP skriptů, kde chci se session pracovat.
Vypíchnu SQL příkazy, které vlastní práci s daty provádí. Tohle zapisuje data session do databáze: REPLACE sessions SET id=AES_ENCRYPT("nejaky string furt stejny", $session_id), lastUpdate=NOW(), sessionData=AES_ENCRYPT($data, $session_id) --
A tohle čte session data z databáze: SELECT AES_DECRYPT(sessionData, $session_id) FROM sessions WHERE id=AES_ENCRYPT("nejaky string furt stejny", $session_id) --
Přijde mi to jako neprůstřelné řešení. Není? Budu rád za Vaše komentáře.