PHP titkosítási módszerek jelszavakhoz és egyéb érzékeny adatokhoz

nemrég részt vettem a Laracon EU 2018 – ban, ahol Marcus Bointon nagyszerű beszédet mondott a KRIPTOGRÁFIÁRÓL a PHP 7.2 – ben. A beszélgetést sokkal jobban elismertem, hogy mennyire bonyolult a kriptográfia, hanem azért is, hogy a PHP hogyan teszi hozzáférhetőbbé a titkosítást a nátrium bevezetésének köszönhetően. Az adatok titkosítása a PHP-ben olyasmi, amin a SpinupWP-n végzett munkám részeként dolgoztam, ezért úgy gondoltam, Itt az ideje, hogy megosszam néhány betekintést. Csatold fel, mert ez egy göröngyös út lehet!

a Titkosítás típusai

számos különböző titkosítási módszert használnak ma, a leggyakoribbak a hash, a titkos kulcs titkosítása és a nyilvános kulcs titkosítása. Ezenkívül minden titkosítási módszer több algoritmussal vagy titkosítással rendelkezik, amelyek közül választhat (mindegyiknek megvan a maga erőssége és gyengesége). Ebben a cikkben fogunk összpontosítani végrehajtási hash és titkos kulcs titkosítás.

kivonatolás

a kivonatoló algoritmus felvesz egy bemeneti értéket, és átalakítja üzenet-kivonattá. Dióhéjban a sima szöveges értékek rögzített hosszúságú kivonattá alakulnak át, és csak az eredeti érték átadásával érvényesíthetők a hash algoritmusnak. Ez teszi hashing tökéletes tárolására felhasználói jelszavakat.

érdemes megjegyezni, hogy a hash nem golyóálló megoldás, és nem minden hash algoritmus egyenlő. Tekintsük MD5 és SHA1, amelyek gyors és hatékony, így ideális checksumming és fájl ellenőrzés. Sebességük azonban alkalmatlanná teszi őket a felhasználó jelszavának kivonatolására. A modern GPU-k mai számítási erejével a jelszó néhány perc alatt brutális erővel feltörhető, felfedve az eredeti egyszerű szöveges jelszót. Ehelyett szándékosan lassabb hash algoritmusokat, például bcrypt vagy Argon2 kell használni.

bár egy algoritmus által generált kivonatolt jelszó minden bizonnyal elhomályosítja az eredeti adatokat, és lelassítja a lehetséges támadókat, fejlesztőként arra kell törekednünk, hogy a rendelkezésre álló legerősebb algoritmust használjuk. Szerencsére a PHP ezt megkönnyíti a password_hash() – nak köszönhetően.

$hash = password_hash($password, PASSWORD_DEFAULT);

a password_hash() funkció nem csak biztonságos egyirányú hash algoritmust használ, hanem automatikusan kezeli a salt-t és megakadályozza az időalapú oldalcsatornás támadásokat. A PHP 5.5-től kezdve a bcrypt lesz a hash generálására, de ez a jövőben megváltozik, mivel újabb és biztonságosabb hash algoritmusok kerülnek a PHP-be. Az Argon2 valószínűleg a következő alapértelmezett hash algoritmus lesz, és ma is használható (PHP 7.2-n) a PASSWORD_ARGON2I jelző PASSWORD_DEFAULThelyett.

a felhasználói jelszó ellenőrzése szintén triviális folyamat a password_verify() funkciónak köszönhetően. Egyszerűen adja át a felhasználó által megadott egyszerű szöveges jelszót, majd hasonlítsa össze a tárolt kivonattal:

if (password_verify($password, $hash)) { echo "Let me in, I'm genuine!";}

figyelje meg, hogyan történik a jelszó ellenőrzése a PHP-ben. Ha egy felhasználó hitelesítő adatait tárolja egy adatbázisban, akkor hajlamos lehet a bejelentkezéskor megadott jelszó kivonatolására, majd az adatbázis lekérdezésére:

SELECT * FROM usersWHERE username = 'Ashley'AND password = 'password_hash'LIMIT 1;

ez a megközelítés hajlamos az oldalsó csatornás támadásokra, ezért kerülni kell. Ehelyett adja vissza a felhasználót, majd ellenőrizze a jelszó kivonatát a PHP-ben.

SELECT username, password FROM usersWHERE username = 'Ashley'LIMIT 1;

míg a kivonatolás kiválóan alkalmas a felhasználó jelszavának tárolására, nem működik olyan tetszőleges adatok esetén, amelyekhez az alkalmazásnak felhasználói beavatkozás nélkül kell hozzáférnie. Vegyünk egy számlázási alkalmazást, amely titkosítja a felhasználó hitelkártya-adatait. Minden hónapban alkalmazásunknak ki kell számláznia a felhasználót az előző havi használatáért. A hitelkártya-adatok kivonatolása nem fog működni, mert megköveteli, hogy alkalmazásunk ismerje az eredeti adatokat, hogy egyszerű szövegben töltse le.

titkos kulcs titkosítás a mentéshez!

titkos kulcs titkosítása

a titkos kulcs titkosítása (vagy szimmetrikus titkosítás, ahogy az is ismert) egyetlen kulcsot használ az adatok titkosításához és visszafejtéséhez. Lássuk, hogyan valósítanánk meg egy ilyen mechanizmust nátrium felhasználásával, amelyet a PHP 7.2-ben vezettek be. Ha a PHP régebbi verzióját futtatja, telepítheti a nátriumot a PECL-en keresztül.

először egy titkosítási kulcsra van szükségünk, amelyet a random_bytes() funkcióval lehet létrehozni. Általában ezt csak egyszer teszi meg, és környezeti változóként tárolja. Ne feledje, hogy ezt a kulcsot minden áron titokban kell tartani. Ha a kulcs veszélybe kerül, akkor minden titkosított adat is.

$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

az érték titkosításához adjuk át a sodium_crypto_secretbox() – nek a $key – vel és a $nonce – vel. A nonce a random_bytes() használatával jön létre, mert ugyanazt a nonce-t soha nem szabad újra felhasználni.

$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);$ciphertext = sodium_crypto_secretbox('This is a secret!', $nonce, $key);

ez problémát jelent, mert szükségünk van a nonce-re az érték későbbi visszafejtéséhez. Szerencsére a nonces-t nem kell titokban tartani, így a $ciphertext, majd a base64_encode() érték elé írhatjuk, mielőtt elmentenénk az adatbázisba.

$encoded = base64_encode($nonce . $ciphertext);var_dump($encoded);// string 'v6KhzRACVfUCyJKCGQF4VNoPXYfeFY+/pyRZcixz4x/0jLJOo+RbeGBTiZudMLEO7aRvg44HRecC' (length=76)

amikor az értéket visszafejtjük, az ellenkezőjét tesszük.

$decoded = base64_decode($encoded);

mivel tudjuk a nonce hosszát, az érték visszafejtése előtt kinyerhetjük a mb_substr() használatával.

$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);var_dump($plaintext);// string 'This is a secret!' (length=17)

ez minden, ami a titkos kulcs titkosításához szükséges a PHP-ben, a nátriumnak köszönhetően!

boríték titkosítás

bár a fent vázolt megközelítés minden bizonnyal egy lépés a helyes irányba, még mindig sebezhetővé teszi adatainkat, ha a titkos kulcs veszélybe kerül. Vegyünk egy rosszindulatú felhasználót, aki hozzáférést kap az alkalmazásunkat tároló szerverhez. Ebben a forgatókönyvben valószínű, hogy a támadó felfedezheti titkos kulcsunkat, amelyet az adatok titkosításához használtunk. Ez teljesen felfedi az adatainkat.

az egyszerű megoldás az, hogy nem tároljuk a titkos kulcsot ugyanazon a helyen, mint a titkosított adatok, de ez problémát jelent. Hogyan titkosíthatjuk és dekódolhatjuk igény szerint? Adja meg a Google Cloud Key Management Service (Cloud KMS) szolgáltatást.

a Cloud KMS a Google által nyújtott szolgáltatás a kriptográfiai kulcsok biztonságos tárolására. Számos hasznos funkciót kínál a kulcstárolás körül, beleértve az automatikus kulcsforgatást és a késleltetett kulcsmegsemmisítést. Ebben a példában azonban elsősorban azzal foglalkozunk, hogy titkos kulcsunkat az adatainktól távol tároljuk.

hogy biztonságosabbá tegyük a dolgokat, egy boríték titkosításként ismert technikát fogunk használni. Lényegében a boríték titkosítása magában foglalja a kulcsok titkosítását egy másik kulccsal. Ezt két okból tesszük:

  1. a Cloud KMS 64 KiB méretkorláttal rendelkezik a titkosítható és visszafejthető adatokon. Ezért előfordulhat, hogy nem lehet az összes adatot egy csapásra elküldeni.
  2. ennél is fontosabb, hogy érzékeny szöveges adatainkat nem akarjuk harmadik félnek elküldeni, függetlenül attól, hogy azok mennyire megbízhatónak tűnnek.

ahelyett, hogy egyszerű szöveges adatokat küldenénk a Cloud KMS-be, egyedi titkosítási kulcsot fogunk létrehozni minden alkalommal, amikor érzékeny adatokat írunk az adatbázisba. Ez a kulcs adat titkosítási kulcs (dek) néven ismert, amelyet adataink titkosítására használunk. Ezután a DEK-t elküldik a Cloud KMS-nek titkosításra, amely visszaad egy kulcs titkosítási kulcsot (kek néven ismert). Végül a KEK-et egymás mellett tárolják az adatbázisban a titkosított adatok mellett, és a DEK megsemmisül. A folyamat így néz ki:

  1. hozzon létre egy egyedi titkosítási kulcsot (dek)
  2. titkosítsa az adatokat titkos kulcs titkosítással
  3. küldje el az egyedi titkosítási kulcsot (DEK) a cloud KMS-be titkosítás céljából, amely visszaadja a KEK
  4. tárolja a titkosított adatokat és a titkosított kulcsot (kek) egymás mellett
  5. semmisítse meg a generált kulcsot (DEK))

az adatok visszafejtésekor a folyamat megfordul:

  1. a titkosított adatok és a titkosított kulcs (kek) lekérése az adatbázisból
  2. küldje el a KEK-t a Cloud KMS-be dekódolásra, amely visszaadja a dek-et
  3. használja a DEK-et a titkosított adatok visszafejtéséhez
  4. pusztítsa el a DEK-t

ezt szem előtt tartva létrehoztam egy nagyon egyszerű segítő osztály a boríték titkosításához. Nem fogom áttekinteni a Google Cloud console szükséges lépéseit, mivel a gyorsindítási és hitelesítési útmutatók mindent felvázolnak, amire szüksége van az induláshoz. A rövidség kedvéért nincs hibakezelés stb. ebben a példában.

<?phpuse Google_Service_CloudKMS as Kms;use Google_Service_CloudKMS_DecryptRequest as DecryptRequest;use Google_Service_CloudKMS_EncryptRequest as EncryptRequest;class KeyManager{ private $kms; private $encryptRequest; private $decryptRequest; private $projectId; private $locationId; private $keyRingId; private $cryptoKeyId; public function __construct(Kms $kms, EncryptRequest $encryptRequest, DecryptRequest $decryptRequest, $projectId, $locationId, $keyRingId, $cryptoKeyId) { $this->kms = $kms; $this->encryptRequest = $encryptRequest; $this->decryptRequest = $decryptRequest; $this->projectId = $projectId; $this->locationId = $locationId; $this->keyRingId = $keyRingId; $this->cryptoKeyId = $cryptoKeyId; } public function encrypt($data) { $key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); $ciphertext = sodium_crypto_secretbox($data, $nonce, $key); return ; } public function decrypt($secret, $data) { $decoded = base64_decode($data); $key = $this->decryptSecret($secret); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); return sodium_crypto_secretbox_open($ciphertext, $nonce, $key); } private function encryptKey($key) { $this->encryptRequest->setPlaintext(base64_encode($key)); $response = $this->kms->projects_locations_keyRings_cryptoKeys->encrypt( $this->getResourceName(), $this->encryptRequest ); return $response; } private function decryptSecret($secret) { $this->decryptRequest->setCiphertext($secret); $response = $this->kms->projects_locations_keyRings_cryptoKeys->decrypt( $this->getResourceName(), $this->decryptRequest ); return base64_decode($response); } private function getResourceName() { return sprintf( 'projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s', $this->projectId, $this->locationId, $this->keyRingId, $this->cryptoKeyId ); }}

észre fogod venni, hogy a tényleges titkosítási és dekódolási módszerek szinte azonosak a fent bemutatott titkos kulcs implementációval. A különbség azonban az, hogy most több titkosítási kulcsot használunk. Lássuk a segítő osztályt akcióban. Meg kell adnia a $projectId, $locationId, $keyRingId és $cryptoKeyId, amelyek a Google Cloud console-ból érhetők el.

<?phpuse Google_Service_CloudKMS as Kms;use Google_Service_CloudKMS_DecryptRequest as DecryptRequest;use Google_Service_CloudKMS_EncryptRequest as EncryptRequest;$client = new Google_Client();$client->setAuthConfig(getenv('GOOGLE_CREDENTIALS_FILE'));$client->addScope('https://www.googleapis.com/auth/cloud-platform');$keyManager = new KeyManager( new Kms($client), new EncryptRequest(), new DecryptRequest(), $projectId, $locationId, $keyRingId, $cryptoKeyId);$encrypted = $keyManager->encrypt('This is a secret!');var_dump($encrypted);// array (size=2)// 'data' => string 'uKjmEU7e1JEU+2vL3hBK2wBk6afCSgb+Y4GQtu/mmLuffgHlnqxnqOMPOI6WGkM18vAGGvFVDTvd' (length=76)// 'secret' => string 'CiQAdA0emUW2nhlU3RijX/5GnUsTnPPrQdLZNxdHWXWYugx49a4SSQBHyYr0T/PEbKwyFhIkaZl28oKkJRkXqNcqOL4Z+OTQFLpGvS6zCDt2mFn/nUQ/bi4znD4DORk9ZDTqiIBK3UNFUZcrXvoExds=' (length=152)$decrypted = $keyManager->decrypt($encrypted, $encrypted);var_dump($decrypted);// string 'This is a secret!' (length=17)

ha egy támadó feltörte a rendszerünket, képesek lennének megszerezni a Cloud KMS API hitelesítő adatait is? Az alkalmazott hitelesítési módszertől függően, akkor igen lehetőség lehet. Ha ez a helyzet, akkor kíváncsi lehet, hogy a boríték titkosítása biztonságosabb-e, mint a szokásos titkos kulcs titkosítás? A legfontosabb különbség (pun intended) az, hogy az API-hozzáférés visszavonható, így meghiúsítva egy támadót, aki az érzékeny adatokkal készült. Ez egyenértékű a zárak cseréjével, ha valaki ellopja a ház kulcsát. A rendszeres titkos kulcs titkosítással, ahol egyetlen helyi kulcs sérül, nincs ilyen luxus. A támadónak a világon minden ideje van az érzékeny adatok visszafejtésére.

csomagolás

az adatbiztonság és a titkosítás hatalmas témák, és csak néhány módszert fedeztem fel az érzékeny adatok védelmére a PHP használatával. (Korábban írtunk az ilyen típusú adatok védelméről a helyi környezetben)

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.

Previous post Frissítés: a rendőrség letartóztatja az állítólagos lövöldözőt a Shady Oaks – ban-Albert Lea Tribune / Albert Lea Tribune
Next post hogyan lehet eltávolítani az olaj-és zsírfoltokat a ruhákon