PHP-krypteringsmetoder för lösenord och annan känslig Data

jag deltog nyligen i Laracon EU 2018 där Marcus Bointon höll ett bra föredrag om krypto i PHP 7.2. Jag lämnade samtalet med en mycket större uppskattning för hur väldigt komplicerad kryptografi är, men också för hur PHP gör kryptering mer tillgänglig tack vare introduktionen av natrium. Datakryptering i PHP är något jag har arbetat med som en del av mitt arbete med SpinupWP så jag trodde att det var dags att jag delade några insikter. Spänn fast dig, för det här kan vara en ojämn åktur!

typer av kryptering

det finns en rad olika krypteringsmetoder som används idag, den vanligaste är hashing, hemlig nyckelkryptering och offentlig nyckelkryptering. Dessutom har varje krypteringsmetod flera algoritmer eller chiffer att välja mellan (var och en med sina egna styrkor och svagheter). I den här artikeln kommer vi att fokusera på att implementera hashing och hemlig nyckelkryptering.

Hashing

en hashingalgoritm tar ett inmatningsvärde och omvandlar det till ett meddelande digest. I ett nötskal, är klartext värden omvandlas till en fast längd hash, och kan endast valideras genom att föra det ursprungliga värdet till hashing algoritmen. Detta gör hashing perfekt för att lagra användarlösenord.

det är värt att notera att hashing inte är en skottsäker lösning och inte alla hashingalgoritmer är lika. Överväg MD5 och SHA1 som är snabba och effektiva, vilket gör dem idealiska för kontrollsummering och filverifiering. Men deras hastighet gör dem olämpliga för hashing en användares lösenord. Med dagens beräkningskraft moderna grafikprocessorer, kan ett lösenord knäckas av brute force på några minuter, avslöjar den ursprungliga klartext lösenord. Istället bör avsiktligt långsammare hashingalgoritmer som bcrypt eller Argon2 användas.

medan ett hashat lösenord som genereras av någon algoritm säkert kommer att dölja originaldata och sakta ner alla angripare, bör vi som utvecklare sträva efter att använda den starkaste algoritmen som finns tillgänglig. Lyckligtvis gör PHP det enkelt tack vare password_hash().

$hash = password_hash($password, PASSWORD_DEFAULT);

funktionen password_hash() använder inte bara en säker envägs hashingalgoritm, men den hanterar automatiskt salt och förhindrar tidsbaserade sidokanalattacker. Från och med PHP 5.5 kommer bcrypt att användas för att generera hash, men detta kommer att förändras i framtiden när nyare och säkrare hashingalgoritmer läggs till PHP. Argon2 kommer sannolikt att bli nästa standard hashing algoritm och kan användas idag (på PHP 7.2) genom att föra PASSWORD_ARGON2I flagga i stället för PASSWORD_DEFAULT.

att verifiera en användares lösenord är också en trivial process tack vare funktionen password_verify(). Helt enkelt passera klartext lösenord som tillhandahålls av användaren och jämföra den med den lagrade hash, som så:

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

Lägg märke till hur lösenordsverifieringen utförs i PHP. Om du lagrar en användares referenser i en databas kan du vara benägen att hash lösenordet in vid inloggning och sedan utföra en databasfråga, som så:

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

detta tillvägagångssätt är mottagligt för sidokanalattacker och bör undvikas. Returnera istället användaren och kontrollera sedan lösenordshashen i PHP.

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

medan hashing är bra för att lagra en användares lösenord, fungerar det inte för godtyckliga data som vår applikation behöver komma åt utan användarintervention. Låt oss överväga en faktureringsapplikation som krypterar användarens kreditkortsinformation. Varje månad måste vår ansökan fakturera användaren för deras föregående månads användning. Hashing kreditkortsdata kommer inte att fungera eftersom det kräver att vår ansökan Vet de ursprungliga uppgifterna för det Hämta det i klartext.

hemlig nyckelkryptering till undsättning!

hemlig nyckelkryptering

hemlig nyckelkryptering (eller symmetrisk kryptering som det också kallas) använder en enda nyckel för att både kryptera och dekryptera data. Låt oss se hur vi skulle implementera en sådan mekanism med natrium, som introducerades i PHP 7.2. Om du kör en äldre version av PHP kan du installera sodium via PECL.

först behöver vi en krypteringsnyckel som kan genereras med funktionen random_bytes(). Vanligtvis gör du det bara en gång och lagrar det som en miljövariabel. Kom ihåg att denna nyckel måste hållas hemlig till varje pris. När nyckeln äventyras, så är alla krypterade data.

$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

för att kryptera värdet skickar vi det till sodium_crypto_secretbox() med vår $key och en $nonce. Nonce genereras med random_bytes(), eftersom samma nonce aldrig ska återanvändas.

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

detta utgör ett problem eftersom vi behöver nonce för att dekryptera värdet senare. Lyckligtvis behöver nonces inte hållas hemliga så vi kan prepend det till vår $ciphertext sedan base64_encode() värdet innan du sparar det i databasen.

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

när det gäller att dekryptera värdet gör vi motsatsen.

$decoded = base64_decode($encoded);

eftersom vi vet längden på nonce kan vi extrahera den med mb_substr() innan vi dekrypterar värdet.

$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)

det är allt som finns för hemlig nyckelkryptering i PHP, tack vare natrium!

Envelope Encryption

medan tillvägagångssättet som beskrivs ovan verkligen är ett steg i rätt riktning, lämnar det fortfarande våra data sårbara om den hemliga nyckeln äventyras. Låt oss överväga en skadlig användare som får tillgång till servern som är värd för vår applikation. I det här scenariot är chansen att angriparen kommer att kunna upptäcka vår hemliga nyckel som vi använde för att kryptera data. Detta lämnar våra data helt exponerade.

den enkla lösningen är att inte lagra vår hemliga nyckel på samma plats som den krypterade data, men detta utgör ett problem. Hur krypterar och dekrypterar vi på begäran? Ange Google Cloud Key Management Service (Cloud KMS).

Cloud KMS är en tjänst som tillhandahålls av Google för säkert värd kryptografiska nycklar. Det ger en mängd användbara funktioner kring nyckellagring, inklusive automatisk nyckelrotation och fördröjd nyckelförstöring. Men i det här exemplet handlar det främst om att lagra vår hemliga nyckel bort från våra data.

för att göra saker säkrare kommer vi att använda en teknik som kallas envelope encryption. I huvudsak innebär envelope encryption att kryptera nycklar med en annan nyckel. Vi gör detta av två skäl:

  1. Cloud KMS har en storleksgräns på 64 kB på data som kan krypteras och dekrypteras. Därför kanske det inte är möjligt att skicka Alla data i ett fall.
  2. ännu viktigare vill vi inte skicka våra känsliga klartextdata till en tredje part, oavsett hur pålitliga de kan verka.

istället för att skicka våra klartextdata till Molnkms, kommer vi att generera en unik krypteringsnyckel varje gång vi skriver känsliga data till databasen. Denna nyckel är känd som en datakrypteringsnyckel (dek), som kommer att användas för att kryptera våra data. DEK skickas sedan till Cloud KMS för att krypteras, vilket returnerar en nyckelkrypteringsnyckel (känd som en KEK). Slutligen lagras KEK sida vid sida i databasen bredvid krypterad data och DEK förstörs. Processen ser ut så:

  1. generera en unik krypteringsnyckel (DEK)
  2. kryptera data med hemlig nyckelkryptering
  3. skicka den unika krypteringsnyckeln (DEK) till Cloud KMS för kryptering, som returnerar KEK
  4. lagra krypterad data och krypterad nyckel (KEK) sida vid sida
  5. förstör den genererade nyckeln (DEK)

vid dekryptering av data är processen omvänd:

  1. hämta krypterad data och krypterad nyckel (KEK) från databasen
  2. skicka KEK till Cloud KMS för dekryptering, som returnerar DEK
  3. använd DEK för att dekryptera våra krypterade data
  4. förstör DEK

med detta i åtanke har jag skapat en mycket enkel och enkel enkel hjälparklass för att utföra kuvertkryptering. Jag kommer inte att gå igenom de steg som krävs i Google Cloud console, eftersom Snabbstart-och Autentiseringsguiderna beskriver allt du behöver för att komma igång. För korthetens skull finns det ingen felhantering etc. i det här exemplet.

<?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 ); }}

du kommer att märka att de faktiska krypterings-och dekrypteringsmetoderna är nästan identiska med den hemliga nyckelimplementeringen som infördes ovan. Skillnaden är dock att vi nu använder flera krypteringsnycklar. Låt oss se hjälparklassen i aktion. Du måste ge din $projectId, $locationId, $keyRingId och $cryptoKeyId som är tillgängliga från Google Cloud console.

<?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)

om en angripare komprometterade vårt system, skulle de också kunna få våra API-referenser för Cloud KMS? Beroende på vilken autentiseringsmetod som används, ja det kan vara en möjlighet. Om så är fallet kanske du undrar hur envelope encryption är säkrare än vanlig hemlig nyckelkryptering? Den viktigaste skillnaden (pun intended) är att API-åtkomst kan återkallas, vilket motverkar en angripare som är gjord av med dina känsliga data. Det motsvarar att byta lås Om någon stjäl din husnyckel. Med vanlig hemlig nyckelkryptering där en enda lokal nyckel äventyras har du inte den lyxen. Angriparen har hela tiden i världen att dekryptera dina känsliga data.

förpackning upp

datasäkerhet och kryptering är stora ämnen och jag har bara täckt en handfull sätt att skydda känslig data med PHP. (Vi skrev tidigare om att skydda denna typ av data i din lokala miljö)

Lämna ett svar

Din e-postadress kommer inte publiceras.

Previous post Uppdatering: polisen arresterar påstådd skytt på Shady Oaks-Albert Lea Tribune / Albert Lea Tribune
Next post hur man tar bort olje-och fettfläckar på kläder