Méthodes de Cryptage PHP pour les Mots de Passe et autres Données Sensibles

J’ai récemment assisté à Laracon EU 2018 où Marcus Bointon a donné une excellente conférence sur la Crypto en PHP 7.2. J’ai quitté la conférence en appréciant beaucoup plus la complexité de la cryptographie, mais aussi la façon dont PHP rend le chiffrement plus accessible grâce à l’introduction de Sodium. Le cryptage des données en PHP est quelque chose sur lequel j’ai travaillé dans le cadre de mon travail sur SpinupWP, j’ai donc pensé qu’il était temps de partager quelques idées. Bouclez votre ceinture, car cela pourrait être une course cahoteuse!

Types de chiffrement

Il existe aujourd’hui une gamme de méthodes de chiffrement différentes, les plus courantes étant le hachage, le chiffrement à clé secrète et le chiffrement à clé publique. De plus, chaque méthode de chiffrement a le choix entre plusieurs algorithmes ou chiffrements (chacun avec ses propres forces et faiblesses). Dans cet article, nous allons nous concentrer sur la mise en œuvre du hachage et du cryptage à clé secrète.

Hachage

Un algorithme de hachage prend une valeur d’entrée et la transforme en un résumé de message. En un mot, les valeurs en texte brut sont transformées en un hachage de longueur fixe et ne peuvent être validées qu’en transmettant la valeur d’origine à l’algorithme de hachage. Cela rend le hachage parfait pour stocker les mots de passe des utilisateurs.

Il convient de noter que le hachage n’est pas une solution pare-balles et que tous les algorithmes de hachage ne sont pas égaux. Considérez MD5 et SHA1 qui sont rapides et efficaces, ce qui les rend idéaux pour la somme de contrôle et la vérification de fichiers. Cependant, leur vitesse les rend impropres au hachage du mot de passe d’un utilisateur. Avec la puissance de calcul actuelle des GPU modernes, un mot de passe peut être déchiffré par la force brute en quelques minutes, révélant le mot de passe en texte brut d’origine. Au lieu de cela, des algorithmes de hachage intentionnellement plus lents tels que bcrypt ou Argon2 devraient être utilisés.

Bien qu’un mot de passe haché généré par n’importe quel algorithme obscurcira certainement les données d’origine et ralentira tout attaquant potentiel, nous, en tant que développeurs, devrions nous efforcer d’utiliser l’algorithme le plus puissant disponible. Heureusement, PHP rend cela facile grâce à password_hash().

$hash = password_hash($password, PASSWORD_DEFAULT);

La fonction password_hash() utilise non seulement un algorithme de hachage unidirectionnel sécurisé, mais elle gère automatiquement salt et empêche les attaques par canal latéral basées sur le temps. À partir de PHP 5.5, bcrypt sera utilisé pour générer le hachage, mais cela changera à l’avenir à mesure que des algorithmes de hachage plus récents et plus sécurisés seront ajoutés à PHP. Argon2 est susceptible de devenir le prochain algorithme de hachage par défaut et peut être utilisé aujourd’hui (sur PHP 7.2) en passant l’indicateur PASSWORD_ARGON2I au lieu de PASSWORD_DEFAULT.

La vérification du mot de passe d’un utilisateur est également un processus trivial grâce à la fonction password_verify(). Il suffit de passer le mot de passe en texte brut fourni par l’utilisateur et de le comparer au hachage stocké, comme ceci:

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

Notez comment la vérification du mot de passe est effectuée en PHP. Si vous stockez les informations d’identification d’un utilisateur dans une base de données, vous pouvez être enclin à hacher le mot de passe entré lors de la connexion, puis à effectuer une requête de base de données, comme ceci:

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

Cette approche est susceptible d’attaques par canal latéral et doit être évitée. Au lieu de cela, renvoyez l’utilisateur, puis vérifiez le hachage du mot de passe en PHP.

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

Bien que le hachage soit idéal pour stocker le mot de passe d’un utilisateur, il ne fonctionne pas pour les données arbitraires auxquelles notre application doit accéder sans intervention de l’utilisateur. Considérons une application de facturation, qui crypte les informations de carte de crédit d’un utilisateur. Chaque mois, notre application doit facturer l’utilisateur pour son utilisation du mois précédent. Le hachage des données de carte de crédit ne fonctionnera pas car il nécessite que notre application connaisse les données d’origine pour qu’elle les récupère en texte brut.

Chiffrement à clé secrète à la rescousse !

Chiffrement à clé secrète

Le chiffrement à clé secrète (ou chiffrement symétrique comme on l’appelle également) utilise une seule clé pour chiffrer et déchiffrer les données. Voyons comment nous implémenterions un tel mécanisme en utilisant Sodium, qui a été introduit dans PHP 7.2. Si vous utilisez une ancienne version de PHP, vous pouvez installer sodium via PECL.

Nous avons d’abord besoin d’une clé de chiffrement, qui peut être générée à l’aide de la fonction random_bytes(). Habituellement, vous ne le faites qu’une seule fois et le stockez en tant que variable d’environnement. Rappelez-vous que cette clé doit être gardée secrète à tout prix. Une fois la clé compromise, toute donnée chiffrée l’est également.

$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

Pour chiffrer la valeur, nous la transmettons à sodium_crypto_secretbox() avec notre $key et un $nonce. Le nonce est généré en utilisant random_bytes(), car le même nonce ne doit jamais être réutilisé.

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

Cela pose un problème car nous avons besoin du nonce pour déchiffrer la valeur plus tard. Heureusement, les nonces n’ont pas besoin d’être gardés secrets afin que nous puissions les ajouter à notre $ciphertext puis base64_encode() la valeur avant de l’enregistrer dans la base de données.

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

Lorsqu’il s’agit de déchiffrer la valeur, nous faisons le contraire.

$decoded = base64_decode($encoded);

Parce que nous connaissons la longueur de nonce, nous pouvons l’extraire en utilisant mb_substr() avant de déchiffrer la valeur.

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

C’est tout ce qu’il y a à cryptage à clé secrète en PHP, grâce au Sodium!

Cryptage d’enveloppe

Bien que l’approche décrite ci-dessus soit certainement un pas dans la bonne direction, elle laisse toujours nos données vulnérables si la clé secrète est compromise. Considérons un utilisateur malveillant qui accède au serveur qui héberge notre application. Dans ce scénario, il y a de fortes chances que l’attaquant puisse découvrir notre clé secrète que nous avons utilisée pour crypter les données. Cela laisse nos données complètement exposées.

La solution simple est de ne pas stocker notre clé secrète au même endroit que les données cryptées, mais cela pose un problème. Comment chiffrons-nous et déchiffrons-nous à la demande? Entrez le service de Gestion des Clés Google Cloud (Cloud KMS).

Cloud KMS est un service fourni par Google pour l’hébergement sécurisé de clés cryptographiques. Il offre une variété de fonctionnalités utiles autour du stockage des clés, y compris la rotation automatique des clés et la destruction retardée des clés. Cependant, dans cet exemple, nous sommes principalement concernés par le stockage de notre clé secrète loin de nos données.

Pour rendre les choses plus sûres, nous allons utiliser une technique connue sous le nom de cryptage d’enveloppe. Essentiellement, le chiffrement d’enveloppe consiste à chiffrer les clés avec une autre clé. Nous le faisons pour deux raisons:

  1. Cloud KMS a une limite de taille de 64 Ko pour les données qui peuvent être chiffrées et déchiffrées. Par conséquent, il peut ne pas être possible d’envoyer toutes les données d’un seul coup.
  2. Plus important encore, nous ne voulons pas envoyer nos données sensibles en texte brut à un tiers, quelle que soit leur fiabilité.

Au lieu d’envoyer nos données en clair au Cloud KMS, nous allons générer une clé de chiffrement unique chaque fois que nous écrivons des données sensibles dans la base de données. Cette clé est connue sous le nom de clé de cryptage des données (DEK), qui sera utilisée pour crypter nos données. Le DEK est ensuite envoyé au Cloud KMS pour être chiffré, qui renvoie une clé de chiffrement (appelée KEK). Enfin, le KEK est stocké côte à côte dans la base de données à côté des données cryptées et le DEK est détruit. Le processus ressemble à ceci:

  1. Générer une clé de chiffrement unique (DEK)
  2. Chiffrer les données à l’aide d’un chiffrement à clé secrète
  3. Envoyer la clé de chiffrement unique (DEK) au Cloud KMS pour le chiffrement, qui renvoie le KEK
  4. Stocker les données chiffrées et la clé chiffrée (KEK) côte à côte
  5. Détruire la clé générée (DEK)

Lors du déchiffrement des données, le processus est inversé:

  1. Récupérez les données chiffrées et la clé chiffrée (KEK) de la base de données
  2. Envoyez le KEK au Cloud KMS pour le décryptage, qui renvoie le DEK
  3. Utilisez le DEK pour décrypter nos données chiffrées
  4. Détruisez le DEK

Avec cela à l’esprit, j’ai créé un outil très simple classe d’assistance pour effectuer le cryptage d’enveloppe. Je ne vais pas passer en revue les étapes requises dans la console Google Cloud, car les guides de démarrage rapide et d’authentification décrivent tout ce dont vous avez besoin pour commencer. Par souci de concision, il n’y a pas de gestion des erreurs, etc. dans cet exemple.

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

Vous remarquerez que les méthodes de chiffrement et de déchiffrement réelles sont presque identiques à l’implémentation de clé secrète présentée ci-dessus. La différence est cependant que nous utilisons maintenant plusieurs clés de chiffrement. Voyons la classe helper en action. Vous devrez fournir votre $projectId, $locationId, $keyRingId et $cryptoKeyId qui sont disponibles depuis la console Google Cloud.

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

Si un attaquant compromettait notre système, pourrait-il également obtenir nos informations d’identification API pour Cloud KMS ? Selon la méthode d’authentification utilisée, alors oui, cela peut être une possibilité. Si tel est le cas, vous vous demandez peut-être en quoi le cryptage d’enveloppe est plus sûr que le cryptage à clé secrète ordinaire? La principale différence (jeu de mots) est que l’accès à l’API peut être révoqué, contrecarrant ainsi un attaquant qui s’est constitué avec vos données sensibles. C’est l’équivalent de changer vos serrures si quelqu’un vole la clé de votre maison. Avec un cryptage à clé secrète régulier où une seule clé locale est compromise, vous n’avez pas ce luxe. L’attaquant a tout le temps du monde pour décrypter vos données sensibles.

Conclusion

La sécurité et le cryptage des données sont de vastes sujets et je n’ai couvert qu’une poignée de façons de protéger les données sensibles en utilisant PHP. (Nous avons déjà écrit sur la protection de ce type de données dans votre environnement local)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Previous post Mise à jour: La police arrête le tireur présumé à Shady Oaks – Albert Lea Tribune / Albert Lea Tribune
Next post Comment enlever les taches d’huile et de graisse sur les vêtements