176 votes

Cryptage bidirectionnel : J'ai besoin de stocker des mots de passe qui peuvent être récupérés.

Je suis en train de créer une application qui stockera des mots de passe, que l'utilisateur pourra récupérer et voir. Les mots de passe sont destinés à un dispositif matériel, de sorte que la vérification par rapport aux hachages est hors de question.

Ce que j'ai besoin de savoir c'est :

  1. Comment crypter et décrypter un mot de passe en PHP ?

  2. Quel est l'algorithme le plus sûr pour crypter les mots de passe ?

  3. Où dois-je stocker la clé privée ?

  4. Au lieu de stocker la clé privée, est-ce une bonne idée de demander aux utilisateurs d'entrer la clé privée chaque fois qu'ils ont besoin de déchiffrer un mot de passe ? (On peut faire confiance aux utilisateurs de cette application)

  5. De quelle manière le mot de passe peut-il être volé et décrypté ? De quoi dois-je être conscient ?

220voto

ircmaxell Points 74865

Personnellement, j'utiliserais mcrypt comme d'autres l'ont fait. Mais il y a beaucoup plus à noter...

  1. Comment crypter et décrypter un mot de passe en PHP ?

    Voir ci-dessous pour une classe forte qui s'occupe de tout pour vous :

  2. Quel est l'algorithme le plus sûr pour crypter les mots de passe ?

    le plus sûr ? n'importe lequel d'entre eux. La méthode la plus sûre pour crypter est de se protéger contre les failles de divulgation d'informations (XSS, inclusion à distance, etc.). Si l'information est divulguée, l'attaquant peut éventuellement craquer le cryptage (aucun cryptage n'est 100% irréversible sans la clé - Comme @NullUserException le souligne, ce n'est pas tout à fait vrai. Il existe des schémas de chiffrement impossibles à craquer, comme par exemple OneTimePad ).

  3. Où dois-je stocker la clé privée ?

    Ce que je ferais, c'est utiliser 3 clés. L'une est fournie par l'utilisateur, l'autre par l'application et la dernière par l'utilisateur (comme un sel). La clé spécifique à l'application peut être stockée n'importe où (dans un fichier de configuration en dehors du web-Root, dans une variable environnementale, etc). La clé spécifique à l'utilisateur sera stockée dans une colonne de la base de données à côté du mot de passe crypté. Le mot de passe fourni par l'utilisateur ne sera pas stocké. Ensuite, vous feriez quelque chose comme ceci :

    $key = $userKey . $serverKey . $userSuppliedKey;

    L'avantage est que deux clés quelconques peuvent être compromises sans que les données ne soient compromises. S'il y a une attaque par injection SQL, ils peuvent obtenir la clé $userKey mais pas les deux autres. S'il y a un exploit sur le serveur local, ils peuvent obtenir $userKey y $serverKey mais pas le troisième $userSuppliedKey . S'ils vont frapper l'utilisateur avec une clé à molette, ils peuvent obtenir la $userSuppliedKey mais pas les deux autres (mais là encore, si l'utilisateur est frappé avec une clé à molette, il est de toute façon trop tard).

  4. Au lieu de stocker la clé privée, est-ce une bonne idée de demander aux utilisateurs de saisir la clé privée chaque fois qu'ils ont besoin de déchiffrer un mot de passe ? (On peut faire confiance aux utilisateurs de cette application)

    Absolument. En fait, c'est la seule façon dont je le ferais. Sinon, il faudrait stocker une version non chiffrée dans un format de stockage durable (mémoire partagée telle que APC ou memcached, ou dans un fichier de session). Vous vous exposez ainsi à des risques supplémentaires. Ne stockez jamais la version non chiffrée du mot de passe dans quoi que ce soit d'autre qu'une variable locale.

  5. De quelles manières le mot de passe peut-il être volé et décrypté ? De quoi dois-je être conscient ?

    Toute forme de compromission de vos systèmes leur permettra de voir les données cryptées. S'ils peuvent injecter du code ou accéder à votre système de fichiers, ils peuvent visualiser des données décryptées (puisqu'ils peuvent modifier les fichiers qui décryptent les données). Toute forme d'attaque Replay ou MITM leur donnera également un accès complet aux clés concernées. Le fait de renifler le trafic HTTP brut leur donnera également les clés.

    Utilisez le protocole SSL pour tout le trafic. Et assurez-vous que rien sur le serveur ne présente de vulnérabilité (CSRF, XSS, injection SQL, escalade de privilèges, exécution de code à distance, etc.)

Edita: Voici une implémentation en classe PHP d'une méthode de cryptage forte :

/**
 * A class to handle secure encryption and decryption of arbitrary data
 *
 * Note that this is not just straight encryption.  It also has a few other
 *  features in it to make the encrypted data far more secure.  Note that any
 *  other implementations used to decrypt data will have to do the same exact
 *  operations.  
 *
 * Security Benefits:
 *
 * - Uses Key stretching
 * - Hides the Initialization Vector
 * - Does HMAC verification of source data
 *
 */
class Encryption {

    /**
     * @var string $cipher The mcrypt cipher to use for this instance
     */
    protected $cipher = '';

    /**
     * @var int $mode The mcrypt cipher mode to use
     */
    protected $mode = '';

    /**
     * @var int $rounds The number of rounds to feed into PBKDF2 for key generation
     */
    protected $rounds = 100;

    /**
     * Constructor!
     *
     * @param string $cipher The MCRYPT_* cypher to use for this instance
     * @param int    $mode   The MCRYPT_MODE_* mode to use for this instance
     * @param int    $rounds The number of PBKDF2 rounds to do on the key
     */
    public function __construct($cipher, $mode, $rounds = 100) {
        $this->cipher = $cipher;
        $this->mode = $mode;
        $this->rounds = (int) $rounds;
    }

    /**
     * Decrypt the data with the provided key
     *
     * @param string $data The encrypted datat to decrypt
     * @param string $key  The key to use for decryption
     * 
     * @returns string|false The returned string if decryption is successful
     *                           false if it is not
     */
    public function decrypt($data, $key) {
        $salt = substr($data, 0, 128);
        $enc = substr($data, 128, -64);
        $mac = substr($data, -64);

        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
             return false;
        }

        $dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);

        $data = $this->unpad($dec);

        return $data;
    }

    /**
     * Encrypt the supplied data using the supplied key
     * 
     * @param string $data The data to encrypt
     * @param string $key  The key to encrypt with
     *
     * @returns string The encrypted data
     */
    public function encrypt($data, $key) {
        $salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        $data = $this->pad($data);

        $enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);

        $mac = hash_hmac('sha512', $enc, $macKey, true);
        return $salt . $enc . $mac;
    }

    /**
     * Generates a set of keys given a random salt and a master key
     *
     * @param string $salt A random string to change the keys each encryption
     * @param string $key  The supplied key to encrypt with
     *
     * @returns array An array of keys (a cipher key, a mac key, and a IV)
     */
    protected function getKeys($salt, $key) {
        $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
        $keySize = mcrypt_get_key_size($this->cipher, $this->mode);
        $length = 2 * $keySize + $ivSize;

        $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);

        $cipherKey = substr($key, 0, $keySize);
        $macKey = substr($key, $keySize, $keySize);
        $iv = substr($key, 2 * $keySize);
        return array($cipherKey, $macKey, $iv);
    }

    /**
     * Stretch the key using the PBKDF2 algorithm
     *
     * @see http://en.wikipedia.org/wiki/PBKDF2
     *
     * @param string $algo   The algorithm to use
     * @param string $key    The key to stretch
     * @param string $salt   A random salt
     * @param int    $rounds The number of rounds to derive
     * @param int    $length The length of the output key
     *
     * @returns string The derived key.
     */
    protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
        $size   = strlen(hash($algo, '', true));
        $len    = ceil($length / $size);
        $result = '';
        for ($i = 1; $i <= $len; $i++) {
            $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
            $res = $tmp;
            for ($j = 1; $j < $rounds; $j++) {
                 $tmp  = hash_hmac($algo, $tmp, $key, true);
                 $res ^= $tmp;
            }
            $result .= $res;
        }
        return substr($result, 0, $length);
    }

    protected function pad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $padAmount = $length - strlen($data) % $length;
        if ($padAmount == 0) {
            $padAmount = $length;
        }
        return $data . str_repeat(chr($padAmount), $padAmount);
    }

    protected function unpad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $last = ord($data[strlen($data) - 1]);
        if ($last > $length) return false;
        if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
            return false;
        }
        return substr($data, 0, -1 * $last);
    }
}

Notez que j'utilise une fonction ajoutée en PHP 5.6 : hash_equals . Si vous êtes sous une version inférieure à la 5.6, vous pouvez utiliser cette fonction de substitution qui implémente une comparaison sécurisée dans le temps en utilisant double vérification HMAC :

function hash_equals($a, $b) {
    $key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
    return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}

Utilisation :

$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);

Ensuite, pour décrypter :

$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);

Notez que j'ai utilisé $e2 la deuxième fois pour vous montrer que différentes instances décrypteront toujours correctement les données.

Maintenant, comment cela fonctionne/pourquoi l'utiliser plutôt qu'une autre solution :

  1. Clés

    • Les clés ne sont pas directement utilisées. Au lieu de cela, la clé est étirée par une dérivation standard PBKDF2.

    • La clé utilisée pour le cryptage est unique pour chaque bloc de texte crypté. La clé fournie devient donc une "clé maîtresse". Cette classe assure donc la rotation des clés pour les clés de chiffrement et d'authentification.

    • NOTE IMPORTANTE le $rounds est configuré pour des clés aléatoires réelles d'une force suffisante (128 bits d'aléatoire cryptographiquement sécurisé au minimum). Si vous comptez utiliser un mot de passe ou une clé non aléatoire (ou moins aléatoire que 128 bits d'aléa CS), il faut doit augmenter ce paramètre. Je suggérerais un minimum de 10000 pour les mots de passe (plus vous pouvez vous permettre, mieux c'est, mais cela ajoutera au temps d'exécution)...

  2. Intégrité des données

    • La version mise à jour utilise ENCRYPT-THEN-MAC, qui est une bien meilleure méthode pour garantir l'authenticité des données cryptées.
  3. Le cryptage :

    • Il utilise mcrypt pour effectuer le cryptage. Je vous suggère d'utiliser soit MCRYPT_BLOWFISH ou MCRYPT_RIJNDAEL_128 et MCRYPT_MODE_CBC pour le mode. Il est suffisamment fort, tout en étant assez rapide (un cycle de cryptage et de décryptage prend environ 1/2 seconde sur ma machine).

Maintenant, en ce qui concerne le point 3 de la première liste, cela vous donnerait une fonction comme celle-ci :

function makeKey($userKey, $serverKey, $userSuppliedKey) {
    $key = hash_hmac('sha512', $userKey, $serverKey);
    $key = hash_hmac('sha512', $key, $userSuppliedKey);
    return $key;
}

Vous pourriez l'étirer dans le makeKey() mais comme elle sera étirée plus tard, il n'y a pas vraiment d'intérêt à le faire.

En ce qui concerne la taille de stockage, cela dépend du texte brut. Blowfish utilise une taille de bloc de 8 octets, donc vous aurez.. :

  • 16 octets pour le sel
  • 64 octets pour le hmac
  • longueur de données
  • Remplissage pour que la longueur des données % 8 == 0

Ainsi, pour une source de données de 16 caractères, il y aura 16 caractères de données à crypter. Cela signifie donc que la taille réelle des données cryptées est de 16 octets en raison du remplissage. Ajoutez ensuite les 16 octets pour le sel et les 64 octets pour le hmac et la taille totale stockée est de 96 octets. Il y a donc, au mieux, une surcharge de 80 caractères, et au pire, une surcharge de 87 caractères...

J'espère que cela vous aidera...

Note : 12/11/12 : Je viens de mettre à jour cette classe avec une méthode de cryptage BEAUCOUP meilleure, en utilisant de meilleures clés dérivées, et en corrigeant la génération de MAC...

15voto

Ivan Points 2346

Comment crypter et décrypter un mot de passe en PHP ? En mettant en œuvre l'un des nombreux algorithmes de cryptage. (ou en utilisant une des nombreuses bibliothèques)

Quel est l'algorithme le plus sûr pour crypter les mots de passe ? Il existe des tonnes d'algorithmes différents, dont aucun n'est sûr à 100 %. Mais beaucoup d'entre eux sont suffisamment sûrs pour le commerce et même pour les besoins militaires.

Où dois-je stocker la clé privée ? Si vous avez décidé de mettre en œuvre un algorithme de cryptographie à clé publique (par exemple RSA), vous ne stockez pas la clé privée. L'utilisateur a la clé privée. Votre système a la clé publique qui peut être stockée où vous le souhaitez.

Au lieu de stocker la clé privée, est-ce une bonne idée de demander aux utilisateurs d'entrer la clé privée chaque fois qu'ils ont besoin de déchiffrer un mot de passe ? (On peut faire confiance aux utilisateurs de cette application) Si votre utilisateur peut se souvenir de nombres premiers ridiculement longs, alors oui, pourquoi pas. Mais en règle générale, vous devrez mettre au point un système qui permettra à l'utilisateur de stocker sa clé quelque part.

De quelles manières le mot de passe peut-il être volé et décrypté ? De quoi dois-je être conscient ? Cela dépend de l'algorithme utilisé. Cependant, il faut toujours s'assurer de ne pas envoyer le mot de passe en clair à ou depuis l'utilisateur. Il faut soit le crypter/décrypter du côté client, soit utiliser https (ou tout autre moyen cryptographique pour sécuriser la connexion entre le serveur et le client).

Toutefois, si tout ce dont vous avez besoin est de stocker des mots de passe de manière cryptée, je vous suggère d'utiliser un simple chiffrement XOR. Le principal problème de cet algorithme est qu'il peut être facilement cassé par une analyse de fréquence. Cependant, comme les mots de passe ne sont généralement pas constitués de longs paragraphes de texte anglais, je ne pense pas que vous deviez vous en inquiéter. Le deuxième problème du chiffrement XOR est que si vous avez un message à la fois crypté et décrypté, vous pouvez facilement trouver le mot de passe avec lequel il a été crypté. Encore une fois, ce n'est pas un gros problème dans votre cas car cela ne concerne que l'utilisateur qui a déjà été compromis par d'autres moyens.

13voto

Jon Rhoades Points 313
  1. La fonction PHP que vous recherchez est Mcrypt ( http://www.php.net/manual/en/intro.mcrypt.php ).

L'exemple du manuel est légèrement modifié pour cet exemple) :

<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$pass = "PasswordHere";
echo strlen($pass) . "\n";

$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $pass, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
?>

Vous utiliseriez mcrypt_decrypt pour décrypter votre mot de passe.

  1. Le meilleur algorithme est plutôt subjective - demandez à 5 personnes, obtenez 5 réponses. Personnellement, si la valeur par défaut (Blowfish) n'est pas suffisante pour vous, vous avez probablement de plus gros problèmes !

  2. Étant donné qu'il est nécessaire à PHP pour crypter, je ne suis pas sûr que vous puissiez le cacher n'importe où - les commentaires à ce sujet sont les bienvenus. Les meilleures pratiques de codage PHP s'appliquent bien sûr !

  3. Étant donné que la clé de cryptage se trouvera de toute façon dans votre code, je ne sais pas ce que vous gagnerez, à condition que le reste de votre application soit sécurisé.

  4. Évidemment, si le mot de passe crypté et la clé de cryptage sont volés, la partie est terminée.

Je mettrais un cavalier sur ma réponse - je ne suis pas un expert en crypto PHP, mais, je pense que ce que j'ai répondu est une pratique standard - je suis ouvert aux commentaires des autres.

6voto

Bradley Points 651

Beaucoup d'utilisateurs ont suggéré d'utiliser mcrypt... ce qui est correct, mais j'aime aller un peu plus loin pour faciliter le stockage et le transfert (car parfois les valeurs cryptées peuvent rendre difficile leur envoi en utilisant d'autres technologies comme curl, ou json).

Une fois que vous avez réussi à crypter à l'aide de mcrypt, passez-le dans base64_encode, puis convertissez-le en code hex. Une fois en code hexadécimal, il est facile de le transférer de diverses manières.

$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $unencrypted);
$encrypted = $ua."||||".$iv;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encrypted = base64_encode($encrypted);
$encrypted = array_shift(unpack('H*', $encrypted));

Et de l'autre côté :

$encrypted = pack('H*', $encrypted);
$encrypted = base64_decode($encrypted);
list($encrypted,$iv) = explode("||||",$encrypted,2);
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$unencrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);

5voto

Long Ears Points 3081

Je ne suggérerais le cryptage par clé publique que si vous souhaitez pouvoir définir le mot de passe d'un utilisateur sans son intervention (cela peut être pratique pour les réinitialisations et les mots de passe partagés).

Clé publique

  1. Le site OpenSSL l'extension, notamment openssl_public_encrypt y openssl_private_decrypt
  2. Il s'agirait d'un simple RSA, en supposant que vos mots de passe entrent dans la taille de la clé - rembourrage, sinon vous avez besoin d'une couche symétrique.
  3. Stockez les deux clés pour chaque utilisateur, la phrase de passe de la clé privée est le mot de passe de l'application.

Symétrique

  1. Le site Mcrypt extension
  2. AES-256 est probablement une valeur sûre, mais cela pourrait être une question de SO en soi.
  3. Vous ne le faites pas - ce serait le mot de passe de leur demande.

Les deux sites

4 . Oui, les utilisateurs devraient entrer leur mot de passe d'application à chaque fois, mais le stockage de ce mot de passe dans la session soulèverait d'autres problèmes.

5 .

  • Si quelqu'un vole les données de l'application, elle est aussi sûre que le chiffrement symétrique (pour le schéma à clé publique, elle est utilisée pour protéger la clé privée avec la phrase de passe).
  • Votre application doit impérativement être accessible uniquement via SSL, en utilisant de préférence des certificats clients.
  • Envisagez d'ajouter un deuxième facteur d'authentification qui ne serait utilisé qu'une fois par session, comme un jeton envoyé par SMS.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X