web-dev-qa-db-fra.com

Cryptage bidirectionnel: je dois stocker des mots de passe pouvant être récupérés

Je crée une application qui stockera les mots de passe, que l'utilisateur pourra récupérer et voir. Les mots de passe sont pour un périphérique matériel, donc la vérification par rapport aux hachages est hors de question.

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

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

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

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

  4. Au lieu de stocker la clé privée, est-il judicieux de demander aux utilisateurs de saisir la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application peuvent être de confiance)

  5. Comment le mot de passe peut-il être volé et déchiffré? De quoi ai-je besoin d'être conscient?

170
HyderA

Personnellement, j'utiliserais mcrypt comme les autres postés. Mais il y a beaucoup plus à noter ...

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

    Voir ci-dessous pour une classe forte qui prend soin de tout pour vous:

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

    le plus sûr? n'importe lequel d'entre eux. La méthode la plus sûre si vous allez chiffrer consiste à vous protéger contre les vulnérabilités de divulgation d'informations (XSS, inclusion à distance, etc.). S'il parvient à sortir, l'attaquant peut éventuellement déchiffrer le chiffrement (aucun chiffrement n'est irréversible à 100% sans la clé - comme le souligne @NullUserException, cela n'est pas tout à fait vrai. Il existe des schémas de chiffrement impossibles à déchiffrer tels que - OneTimePad ).

  3. Où est-ce que je stocke la clé privée?

    Ce que je voudrais faire est d'utiliser 3 clés. L'un est fourni par l'utilisateur, l'un est spécifique à l'application et l'autre est spécifique à 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 de la racine Web, dans une variable d'environnement, etc.). Le nom d'utilisateur spécifique serait stocké dans une colonne de la base de données à côté du mot de passe crypté. L'utilisateur fourni ne serait pas stocké. Ensuite, vous feriez quelque chose comme ça:

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

    L'avantage ici est que deux clés peuvent être compromises sans compromettre les données. En cas d'attaque par injection SQL, ils peuvent obtenir le $userKey, Mais pas les 2 autres. S'il existe un exploit sur un serveur local, ils peuvent obtenir $userKey Et $serverKey, Mais pas le troisième $userSuppliedKey. S'ils vont frapper l'utilisateur avec une clé, ils peuvent obtenir le $userSuppliedKey, Mais pas les 2 autres (mais encore une fois, si l'utilisateur est battu avec une clé, vous êtes de toute façon trop en retard).

  4. Au lieu de stocker la clé privée, est-il judicieux de demander aux utilisateurs de saisir la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application peuvent être de confiance)

    Absolument. En fait, c'est la seule façon dont je le ferais. Sinon, vous devrez 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). Cela vous expose à des compromis supplémentaires. Ne stockez jamais la version non chiffrée du mot de passe dans une variable autre qu'une variable locale.

  5. Comment le mot de passe peut-il être volé et déchiffré? De quoi ai-je besoin d'être conscient?

    Toute forme de compromission de vos systèmes leur permettra de visualiser les données cryptées. S'ils peuvent injecter du code ou accéder à votre système de fichiers, ils peuvent afficher les données déchiffrées (car ils peuvent éditer les fichiers qui déchiffrent les données). Toute forme d’attaque par Replay ou MITM leur donnera également un accès complet aux clés impliquées. Renifler le trafic HTTP brut leur donnera également les clés.

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

Edit: Voici une implémentation de classe PHP) d'une méthode de cryptage fort:

/**
 * 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 dans PHP 5.6: hash_equals . Si vous êtes sur une version inférieure à 5.6, vous pouvez utiliser ce substitut. fonction qui implémente une comparaison temporelle sûre fonction 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);
}

Usage:

$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échiffreraient toujours correctement les données.

Maintenant, comment ça marche/pourquoi l'utiliser avec une autre solution:

  1. Clés

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

    • La clé utilisée pour le cryptage est unique pour chaque bloc de texte crypté. La clé fournie devient alors une "clé principale". Cette classe fournit donc une rotation de clé pour les clés chiffrées et authentiques.

    • REMARQUE IMPORTANTE , le paramètre $rounds Est configuré pour des clés aléatoires véritables de force suffisante (128 bits au minimum de valeur aléatoire sécurisée de manière cryptographique). Si vous allez utiliser un mot de passe ou une clé non aléatoire (ou moins aléatoire que 128 bits de valeur aléatoire CS), vous devez augmenter ce paramètre. Je suggérerais un minimum de 10000 pour les mots de passe (plus vous en aurez les moyens, mieux ce sera, mais cela ajoutera du temps à l'exécution) ...

  2. Intégrité des données

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

    • Il utilise mcrypt pour effectuer le cryptage. Je suggérerais d'utiliser soit MCRYPT_BLOWFISH Ou MCRYPT_RIJNDAEL_128 Cyphers et MCRYPT_MODE_CBC Pour le mode. Il est assez puissant et reste assez rapide (un cycle de chiffrement et de déchiffrement 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 pouvez l'étirer dans la fonction makeKey(), mais comme elle sera étirée plus tard, il n'y a pas vraiment de raison de 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, vous aurez donc:

  • 16 octets pour le sel
  • 64 octets pour le hmac
  • longueur de données
  • Rembourrage 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 à chiffrer. Cela signifie donc que la taille réelle des données cryptées est de 16 octets en raison du remplissage. Ajoutez ensuite 16 octets pour le sel et 64 octets pour le hmac et la taille totale stockée est de 96 octets. Donc, il y a au mieux une surcharge de 80 caractères et au pire une surcharge de 87 caractères ...

J'espère que ça aide ...

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

209
ircmaxell

Comment chiffrer et déchiffrer un mot de passe en PHP? En implémentant l'un des nombreux algorithmes de chiffrement. (ou en utilisant l'une des nombreuses bibliothèques)

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

Où dois-je stocker la clé privée? Si vous avez décidé d'implémenter une clé publique - algorithme de cryptographie (par exemple, RSA), vous ne pas stocker la clé privée. l'utilisateur a la clé privée. votre système possède une clé publique qui peut être stockée où vous le souhaitez.

Au lieu de stocker la clé privée, est-il judicieux de demander aux utilisateurs de saisir la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application sont fiables) Eh bien, si votre utilisateur peut se souvenir de nombres premiers ridiculement longs, alors - oui, pourquoi pas. Mais en règle générale, vous devrez créer 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échiffré? De quoi dois-je avoir connaissance? Cela dépend de l'algorithme utilisé. Cependant, veillez toujours à ne pas envoyer de mot de passe non chiffré à l'utilisateur. Chiffrez/déchiffrez-le côté client ou utilisez https (ou utilisez un autre moyen cryptographique pour sécuriser la connexion entre le serveur et le client).

Cependant, si tout ce dont vous avez besoin est de stocker les mots de passe de manière cryptée, je vous suggère d'utiliser un simple XOR Cipher. Le principal problème de cet algorithme est qu'il pourrait être facilement cassé par 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 devriez vous en préoccuper. Le deuxième problème avec XOR Cipher est que si vous avez un message chiffré et sous forme décryptée, vous pouvez facilement trouver le mot de passe avec lequel il a été crypté.C encore une fois, ce n'est pas un gros problème dans votre cas car il ne concerne que l'utilisateur qui a déjà été compromis par d'autres moyens.

14
Ivan
  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 édité 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échiffrer votre mot de passe.

  1. Le meilleur algorithme est plutôt subjectif - demandez à 5 personnes, obtenez 5 réponses. Personnellement, si le défaut (Blowfish) ne vous suffit pas, vous avez probablement de plus gros problèmes!

  2. Étant donné que PHP en a besoin pour chiffrer - il n'est pas sûr que vous puissiez le cacher n'importe où - nous vous invitons à nous faire part de vos commentaires. Les meilleures pratiques de codage standard PHP s'appliquent bien sûr!

  3. Étant donné que la clé de cryptage sera dans votre code de toute façon, vous ne savez pas exactement 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 mot sur ma réponse - je ne suis pas un expert en crypto PHP, mais je pense que ce à quoi j'ai répondu est une pratique courante - je me réjouis des commentaires que d'autres pourraient avoir.

12
Jon Rhoades

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

Une fois que vous avez réussi à chiffrer avec mcrypt, exécutez-le à l'aide de base64_encode, puis convertissez-le en code hexadécimal. Une fois dans le code hexadécimal, il est facile de transférer de nombreuses façons.

$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);
6
Bradley

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

Clé publique

  1. L'extension OpenSSL , plus précisément openssl_public_encrypt et openssl_private_decrypt
  2. Ce serait un RSA direct en supposant que vos mots de passe tiendront dans la taille de la clé - padding, sinon vous aurez besoin d’un calque symétrique
  3. Stocker les deux clés pour chaque utilisateur, la phrase secrète de la clé privée est leur mot de passe d'application

Symétrique

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

Tous les deux

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

5.

  • Si quelqu'un vole les données de l'application, il est aussi sécurisé que le chiffrement symétrique (pour le schéma de clé publique, il est utilisé pour protéger la clé privée avec la phrase secrète.)
  • Votre application ne devrait définitivement être accessible que via SSL, de préférence à l'aide de 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.
5
Long Ears

Les mots de passe sont pour un périphérique matériel, donc la vérification contre les hachages est hors de question

Hein? Je ne comprends pas. Voulez-vous simplement dire que le mot de passe doit être récupérable?

Comme d'autres l'ont déjà dit, l'extension mcrypt permet d'accéder à de nombreuses fonctions cryptographiques. Cependant, vous invitez vos utilisateurs à mettre tous leurs œufs dans le même panier - une cible potentielle pour les attaquants - et si vous ne le savez même pas comment commencer à résoudre le problème, vous rendez un mauvais service à vos utilisateurs. Vous n'êtes pas en mesure de comprendre comment protéger les données.

La plupart des vulnérabilités en matière de sécurité proviennent non pas d'un algorithme défectueux ou peu sûr, mais de problèmes liés à la manière dont il est utilisé dans le code de l'application.

Cela dit, il est possible de créer un système raisonnablement sécurisé.

Vous ne devez envisager le cryptage asymétrique que si un utilisateur doit créer un message sécurisé lisible par un autre utilisateur (spécifique). La raison en est que ses calculs sont coûteux. Si vous souhaitez simplement fournir un référentiel permettant aux utilisateurs d'entrer et de récupérer leurs propres données, le cryptage symétrique convient.

Si, toutefois, vous stockez la clé de déchiffrement du message au même endroit que le message chiffré (ou le lieu où le message chiffré est stocké), le système n'est pas sécurisé. Utilisez le même jeton pour authentifier l'utilisateur que pour la clé de déchiffrement (ou, dans le cas d'un chiffrement asymétrique, utilisez le jeton comme phrase secrète de la clé privée). Etant donné que vous aurez besoin de stocker le jeton sur le serveur sur lequel le déchiffrement a lieu au moins temporairement, vous pouvez envisager d’utiliser un substrat de stockage de session non interrogeable, ou de transmettre le jeton directement à un démon associé à la session qui stockerait le fichier. jeton en mémoire et effectuer le déchiffrement des messages à la demande.

2
symcbean

J'ai essayé quelque chose comme ça mais s'il vous plaît, notez que je ne suis pas cryptographe, ni que je possède une connaissance approfondie de php ou de tout langage de programmation. C'est juste une idée. Mon idée est de stocker key dans un fichier ou database (ou de le saisir manuellement), ce qui (emplacement) est difficilement prévisible ( temps de décryptage) et chiffrer les informations sensibles.

$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH , MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_Rand);
$key = "evenifyouaccessmydatabaseyouwillneverfindmyemail";
$text = "[email protected]";
echo "Key : ".$key."<br/>";
echo "Text : ".$text . "<br/>";
echo "Md5 : ".md5($text). "<br/>";
echo "Sha1 : ".sha1($text). "<br/>";



$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH , $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Crypted Data : ".$crypttext."<br>";

$base64 = base64_encode($crypttext);
echo "Encoded Data : ".$base64."<br/>";
$decode =  base64_decode($base64);


$decryptdata = mcrypt_decrypt(MCRYPT_BLOWFISH , $key, $crypttext, MCRYPT_MODE_ECB, $iv);

echo "Decoded Data : ".ereg_replace("?", null ,  $decryptdata); 
//event if i add '?' to the sting to the text it works, I don't know why.

Veuillez noter qu'il ne s'agit que d'un concept. Toute amélioration de ce code serait très appréciable.

2
Santosh Linkha

Utilisez password_hash et password_verify

<?php
/**
 * In this case, we want to increase the default cost for BCRYPT to 12.
 * Note that we also switched to BCRYPT, which will always be 60 characters.
 */
$options = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>

Et à déchiffrer:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}
?>
1
jvitoroc