Cryptage bidirectionnel le plus simple en utilisant PHP



1 Answers

Important : à moins d'avoir 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 qu'il crypte ses mots de passe dans une application côté serveur, il est soit mal informé, soit décrivant une conception de système dangereuse. Stocker des mots de passe en toute sécurité est un problème totalement distinct du chiffrement.

Être informé. Concevoir des systèmes sûrs.

Chiffrement de données portable en PHP

Si vous utilisez PHP 5.4 ou plus récent et que vous ne voulez pas écrire vous-même un module de cryptographie, je vous recommande d'utiliser une bibliothèque existante qui fournit un cryptage authentifié . La bibliothèque que je lie dépend uniquement de ce que PHP fournit et fait l'objet d'un examen périodique par une poignée de chercheurs en sécurité. (Moi-même inclus.)

Si vos objectifs de portabilité n'empêchent pas d'exiger des extensions PECL, libsodium est fortement recommandé sur 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 crypto libsodium sans installer d'extensions PECL.

Si vous voulez vous essayer à l'ingénierie cryptographique, lisez la suite.

Tout d'abord, vous devriez prendre le temps d'apprendre les dangers du cryptage non authentifié et le principe du Doom cryptographique .

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

Chiffrement et déchiffrement

Le cryptage en PHP est en fait simple (nous allons utiliser openssl_encrypt() et openssl_decrypt() une fois que vous avez pris des décisions sur la façon de crypter vos informations.Veuillez consulter openssl_get_cipher_methods() pour une liste des méthodes supportées sur votre système. le 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 la taille de la clé AES est un problème important à prendre en compte (plus gros n'est probablement pas meilleur, en raison d'une mauvaise programmation des clés en mode 256 bits).

Remarque: Nous n'utilisons pas mcrypt car il s'agit d' abandonware et de bogues non corrigés susceptibles d'affecter la sécurité. Pour ces raisons, j'encourage d'autres développeurs PHP à l'éviter aussi.

Encryption / Decryption Simple Wrapper 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 à utiliser. Nous devons authentifier les textes chiffrés et les vérifier avant de déchiffrer .

Note : Par défaut, UnsafeCrypto::encrypt() retournera une chaîne binaire brute. Appelez-le comme ceci si vous avez besoin de le stocker dans un format binaire sécurisé (encodé 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émonstration : http://3v4l.org/f5K93

Un 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);

Demos : binaire brut , base64-encoded

Si quelqu'un souhaite utiliser cette bibliothèque SaferCrypto 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 second avis avant de le faire. Ils pourront vous parler d'erreurs dont je ne suis peut-être même pas conscient.

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

Question

Quelle est la manière la plus simple de faire un chiffrement bidirectionnel dans les installations PHP courantes?

Je dois être capable de crypter des données avec une clé de chaîne, et utiliser la même clé pour décrypter à l'autre extrémité.

La sécurité n'est pas aussi importante que la portabilité du code, alors j'aimerais pouvoir garder les choses aussi simples que possible. Actuellement, j'utilise une implémentation RC4, mais si je peux trouver quelque chose de nativement supporté, je pense que je peux économiser beaucoup de code inutile.




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

  • Cryptage AES-256 en mode CBC
  • PBKDF2 pour créer une clé de cryptage avec un mot de passe en texte brut
  • HMAC pour authentifier le message chiffré.

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




Related