PHP-versleutelingsmethoden voor wachtwoorden en andere gevoelige gegevens

ik heb onlangs bijgewoond Laracon EU 2018 waar Marcus Bointon gaf een geweldig gesprek over Crypto in PHP 7.2. Ik verliet de talk met een veel grotere waardering voor hoe enorm ingewikkeld cryptografie is, maar ook voor hoe PHP is het maken van encryptie toegankelijker dankzij de introductie van natrium. Data-encryptie in PHP is iets wat ik heb gewerkt aan als onderdeel van mijn werk op SpinupWP dus ik dacht dat het tijd was dat ik deelde een paar inzichten. Riemen vast, want dit kan een hobbelige rit worden!

soorten encryptie

er zijn een reeks verschillende encryptie methoden in gebruik, de meest voorkomende zijn hashing, secret key en public key encryptie. Bovendien, elke encryptie methode heeft meerdere algoritmen of cijfers om uit te kiezen (elk met hun eigen sterke en zwakke punten). In dit artikel gaan we ons richten op het implementeren van hashing en secret key encryptie.

Hashing

een hashing-algoritme neemt een invoerwaarde en transformeert deze naar een berichtsamenvatting. In een notendop, platte tekst waarden worden getransformeerd naar een vaste lengte hash, en kan alleen worden gevalideerd door het doorgeven van de oorspronkelijke waarde aan de hashing algoritme. Dit maakt hashing perfect voor het opslaan van gebruikerswachtwoorden.

het is vermeldenswaard dat hashing geen kogelvrije oplossing is en dat niet alle hashing-algoritmen gelijk zijn. Overweeg MD5 en SHA1 die snel en efficiënt zijn, waardoor ze ideaal zijn voor controlesummering en bestandscontrole. Echter, hun snelheid maakt ze ongeschikt voor hashing wachtwoord van een gebruiker. Met de huidige computationele kracht van moderne GPU ‘ s, een wachtwoord kan worden gekraakt door brute kracht in een kwestie van minuten, het onthullen van de originele platte tekst wachtwoord. In plaats daarvan moeten opzettelijk langzamere hashing algoritmen zoals bcrypt of Argon2 worden gebruikt.

hoewel een gehasht wachtwoord gegenereerd door een algoritme zeker de originele gegevens zal verduisteren en elke mogelijke aanvaller zal vertragen, zouden wij als ontwikkelaars ernaar moeten streven om het sterkste beschikbare algoritme te gebruiken. Gelukkig maakt PHP dit eenvoudig dankzij password_hash().

$hash = password_hash($password, PASSWORD_DEFAULT);

de functie password_hash() gebruikt niet alleen een veilig eenrichtings hashing-algoritme, maar het behandelt ook automatisch salt en voorkomt op tijd gebaseerde zijkanaalaanvallen. Vanaf PHP 5.5, zal bcrypt worden gebruikt om de hash te genereren, maar dit zal in de toekomst veranderen als nieuwere en veiligere hashing algoritmen worden toegevoegd aan PHP. Argon2 zal waarschijnlijk het volgende standaard hashing algoritme worden en kan vandaag gebruikt worden (op PHP 7.2) door de PASSWORD_ARGON2I vlag te geven in plaats van PASSWORD_DEFAULT.

het controleren van het wachtwoord van een gebruiker is ook een triviaal proces dankzij de functie password_verify(). Geef gewoon het door de gebruiker opgegeven wachtwoord in platte tekst door en vergelijk het met de opgeslagen hash, zoals zo:

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

let op hoe de wachtwoordverificatie wordt uitgevoerd in PHP. Als u de referenties van een gebruiker in een database opslaat, kunt u geneigd zijn om het wachtwoord dat bij het inloggen is ingevoerd te hasheren en vervolgens een database-query uit te voeren, zoals zo:

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

deze aanpak is gevoelig voor side-channel aanvallen en moet worden vermeden. In plaats daarvan, retourneer de gebruiker en controleer dan de wachtwoord hash in PHP.

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

hoewel hashing geweldig is voor het opslaan van een wachtwoord van een gebruiker, werkt het niet voor willekeurige gegevens die onze applicatie nodig heeft om toegang te krijgen zonder tussenkomst van de gebruiker. Laten we eens kijken naar een factureringstoepassing, die de creditcardgegevens van een gebruiker versleutelt. Elke maand moet onze applicatie de gebruiker factureren voor het gebruik van de vorige maand. Hashing van de creditcardgegevens zal niet werken, omdat het vereist dat onze applicatie weet dat de originele gegevens voor het ophalen in platte tekst.

geheime sleutelversleuteling te hulp!

Secret Key encryptie

Secret key encryptie (of symmetrische encryptie zoals het ook bekend staat) gebruikt één sleutel om gegevens te versleutelen en te decoderen. Laten we eens kijken hoe we een dergelijk mechanisme zouden implementeren met behulp van Sodium, dat werd geïntroduceerd in PHP 7.2. Als je een oudere versie van PHP draait, kun je sodium installeren via PECL.

eerst hebben we een encryptiesleutel nodig, die kan worden gegenereerd met de random_bytes() functie. Meestal doe je dit maar één keer en sla je het op als een omgevingsvariabele. Vergeet niet dat deze sleutel tot elke prijs geheim moet worden gehouden. Zodra de sleutel is gecompromitteerd, zo is alle versleutelde gegevens.

$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

om de waarde te versleutelen geven we deze door aan sodium_crypto_secretbox() met onze $key en een $nonce. De nonce wordt gegenereerd met random_bytes(), omdat dezelfde nonce nooit mag worden hergebruikt.

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

dit vormt een probleem omdat we de nonce nodig hebben om de waarde later te decoderen. Gelukkig hoeven nonces niet geheim gehouden te worden, dus we kunnen het vooraf toevoegen aan onze $ciphertext dan base64_encode() de waarde voordat we het opslaan in de database.

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

als het gaat om het decoderen van de waarde, doen we het tegenovergestelde.

$decoded = base64_decode($encoded);

omdat we de lengte van nonce kennen, kunnen we deze extraheren met mb_substr() voordat we de waarde ontcijferen.

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

dat is alles wat er is om geheime sleutel versleuteling in PHP, dankzij Sodium!

Envelope encryptie

hoewel de hierboven beschreven aanpak zeker een stap in de goede richting is, blijft het onze gegevens kwetsbaar als de geheime sleutel wordt aangetast. Laten we eens kijken naar een kwaadaardige gebruiker die toegang krijgt tot de server die onze applicatie host. In dit scenario is de kans groot dat de aanvaller in staat zal zijn om onze geheime sleutel te ontdekken die we gebruikten om de gegevens te versleutelen. Dit laat onze gegevens volledig bloot.

de eenvoudige oplossing is om onze geheime sleutel niet op dezelfde locatie op te slaan als de versleutelde gegevens, maar dit levert een probleem op. Hoe versleutelen en decoderen we op aanvraag? Voer Google Cloud Key Management Service (Cloud KMS) in.

Cloud KMS is een service van Google voor het veilig hosten van cryptografische sleutels. Het biedt een verscheidenheid aan handige functies rond sleutelopslag, waaronder automatische sleutelrotatie en vertraagde sleutelvernietiging. Echter, in dit voorbeeld zijn we vooral bezig met het opslaan van onze geheime sleutel uit de buurt van onze gegevens.

om de zaken veiliger te maken gaan we een techniek gebruiken die bekend staat als envelope encryptie. In wezen omvat envelope encryptie het versleutelen van sleutels met een andere sleutel. We doen dit om twee redenen:

  1. Cloud KMS heeft een limiet van 64 KiB op de gegevens die kunnen worden versleuteld en gedecodeerd. Daarom is het misschien niet mogelijk om alle gegevens in één klap te verzenden.
  2. belangrijker is dat we onze gevoelige platte tekst gegevens niet naar een derde partij willen sturen, ongeacht hoe betrouwbaar ze lijken.

in plaats van onze platte tekst gegevens naar Cloud KMS te sturen, gaan we een unieke encryptiesleutel genereren elke keer dat we gevoelige gegevens naar de database schrijven. Deze sleutel staat bekend als een data encryption key (DEK), die zal worden gebruikt om onze gegevens te versleutelen. Het DEK wordt vervolgens verzonden naar Cloud KMS te worden versleuteld, die een sleutel encryptiesleutel retourneert (bekend als een KEK). Ten slotte wordt de KEK naast de versleutelde data in de database opgeslagen en wordt het DEK vernietigd. Het proces ziet er zo uit:

  1. Genereer een unieke versleutelingssleutel (DEK)
  2. Versleutel de gegevens met behulp van geheime sleutelversleuteling
  3. stuur de unieke versleutelingssleutel (DEK) naar Cloud KMS voor versleuteling, die de KEK
  4. geeft de versleutelde gegevens en versleutelde sleutel (KEK) side-by-side
  5. vernietig de gegenereerde sleutel (DEK))

bij het decoderen van gegevens wordt het proces omgekeerd:

  1. haal de versleutelde gegevens en versleutelde sleutel (KEK) op uit de database
  2. stuur de KEK naar Cloud KMS voor decryptie, die het DEK
  3. geeft gebruik het DEK om onze versleutelde gegevens te decoderen
  4. vernietig het DEK

met dit in gedachten heb ik een zeer eenvoudige helper class gemaakt voor het uitvoeren van envelop encryptie. Ik ben niet van plan om te gaan over de stappen die nodig zijn in de Google Cloud console, als de Quickstart en authenticatie gidsen schetsen alles wat je nodig hebt om te beginnen. Omwille van de beknoptheid is er geen foutafhandeling etc. in dit voorbeeld.

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

u zult merken dat de werkelijke encryptie en decryptie methoden zijn bijna identiek aan de geheime sleutel implementatie hierboven geà ntroduceerd. Het verschil is echter dat we nu meerdere encryptiesleutels gebruiken. Laten we de helperklas in actie zien. U moet uw $projectId, $locationId, $keyRingId en $cryptoKeyId die beschikbaar zijn op de 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)

als een aanvaller ons systeem gecompromitteerd heeft, zouden ze dan ook onze API-referenties voor Cloud KMS kunnen verkrijgen? Afhankelijk van de gebruikte authenticatiemethode, dan ja het kan een mogelijkheid zijn. Als dat het geval is, vraagt u zich misschien af hoe envelope encryptie veiliger is dan gewone secret key encryptie? Het belangrijkste verschil (woordspeling bedoeld) is dat API-toegang kan worden ingetrokken, dus het dwarsbomen van een aanvaller die is gemaakt met uw gevoelige gegevens. Het is het equivalent van het veranderen van je sloten als iemand je huissleutel steelt. Met reguliere geheime sleutel encryptie waar een enkele lokale sleutel is gecompromitteerd heb je niet die luxe. De aanvaller heeft alle tijd in de wereld om uw gevoelige gegevens te decoderen.

het inpakken van

Data beveiliging en encryptie zijn enorme onderwerpen en ik heb slechts een handvol manieren behandeld om gevoelige data te beschermen met behulp van PHP. (We schreven eerder over het beschermen van dit soort gegevens in uw lokale omgeving)

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.

Previous post Update: politie arrest vermeende schutter bij Shady Oaks-Albert Lea Tribune / Albert Lea Tribune
Next post hoe olie-en vetvlekken op kleding te verwijderen