Avant de faire quoi que ce soit d'autre, cherchez à comprendre la différence entre le cryptage et l' authentification , et pourquoi vous voulez probablement un cryptage authentifié plutôt qu'un simple cryptage .
Pour implémenter le cryptage authentifié, vous souhaitez crypter puis MAC. L'ordre de chiffrement et d'authentification est très important! L'une des réponses existantes à cette question a fait cette erreur; comme le font de nombreuses bibliothèques de cryptographie écrites en PHP.
Vous devez éviter d'implémenter votre propre cryptographie et utiliser à la place une bibliothèque sécurisée écrite et révisée par des experts en cryptographie.
Mise à jour: PHP 7.2 fournit désormais libsodium ! Pour une meilleure sécurité, mettez à jour vos systèmes pour utiliser PHP 7.2 ou supérieur et suivez uniquement les conseils de libsodium dans cette réponse.
Utilisez libsodium si vous avez un accès PECL (ou sodium_compat si vous voulez libsodium sans PECL); sinon ...
Utilisez le cryptage de désamorçage / php ; ne lancez pas votre propre cryptographie!
Les deux bibliothèques liées ci-dessus facilitent et simplifient l'implémentation du chiffrement authentifié dans vos propres bibliothèques.
Si vous souhaitez toujours écrire et déployer votre propre bibliothèque de cryptographie, contrairement à la sagesse conventionnelle de tous les experts en cryptographie sur Internet, voici les étapes à suivre.
Chiffrement:
- Chiffrez en utilisant AES en mode CTR. Vous pouvez également utiliser GCM (ce qui supprime le besoin d'un MAC séparé). De plus, ChaCha20 et Salsa20 (fournis par libsodium ) sont des chiffrements de flux et n'ont pas besoin de modes spéciaux.
- Sauf si vous avez choisi GCM ci-dessus, vous devez authentifier le texte chiffré avec HMAC-SHA-256 (ou, pour les chiffrements de flux, Poly1305 - la plupart des API libsodium le font pour vous). Le MAC devrait couvrir l'IV ainsi que le texte chiffré!
Décryptage:
- Sauf si Poly1305 ou GCM est utilisé, recalculez le MAC du texte chiffré et comparez-le avec le MAC envoyé à l'aide
hash_equals()
. S'il échoue, abandonnez.
- Déchiffrez le message.
Autres considérations de conception:
- Ne compressez jamais rien. Le texte chiffré n'est pas compressible; la compression du texte en clair avant le chiffrement peut entraîner des fuites d'informations (par exemple CRIME et BREACH sur TLS).
- Assurez-vous d'utiliser
mb_strlen()
et d' mb_substr()
utiliser le '8bit'
mode de jeu de caractères pour éviter les mbstring.func_overload
problèmes.
- Les IV devraient être générés à l' aide d'un CSPRNG ; Si vous utilisez
mcrypt_create_iv()
, NE PAS UTILISERMCRYPT_RAND
!
- Sauf si vous utilisez une construction AEAD, TOUJOURS crypter puis MAC!
bin2hex()
, base64_encode()
Etc. peut entraîner une fuite d' informations sur vos clés de chiffrement via synchronisation du cache. Évitez-les si possible.
Même si vous suivez les conseils donnés ici, beaucoup de choses peuvent mal tourner avec la cryptographie. Demandez toujours à un expert en cryptographie d'examiner votre implémentation. Si vous n'avez pas la chance d'être des amis personnels avec un étudiant en cryptographie de votre université locale, vous pouvez toujours essayer le forum Cryptography Stack Exchange pour obtenir des conseils.
Si vous avez besoin d'une analyse professionnelle de votre implémentation, vous pouvez toujours embaucher une équipe réputée de consultants en sécurité pour revoir votre code de cryptographie PHP (divulgation: mon employeur).
Important: quand ne pas utiliser le chiffrement
Ne cryptez pas les mots de passe . Vous souhaitez les hacher à la place, en utilisant l'un de ces algorithmes de hachage de mot de passe:
N'utilisez jamais une fonction de hachage à usage général (MD5, SHA256) pour le stockage des mots de passe.
Ne cryptez pas les paramètres d'URL . Ce n'est pas le bon outil pour le travail.
Exemple de chiffrement de chaîne PHP avec Libsodium
Si vous êtes sur PHP <7.2 ou si vous n'avez pas installé libsodium, vous pouvez utiliser sodium_compat pour obtenir le même résultat (quoique plus lentement).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Puis pour le tester:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite - Libsodium simplifié
L'un des projets sur lesquels j'ai travaillé est une bibliothèque de cryptage appelée Halite , qui vise à rendre libsodium plus facile et plus intuitif.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Toute la cryptographie sous-jacente est gérée par libsodium.
Exemple avec désamorçage / cryptage php
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Remarque : Crypto::encrypt()
renvoie une sortie codée hexadécimale.
Gestion des clés de chiffrement
Si vous êtes tenté d'utiliser un "mot de passe", arrêtez tout de suite. Vous avez besoin d'une clé de cryptage aléatoire de 128 bits, et non d'un mot de passe mémorable humain.
Vous pouvez stocker une clé de chiffrement pour une utilisation à long terme comme ceci:
$storeMe = bin2hex($key);
Et, à la demande, vous pouvez le récupérer comme suit:
$key = hex2bin($storeMe);
Je recommande fortement de simplement stocker une clé générée de manière aléatoire pour une utilisation à long terme au lieu de tout type de mot de passe comme clé (ou pour dériver la clé).
Si vous utilisez la bibliothèque de Defuse:
"Mais je veux vraiment utiliser un mot de passe."
C'est une mauvaise idée, mais d'accord, voici comment le faire en toute sécurité.
Tout d'abord, générez une clé aléatoire et stockez-la dans une constante.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
Notez que vous ajoutez du travail supplémentaire et que vous pouvez simplement utiliser cette constante comme clé et vous épargner beaucoup de chagrin d'amour!
Utilisez ensuite PBKDF2 (comme cela) pour dériver une clé de chiffrement appropriée de votre mot de passe plutôt que de chiffrer directement avec votre mot de passe.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
N'utilisez pas seulement un mot de passe à 16 caractères. Votre clé de cryptage sera cassée de façon comique.