Ukládání citlivých informací

Školení, která pořádám

Pokud se do databáze mají ukládat extrémně citlivé údaje (jako např. čísla kreditních karet), stojí za zvážení nespoléhat se na zabezpečení serveru, kde databáze běží, a přidat další úroveň v podobě šifrování dat. Pokud se ukládají data, u kterých není nutné znát přesnou hodnotu a stačí ověřovat jejich platnost (jako jsou např. hesla), stačí ukládat jejich hash, jinak je potřeba šifrovat.

Šifrovací funkce jsou v PHP k dispozici prostřednictvím knihoven Mcrypt nebo OpenSSL. Pokud místo nich využijeme šifrovací funkce poskytované databází, získáme dvě výhody:

  1. Nebudeme potřebovat PHP rozšíření, které nemusí být všude k dispozici.
  2. S daty bude moci pracovat i aplikace napsaná v jiném jazyce.

MySQL nabízí tři dvojice funkcí: ENCODE+DECODE, AES_ENCRYPT+AES_DECRYPT a DES_ENCRYPT+DES_DECRYPT. Každá používá jinou šifru, jejich použití je ale v zásadě stejné (až na drobné rozdíly v potřebné délce pro uložení zašifrovaného řetězce).

Zbývá zvolit klíč, kterým budeme data šifrovat. Vzhledem k tomu, že si ho nebude muset nikdo pamatovat, je možné vygenerovat klíč náhodný a dostatečně dlouhý. Možnosti jeho uložení jsou široké:

Uložit ho do zdrojových kódů
Tím se ale zdrojáky stávají tajné a každý, kdo k nim získá přístup, má snadnější cestu k chráněným údajům.
Uložit ho do souboru nedostupného z webu
Přístup k tomuto souboru musíme co nejvíce omezit, zároveň ale musí zůstat čitelný uživateli, pod kterým běží webový server.
Nastavit ho do proměnné prostředí webového serveru
Pokud je webový server spouštěn pod uživatelem root, může být soubor s klíčem čitelný pouze oprávněným uživatelem a proměnná je přístupná pouze v daném kontextu (např. jeden adresář). V PHP by se potom proměnná získávala pomocí pole $_SERVER.
Uložit ho do databáze zašifrované heslem uživatele
Dešifrovací klíč nebo uživatelské heslo si potom skript bude muset pamatovat např. v session proměnné. Druhou možností je při přístupu k chráněným údajům požadovat opětovné zadání uživatelské hesla.
Neukládat ho nikam a požadovat ho při zobrazení dat
Klíč musí být zapamatovatelný a uživatele nesmí obtěžovat, že ho při přístupu k chráněným datům musí vždy znovu zadat.

Za většiny okolností mi přijde nejlepší kombinace prvního a čtvrtého způsobu – půlku hesla mít uloženou ve zdrojácích a půlku v databázi. Uživatelé budou uloženi v tabulce admin(login varchar(20), heslo_sha1 char(40), master_encoded tinyblob) a jednotlivé operace mohou vypadat třeba takhle:

<?php
// přihlášení uživatele
$row = mysql_fetch_assoc(mysql_query("SELECT DECODE(master_encoded, '" . mysql_real_escape_string($_POST["heslo"]) . "') AS master FROM admin WHERE login = '" . mysql_real_escape_string($_POST["login"]) . "' AND heslo_sha1 = SHA1('" . mysql_real_escape_string($_POST["heslo"]) . "')"));
if ($row) { // přihlášení se podařilo
    $_SESSION["master"] = $row["master"];
    define("MASTER2", "druhá polovina hesla");
}

// získání zašifrovaných dat
mysql_query("SELECT DECODE(kreditka_encoded, '" . mysql_real_escape_string($_SESSION["master"] . MASTER2) . "') AS kreditka FROM zakaznici WHERE id = " . intval($_GET["id"]));

// přidání nového uživatele
mysql_query("INSERT INTO admin (login, heslo_sha1, master_encoded) VALUES ('" . mysql_real_escape_string($_POST["login"]) . "', SHA1('" . mysql_real_escape_string($_POST["heslo"]) . "'), ENCODE('" . mysql_real_escape_string($_SESSION["master"]) . "', '" . mysql_real_escape_string($_POST["heslo"]) . "'))");
?>

Typ tinyblob se pro ukládání zašifrovaných dat používá proto, že byť MySQL umožňuje do typu varchar uložit i binární data, ořezává koncové mezery, což může v tomto případě zásadně vadit. Kvůli ukládání části hesla do session proměnné je vhodné ukládat session data raději pouze do paměti.

Využití asymetrické kryptografie

Pokud je část klíče uložena v databázi zašifrovaná heslem uživatele, může s citlivými údaji pracovat pouze přihlášený uživatel. Pro čtení je to pochopitelný požadavek, ale zápis by měl být umožněn i anonymním uživatelům, kteří data vyplňují. Ke slovu přichází asymetrická kryptografie. Ta bohužel v MySQL není implementovaná, takže musíme sáhnout po některé knihovně PHP, např. OpenSSL:

<?php
// uložení citlivé informace
openssl_public_encrypt($_POST["kreditka"], $crypted, PUBLIC_KEY);

// přihlášení uživatele
$pkey = openssl_pkey_get_private($row["private_key"], $_POST["heslo"]);
openssl_pkey_export($pkey, $_SESSION["private_key"]);

// přidání nového uživatele nebo změna hesla
openssl_pkey_export($_SESSION["private_key"], $private_key, $_POST["heslo"]);

// získání citlivé informace
openssl_private_decrypt($row["kreditka"], $decrypted, $_SESSION["private_key"]);
?>

Při ukládání do databáze je nutné s daty pracovat jako s binárními. Prvotní soukromý a veřejný klíč lze vygenerovat i pomocí funkcí PHP funkcí openssl_pkey_new a openssl_pkey_get_public.

Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací (14.12.2016, Praha).

Jakub Vrána, Výuka, 2.6.2006, diskuse: 5 (nové: 0)

Diskuse

ttt:

aaa

drizzdt:

Chtěl bych se zeptat, kde by se daly sehnat zkompilované verze mod_ssl a openssl pro wokenní platformu a apahce2 + PHP5

kozotoč:

...nutno ještě podotknouti, že informace v $_POST['kreditka'] by měla dorazit alespoň pod https://

kriplozoik:

v první ukázce kódu: co je to MASTER2? (nevidím, že by to bylo předtím kdekoli definováno)

ikona Jakub Vrána OpenID:

MASTER2 je druhá polovina hesla. Do ukázky jsem to doplnil.

Vložit příspěvek

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2016 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.