PHP krypteringsmetoder til adgangskoder og andre følsomme Data

jeg deltog for nylig i Laracon EU 2018, hvor Marcus Bointon holdt en god tale om krypto i PHP 7.2. Jeg forlod samtalen med en meget større forståelse for, hvor meget kompliceret kryptografi er, men også for, hvordan PHP gør kryptering mere tilgængelig takket være introduktionen af natrium. Datakryptering i PHP er noget, jeg har arbejdet på som en del af mit arbejde med Spinup, så jeg troede, det var på tide, at jeg delte et par indsigter. Spænde op, fordi dette kunne være en ujævn tur!

typer af kryptering

der er en række forskellige krypteringsmetoder i brug i dag, den mest almindelige er hashing, hemmelig nøglekryptering og offentlig nøglekryptering. Derudover har hver krypteringsmetode flere algoritmer eller cifre at vælge imellem (hver med deres egne styrker og svagheder). I denne artikel vil vi fokusere på implementering af hashing og hemmelig nøglekryptering.

Hashing

en hashingalgoritme tager en inputværdi og omdanner den til en meddelelsesfordøjelse. I en nøddeskal omdannes almindelig tekstværdier til en hash med fast længde og kan kun valideres ved at overføre den oprindelige værdi til hashingalgoritmen. Dette gør hashing perfekt til lagring af brugeradgangskoder.

det er værd at bemærke, at hashing ikke er en skudsikker løsning, og ikke alle hashingalgoritmer er ens. Overvej MD5 og SHA1, som er hurtige og effektive, hvilket gør dem ideelle til checksumming og fil verifikation. Deres hastighed gør dem imidlertid uegnede til hashing af en brugers adgangskode. Med dagens beregningskraft af moderne GPU ‘ er kan en adgangskode knækkes af brute force i løbet af få minutter, hvilket afslører den originale adgangskode til almindelig tekst. I stedet bør bevidst langsommere hashingalgoritmer som bcrypt eller Argon2 anvendes.

mens en hashet adgangskode genereret af enhver algoritme helt sikkert vil skjule de originale data og bremse enhver potentiel angriber, bør vi som udviklere stræbe efter at bruge den stærkeste tilgængelige algoritme. Heldigvis gør PHP det nemt takket være password_hash().

$hash = password_hash($password, PASSWORD_DEFAULT);

funktionen password_hash() bruger ikke kun en sikker envejs hashingalgoritme, men den håndterer automatisk salt og forhindrer tidsbaserede sidekanalangreb. Fra PHP 5.5 vil bcrypt blive brugt til at generere hash, men dette vil ændre sig i fremtiden, da nyere og mere sikre hashingalgoritmer føjes til PHP. Argon2 vil sandsynligvis blive den næste standard hashing algoritme og kan bruges i dag (på PHP 7.2) ved at passere PASSWORD_ARGON2I flag i stedet for PASSWORD_DEFAULT.

bekræftelse af en brugers adgangskode er også en triviel proces takket være funktionen password_verify(). Du skal blot videregive adgangskoden til almindelig tekst, der leveres af brugeren, og sammenligne den med den gemte hash, som sådan:

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

bemærk, hvordan adgangskodebekræftelsen udføres i PHP. Hvis du gemmer en brugers legitimationsoplysninger i en database, kan du være tilbøjelig til at hash den adgangskode, der blev indtastet ved login, og derefter udføre en databaseforespørgsel, som sådan:

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

denne tilgang er modtagelig for sidekanalangreb og bør undgås. I stedet skal du returnere brugeren og derefter kontrollere adgangskoden hash i PHP.

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

selvom hashing er fantastisk til lagring af en brugers adgangskode, fungerer det ikke for vilkårlige data, som vores applikation skal have adgang til uden brugerintervention. Lad os overveje en faktureringsapplikation, der krypterer en brugers kreditkortoplysninger. Hver måned skal vores ansøgning fakturere brugeren for deres tidligere måneds brug. Hashing kreditkort data vil ikke arbejde, fordi det kræver, at vores ansøgning kender de oprindelige data for det hente det i klartekst.

hemmelig nøglekryptering til redning!

hemmelig nøglekryptering

hemmelig nøglekryptering (eller symmetrisk kryptering, som den også er kendt) bruger en enkelt nøgle til både at kryptere og dekryptere data. Lad os se, hvordan vi ville implementere en sådan mekanisme ved hjælp af natrium, som blev introduceret i PHP 7.2. Hvis du kører en ældre version af PHP, kan du installere natrium via PECL.

først har vi brug for en krypteringsnøgle, som kan genereres ved hjælp af funktionen random_bytes(). Normalt gør du dette kun en gang og gemmer det som en miljøvariabel. Husk, at denne nøgle skal holdes hemmelig for enhver pris. Når nøglen er kompromitteret,så er alle krypterede data.

$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

for at kryptere værdien sender vi den til sodium_crypto_secretbox() med vores $keyog en $nonce. Nonce genereres ved hjælp af random_bytes(), fordi den samme nonce aldrig bør genbruges.

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

dette udgør et problem, fordi vi har brug for nonce til at dekryptere værdien senere. Heldigvis behøver nonces ikke holdes hemmelige, så vi kan forberede det til vores $ciphertext derefter base64_encode() værdien, før vi gemmer den i databasen.

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

når det kommer til dekryptering af værdien, gør vi det modsatte.

$decoded = base64_decode($encoded);

fordi vi kender længden af nonce, kan vi udtrække den ved hjælp af mb_substr() før dekryptering af værdien.

$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 er alt, hvad der er til hemmelig nøglekryptering i PHP, takket være natrium!

Konvolutkryptering

mens fremgangsmåden beskrevet ovenfor bestemt er et skridt i den rigtige retning, efterlader den stadig vores data sårbare, hvis den hemmelige nøgle kompromitteres. Lad os overveje en ondsindet bruger, der får adgang til den server, der er vært for vores applikation. I dette scenario er chancerne for, at angriberen vil kunne opdage vores hemmelige nøgle, som vi brugte til at kryptere dataene. Dette efterlader vores data fuldstændigt eksponeret.

den enkle løsning er ikke at gemme vores hemmelige nøgle på samme sted som de krypterede data, men dette udgør et problem. Hvordan krypterer og dekrypterer vi on demand? Indtast Google Cloud Key Management Service (Cloud KMS).

Cloud KMS er en tjeneste, der leveres af Google til sikker hosting kryptografiske nøgler. Det giver en række nyttige funktioner omkring nøgle opbevaring, herunder automatisk nøgle rotation og forsinket nøgle ødelæggelse. I dette eksempel er vi dog primært optaget af at gemme vores hemmelige nøgle væk fra vores data.

for at gøre tingene mere sikre skal vi bruge en teknik kendt som konvolutkryptering. I det væsentlige involverer konvolutkryptering kryptering af nøgler med en anden nøgle. Vi gør dette af to grunde:

  1. Cloud KMS har en størrelsesgrænse på 64 kB på de data, der kan krypteres og dekrypteres. Derfor er det muligvis ikke muligt at sende alle dataene i et fald.
  2. endnu vigtigere ønsker vi ikke at sende vores følsomme klartekstdata til en tredjepart, uanset hvor pålidelige de kan synes.

i stedet for at sende vores klartekstdata til Cloud KMS, genererer vi en unik krypteringsnøgle, hver gang vi skriver følsomme data til databasen. Denne nøgle er kendt som en datakrypteringsnøgle (DEK), som vil blive brugt til at kryptere vores data. DEK sendes derefter til Cloud KMS, der skal krypteres, hvilket returnerer en nøglekrypteringsnøgle (kendt som en KEK). Endelig gemmes KEK side om side i databasen ved siden af de krypterede data, og DEK ødelægges. Processen ser sådan ud:

  1. Generer en unik krypteringsnøgle (DEK)
  2. Krypter dataene ved hjælp af hemmelig nøglekryptering
  3. Send den unikke krypteringsnøgle (DEK) til Cloud KMS til kryptering, som returnerer KEK
  4. Gem de krypterede data og krypterede nøgle (KEK) side om side
  5. Ødelæg den genererede nøgle (DEK)

ved dekryptering af data vendes processen:

  1. Hent de krypterede data og krypterede nøgle (KEK) fra databasen
  2. Send KEK til Cloud KMS til dekryptering, som returnerer DEK
  3. brug DEK til at dekryptere vores krypterede data
  4. Ødelæg DEK

med dette i tankerne har jeg oprettet en meget enkel hjælperklasse til udførelse af konvolutkryptering. Jeg vil ikke gennemgå de trin, der kræves i Google Cloud console, da hurtigstart-og Godkendelsesvejledningerne skitserer alt hvad du har brug for for at komme i gang. Af hensyn til kortfattethed er der ingen fejlhåndtering osv. i dette eksempel.

<?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 vil bemærke, at de faktiske krypterings-og dekrypteringsmetoder er næsten identiske med den hemmelige nøgleimplementering, der er introduceret ovenfor. Forskellen er dog, at vi nu bruger flere krypteringsnøgler. Lad os se hjælperklassen i aktion. Du bliver nødt til at give din $projectId, $locationId, $keyRingId og $cryptoKeyId som er tilgængelige fra 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)

hvis en angriber kompromitterede vores system, ville de også kunne få vores API-legitimationsoplysninger til Cloud KMS? Afhængigt af den anvendte godkendelsesmetode, så ja det kan være en mulighed. Hvis det er tilfældet, undrer du dig måske over, hvordan konvolutkryptering er mere sikker end almindelig hemmelig nøglekryptering? Nøgleforskellen (ordspil beregnet) er, at API-adgang kan tilbagekaldes og dermed modvirke en angriber, der er lavet med dine følsomme data. Det svarer til at ændre dine låse, hvis nogen stjæler din husnøgle. Med regelmæssig hemmelig nøglekryptering, hvor en enkelt lokal nøgle er kompromitteret, har du ikke den luksus. Angriberen har hele tiden i verden til at dekryptere dine følsomme data.

indpakning

datasikkerhed og kryptering er store emner, og jeg har kun dækket en håndfuld måder at beskytte følsomme data ved hjælp af PHP. (Vi skrev tidligere om at beskytte denne form for data i dit lokale miljø)

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.

Previous post Opdatering: politiet arresterer påstået skydespil ved Shady Oaks-Albert Lea Tribune / Albert Lea Tribune
Next post Sådan fjerner du olie-og fedtpletter på tøj