web-dev-qa-db-fra.com

Le cryptage bidirectionnel le plus simple avec PHP

Quel est le moyen le plus simple de procéder à un cryptage bidirectionnel commun à PHP installations?

Je dois pouvoir chiffrer les données avec une clé de chaîne et utiliser la même clé pour déchiffrer de l'autre côté.

La sécurité n’est pas aussi préoccupante que la portabilité du code, donc je voudrais pouvoir garder les choses aussi simples que possible. Actuellement, j'utilise une implémentation RC4, mais si je trouve un support natif, je pense pouvoir économiser beaucoup de code inutile.

206
user1206970

édité:

Vous devriez vraiment utiliser openssl_encrypt () & openssl_decrypt ()

Comme Scott dit, Mcrypt n'est pas une bonne idée car il n'a pas été mis à jour depuis 2007.

Il existe même une RFC pour supprimer Mcrypt de PHP - https://wiki.php.net/rfc/mcrypt-viking-funeral

180
472084

Important : Sauf si vous avez un très cas d'utilisation particulier, ne chiffrez pas les mots de passe , utilisez un algorithme de hachage de mot de passe à la place. Lorsque quelqu'un dit qu'il chiffre ses mots de passe dans une application côté serveur, il est soit mal informé, soit décrit une conception de système dangereuse. Stockage sécurisé des mots de passe est un problème totalement distinct du cryptage.

Être informé. Concevoir des systèmes sécurisés.

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 vous-même un module de cryptographie, je vous recommande d'utiliser ne bibliothèque existante fournissant un cryptage authentifié . La bibliothèque que j'ai liée s'appuie uniquement sur ce que PHP fournit et fait l'objet de vérifications périodiques par une poignée de chercheurs en sécurité. (Moi-même inclus.)

Si vos objectifs de portabilité n'empêchent pas de requérir des extensions PECL, libsodium est hautement ​​recommandé par rapport à tout ou je peux écrire en PHP.

Mise à jour (2016-06-12): Vous pouvez maintenant utiliser sodium_compat et utiliser les mêmes offres crypto libsodium sans installer les extensions PECL. .

Si vous voulez vous essayer à la cryptographie, lisez la suite.


Tout d’abord, prenez le temps d’apprendre les dangers du cryptage non authentifié et principe de la cryptographie .

  • Les données cryptées peuvent toujours être altérées par un utilisateur malveillant.
  • L'authentification des données cryptées empêche toute manipulation frauduleuse.
  • L'authentification des données non chiffrées n'empêche pas la falsification.

Cryptage et décryptage

Le chiffrement dans PHP est en réalité simple (nous allons utiliser openssl_encrypt() et openssl_decrypt() une fois que vous avez pris des décisions sur la manière de chiffrer vos informations. Consultez openssl_get_cipher_methods() pour obtenir 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 croire que le taille de la clé AES est un problème important à prendre en compte (plus gros est probablement pas mieux, en raison d'un mauvais ordonnancement des clés dans le 256- mode bit).

Note: Nous n'utilisons pas mcrypt parce que c'est abandonware et a bogues non corrigés = cela pourrait affecter la sécurité. Pour ces raisons, j’encourage d’autres PHP développeurs à l’éviter également.

Wrapper Simple Encryption/Decryption utilisant 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 chiffrement simple ci-dessus n'est toujours pas sûre. Nous devons authentifier les textes chiffrés et les vérifier avant de les déchiffrer .

Remarque : Par défaut, UnsafeCrypto::encrypt() renverra 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/f5K9

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émonstrations : binaire brut , codé en base64


Si quelqu'un souhaite utiliser cette bibliothèque SaferCrypto dans un environnement de production, ou votre propre implémentation des mêmes concepts, je vous recommande fortement de contacter vos cryptographes résidents pour un deuxième avis avant de le faire. Ils pourront vous raconter des erreurs que je ne connais même pas.

Vous ferez bien mieux d'utiliser ne bibliothèque de cryptographie réputée .

207

Utilisez mcrypt_encrypt() et mcrypt_decrypt() avec les paramètres correspondants. Vraiment facile et simple, vous utilisez un logiciel de chiffrement éprouvé.

EDIT

Cinq ans et quatre mois après cette réponse, l'extension mcrypt est maintenant en cours de dépréciation et de suppression éventuelle de PHP.

22
Eugen Rieck

PHP 7.2 s'est complètement éloigné de Mcrypt et le chiffrement est maintenant basé sur la bibliothèque maintenable Libsodium.

Tous vos besoins de chiffrement peuvent être résolus de manière fondamentale via la bibliothèque Libsodium.

// On Alice's computer:
$msg = 'This comes from Alice.';
$signed_msg = sodium_crypto_sign($msg, $secret_sign_key);


// On Bob's computer:
$original_msg = sodium_crypto_sign_open($signed_msg, $alice_sign_publickey);
if ($original_msg === false) {
    throw new Exception('Invalid signature');
} else {
    echo $original_msg; // Displays "This comes from Alice."
}

Documentation Libsodium: https://github.com/paragonie/pecl-libsodium-doc

4
Hemerson Varela

Voici une implémentation simple mais suffisamment sécurisée:

  • Cryptage AES-256 en mode CBC
  • PBKDF2 pour créer une clé de chiffrement à partir d'un mot de passe en texte brut
  • HMAC pour authentifier le message chiffré.

Le code et les exemples sont ici: https://stackoverflow.com/a/19445173/138716

2
Eugene Fidelin