Important : à moins que vous n'ayez un cas d'utilisation très particulier, ne cryptez pas les mots de passe , utilisez plutôt un algorithme de hachage de mot de passe. Lorsque quelqu'un dit chiffrer ses mots de passe dans une application côté serveur, il n'est pas informé ou décrit une conception de système dangereuse. Le stockage sécurisé des mots de passe est un problème totalement distinct du cryptage.
Être informé. Concevez des systèmes sûrs.
Cryptage de données portable en PHP
Si vous utilisez PHP 5.4 ou une version plus récente et que vous ne voulez pas écrire un module de cryptographie vous-même, je vous recommande d'utiliser une bibliothèque existante qui fournit un cryptage authentifié . La bibliothèque que j'ai liée ne dépend que de ce que PHP fournit et est régulièrement revue par une poignée de chercheurs en sécurité. (Moi inclus.)
Si vos objectifs de portabilité n'empêchent pas d'exiger des extensions PECL, libsodium est fortement recommandé par rapport à tout ce que vous ou moi pouvons écrire en PHP.
Mise à jour (2016-06-12): Vous pouvez maintenant utiliser sodium_compat et utiliser les mêmes offres de crypto libsodium sans installer d'extensions PECL.
Si vous voulez vous essayer à l'ingénierie de la cryptographie, lisez la suite.
Tout d'abord, vous devriez prendre le temps d'apprendre les dangers du chiffrement non authentifié et le principe du destin cryptographique .
- Les données chiffrées peuvent toujours être falsifiées par un utilisateur malveillant.
- L'authentification des données chiffrées empêche toute falsification.
- L'authentification des données non cryptées n'empêche pas la falsification.
Cryptage et décryptage
Le cryptage en PHP est en fait simple (nous allons utiliser openssl_encrypt()
et openssl_decrypt()
une fois que vous aurez pris certaines décisions sur la façon de crypter vos informations. Consultez openssl_get_cipher_methods()
pour une liste des méthodes prises en charge sur votre système. Le meilleur choix est AES en mode CTR :
aes-128-ctr
aes-192-ctr
aes-256-ctr
Il n'y a actuellement aucune raison de penser que la taille de la clé AES est un problème important à craindre (une taille plus grande n'est probablement pas meilleure, en raison d'une mauvaise programmation des clés en mode 256 bits).
Remarque: nous ne l'utilisons pas mcrypt
car il s'agit d'un abandonware et de bogues non corrigés qui pourraient affecter la sécurité. Pour ces raisons, j'encourage les autres développeurs PHP à l'éviter également.
Wrapper de chiffrement / déchiffrement simple à l'aide d'OpenSSL
class UnsafeCrypto
{
const METHOD = 'aes-256-ctr';
/**
* Encrypts (but does not authenticate) a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = openssl_random_pseudo_bytes($nonceSize);
$ciphertext = openssl_encrypt(
$message,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
// Now let's pack the IV and the ciphertext together
// Naively, we can just concatenate
if ($encode) {
return base64_encode($nonce.$ciphertext);
}
return $nonce.$ciphertext;
}
/**
* Decrypts (but does not verify) a message
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string
*/
public static function decrypt($message, $key, $encoded = false)
{
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = mb_substr($message, 0, $nonceSize, '8bit');
$ciphertext = mb_substr($message, $nonceSize, null, '8bit');
$plaintext = openssl_decrypt(
$ciphertext,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
return $plaintext;
}
}
Exemple d'utilisation
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Démo : https://3v4l.org/jl7qR
La bibliothèque de cryptographie simple ci-dessus n'est toujours pas sûre à utiliser. Nous devons authentifier les textes chiffrés et les vérifier avant de déchiffrer .
Remarque : par défaut, UnsafeCrypto::encrypt()
renvoie une chaîne binaire brute. Appelez-le comme ceci si vous avez besoin de le stocker dans un format binaire (codé en base64):
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);
var_dump($encrypted, $decrypted);
Démo : http://3v4l.org/f5K93
Wrapper d'authentification simple
class SaferCrypto extends UnsafeCrypto
{
const HASH_ALGO = 'sha256';
/**
* Encrypts then MACs a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded string
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
list($encKey, $authKey) = self::splitKeys($key);
// Pass to UnsafeCrypto::encrypt
$ciphertext = parent::encrypt($message, $encKey);
// Calculate a MAC of the IV and ciphertext
$mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);
if ($encode) {
return base64_encode($mac.$ciphertext);
}
// Prepend MAC to the ciphertext and return to caller
return $mac.$ciphertext;
}
/**
* Decrypts a message (after verifying integrity)
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string (raw binary)
*/
public static function decrypt($message, $key, $encoded = false)
{
list($encKey, $authKey) = self::splitKeys($key);
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
// Hash Size -- in case HASH_ALGO is changed
$hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
$mac = mb_substr($message, 0, $hs, '8bit');
$ciphertext = mb_substr($message, $hs, null, '8bit');
$calculated = hash_hmac(
self::HASH_ALGO,
$ciphertext,
$authKey,
true
);
if (!self::hashEquals($mac, $calculated)) {
throw new Exception('Encryption failure');
}
// Pass to UnsafeCrypto::decrypt
$plaintext = parent::decrypt($ciphertext, $encKey);
return $plaintext;
}
/**
* Splits a key into two separate keys; one for encryption
* and the other for authenticaiton
*
* @param string $masterKey (raw binary)
* @return array (two raw binary strings)
*/
protected static function splitKeys($masterKey)
{
// You really want to implement HKDF here instead!
return [
hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
];
}
/**
* Compare two strings without leaking timing information
*
* @param string $a
* @param string $b
* @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
* @return boolean
*/
protected static function hashEquals($a, $b)
{
if (function_exists('hash_equals')) {
return hash_equals($a, $b);
}
$nonce = openssl_random_pseudo_bytes(32);
return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
}
}
Exemple d'utilisation
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Démos : binaire brut , encodé en base64
Si quelqu'un souhaite utiliser cette SaferCrypto
bibliothèque dans un environnement de production, ou votre propre implémentation des mêmes concepts, je recommande fortement de contacter vos cryptographes résidents pour un deuxième avis avant de le faire. Ils pourront vous parler d'erreurs que je ne connais peut-être même pas.
Vous serez beaucoup mieux en utilisant une bibliothèque de cryptographie réputée .