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

je crée une application qui stockera les mots de passe, que l'utilisateur peut récupérer et voir. Les mots de passe sont pour un dispositif matériel, donc vérifier contre les hachures sont hors de question.

ce que j'ai besoin de savoir 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ù puis-je conserver la clé privée?

  4. au lieu de stocker la clé privée, est-ce une bonne idée d'exiger des utilisateurs d'entrer la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application peuvent être fiables)

  5. de quelles façons le mot de passe peut-il être volé et déchiffré? Que dois-je faire attention?

163
demandé sur jww 2011-02-23 13:48:00

8 réponses

personnellement, j'utiliserais mcrypt comme d'autres affiché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 ? tout d' ils. La méthode la plus sûre si vous allez Crypter est de protéger contre les vulnérabilités de divulgation d'information (XSS, inclusion à distance, etc). S'il sort, L'attaquant peut éventuellement déchiffrer le cryptage (aucun cryptage n'est 100% réversible sans la clé-comme @Nuluserexception le souligne, ce n'est pas tout à fait vrai. Il existe certains systèmes de cryptage qui sont impossibles à déchiffrer tels que OneTimePad ).

  3. où puis-je stocker la clé privée?

    ce que je ferais c'est utiliser 3 touches. L'un est fourni par l'utilisateur, l'une est spécifique à l'application et l'autre est spécifique à l'utilisateur (comme le 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 d'environnement, etc.). L'utilisateur spécifique serait stockée dans une colonne dans la db suivante pour le mot de passe crypté. L'utilisateur fournies ne seraient pas stockées. Alors, vous feriez quelque chose comme ceci:

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

    l'avantage ici, c'est que deux des clés peuvent être compromises sans que les données soient compromises. S'il y a une attaque par Injection SQL, ils peuvent obtenir le $userKey , mais pas les 2 autres. S'il y a une exploitation de serveur local, ils peuvent obtenir $userKey et $serverKey , mais pas le troisième $userSuppliedKey . Si ils vont battre l'utilisateur avec une clé, ils peuvent obtenir de la $userSuppliedKey , mais pas les 2 autres (mais là encore, si l'utilisateur est battu avec une clé, vous êtes trop tard de toute façon).

  4. au lieu de stocker la clé privée, est-ce une bonne idée d'exiger des utilisateurs d'entrer la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application peuvent être fiables)

    absolument. En fait, c'est la seule façon que je le ferais. Sinon, vous aurez besoin de stocker une version non chiffrée dans un format de stockage durable (mémoire partagée comme APC ou memcached, ou dans un fichier de session). C'est vous exposer à d'autres compromis. Ne stockez jamais la version non chiffrée du mot de passe dans quoi que ce soit sauf une variable locale.

  5. de quelle manière le mot de passe peut-il être volé et déchiffré? Que dois-je faire attention?

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

    utilisez SSL pour tout le trafic. Et assurez-vous que rien sur le serveur n'a de vulnérabilités (CSRF, XSS, SQL Injection, augmentation de privilèges, exécution de Code à distance, etc.).

Edit: Voici une implémentation de 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 sur inférieur à 5.6, vous pouvez utiliser cette fonction de remplacement qui met en œuvre une comparaison de temps-Sécurité fonction 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);

puis, 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 différentes instances seront encore correctement déchiffrer les données.

Maintenant, comment ça fonctionne et pourquoi l'utiliser sur une autre solution:

  1. Clés

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

    • la clé utilisée pour le cryptage est unique pour chaque bloc de texte crypté. La clé fournie devient donc une"clé principale". Cette classe fournit donc la rotation des clés pour les clés cipher et auth.

    • NOTE IMPORTANTE , le paramètre $rounds est configuré pour les vraies clés aléatoires d'une puissance suffisante (128 bits de cryptographically Secure random à un minimum). Si vous allez utiliser un mot de passe, ou clé non aléatoire (ou moins aléatoire que 128 bits de CS aléatoire), vous doit augmenter ce paramètre. Je suggère un minimum de 10000 pour les mots de passe (le plus vous pouvez vous permettre, le mieux, mais il ajoutera à l'exécution)...

  2. Intégrité Des Données

    • la version mise à jour utilise ENCRYPT-THEN-MAC, qui est une bien meilleure méthode pour assurer l'authenticité de la des données chiffrées.
  3. cryptage:

    • il utilise mcrypt pour effectuer le cryptage. Je suggère d'utiliser soit MCRYPT_BLOWFISH ou MCRYPT_RIJNDAEL_128 Cypher et MCRYPT_MODE_CBC pour le mode. C'est assez fort, et encore assez rapide (un cycle de chiffrement et de déchiffrement prend environ 1/2 seconde sur ma machine).

maintenant, comme le point 3 de la première liste, ce qui vous donnerait une fonction comme ceci:

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 il va être étiré plus tard, il n'y a pas vraiment un grand point à faire.

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

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

donc pour une source de données de 16 caractères, il y aura 16 caractères de données à crypter. Donc cela signifie que la taille réelle des données cryptées est de 16 octets à cause du padding. Ajoutez ensuite les 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 80 personnages au-dessus, et à pire, 87 caractère de frais généraux...

j'espère que ça aidera...

Note: 12/11/12: je viens de mettre à jour cette classe avec une méthode de cryptage beaucoup mieux, en utilisant des clés mieux dérivées, et la fixation de la génération MAC...

203
répondu ircmaxell 2015-05-27 13:18:25

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

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

Où puis-je stocker la clé privée? Si vous avez décidé de mettre en œuvre l'algorithme de cryptographie à clé publique(par exemple RSA), vous ne stockez pas de clé privée. utilisateur de la clé privée. votre système a une 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 d'obliger les utilisateurs à entrer la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application peut être fiable) Eh bien, si votre utilisateur peut se rappeler des nombres premiers ridiculement longs alors - oui, pourquoi pas. Mais généralement vous aurez besoin de venir avec le système qui permettra aux utilisateurs de stocker leur clé quelque part.

de quelles façons le mot de passe peut-il être volé et déchiffré? Que dois-je faire attention? Cela dépend de l'algorithme utilisé. Cependant, assurez-vous toujours que vous n'envoyez pas mot de passe non crypté ou de l'utilisateur. Soit le chiffrer/le déchiffrer du côté du client, soit utiliser https(ou un autre moyen cryptographique de l'utilisateur 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érerais d'utiliser un simple XOR Cipher. Le principal problème avec cet algorithme est qu'il pourrait être facilement cassé par analyse de fréquence. Cependant, comme généralement les mots de passe ne sont pas faits à partir de longs paragraphes de texte anglais Je ne pense pas que tu devrais t'en inquiéter. Le deuxième problème avec XOR Cipher est que si vous avez un message à la fois crypté et sous forme déchiffrée, vous pouvez facilement trouver mot de passe avec lequel il a été crypté. Encore une fois, pas un gros problème dans votre cas car il affecte seulement l'utilisateur qui a déjà été compromis par d'autres moyens.

14
répondu Ivan 2011-02-23 11:49:22
  1. la fonction PHP que vous recherchez est Mcrypt ( http://www.php.net/manual/en/intro.mcrypt.php ).

L'exemple le 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échiffrer votre mot de passe.

  1. Le meilleur algorithme est plutôt subjectif-demandez à 5 personnes, obtenez 5 réponses. Personnellement si le The default (Blowfish) n'est pas assez bon pour vous, vous avez probablement de plus gros problèmes!

  2. étant donné QU'il est nécessaire par PHP pour chiffrer - pas sûr que vous pouvez le cacher n'importe où - bienvenue des commentaires sur ce. Les meilleures pratiques de codage standard de PHP s'appliquent bien sûr!

  3. étant donné que la clé de cryptage sera dans votre code de toute façon, pas sûr de ce que vous gagnera, à 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, alors game over.

j'ai mis un cavalier sur ma réponse - Je ne suis pas un expert de PHP crypto, mais, je pense que ce que j'ai répondu est la pratique standard - je me réjouis des commentaires que d'autres peuvent avoir.

12
répondu Jon Rhoades 2013-12-22 04:44:05

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é (comme les valeurs parfois cryptées peuvent les rendre difficiles à envoyer en utilisant d'autres technologies comme curl, ou json).

après avoir chiffré avec succès en utilisant mcrypt, exécutez-le à travers base64_encode et convertissez-le en code hexadécimal. Une fois en code hexadécimal, il est facile de le transférer de différentes 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
répondu Bradley 2011-02-25 23:20:21

Je ne suggérerais le cryptage à clé publique que si vous voulez la possibilité de 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. Le OpenSSL extension, en particulier openssl_public_encrypt et openssl_private_decrypt
  2. ce serait RSA droite en supposant que vos mots de passe s'adapteront dans la taille de la clé-rembourrage, sinon vous avez besoin d'un symétrique couche
  3. stocker les deux clés pour chaque utilisateur, la phrase de passe de la clé privée est leur mot de passe d'application

symétrique

  1. the Mcrypt extension
  2. AES-256 est probablement un pari sûr, mais cela pourrait être une question en elle-même
  3. Vous n'avez pas - ce serait leur demande de mot de passe

les deux

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

5 .

  • si quelqu'un vole les données de l'application, c'est aussi sûr que le chiffre symétrique (pour le schéma de clé publique, il est utilisé pour protéger la clé privée avec la phrase de passe.)
  • votre application ne devrait certainement être accessible sur SSL, de préférence en utilisant des certificats clients.
  • envisager d'ajouter un second facteur d'authentification qui ne serait utilisé qu'une fois par session, comme un token envoyé par SMS.
5
répondu Long Ears 2011-02-23 11:57:33

j'ai essayé quelque chose comme cela, mais s'il vous plaît noter que je ne suis pas cryptographe ni je tiens des connaissances approfondies sur php ou n'importe quel langage de programmation. C'est juste une idée. Mon idée est de stocker key dans un fichier ou database (ou entrer manuellement) qui(emplacement) ne peut pas être facilement prédit(et bien sûr tout sera déchiffré un jour, le concept est d'allonger le temps de déchiffrement) et de 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 = "myemail@domain.com";
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.

s'il vous Plaît note que c'est juste un concept. Toute amélioration dans ce code serait très appréciable.

2
répondu Santosh Linkha 2011-03-01 04:29:28

les mots de passe sont pour un dispositif matériel, donc vérifier contre les hachures sont hors de question

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

comme d'autres l'ont dit, l'extension mcrypt fournit l'accès à de nombreuses fonctions cryptographiques - cependant vous invitez vos utilisateurs à mettre tous leurs œufs dans un panier - qui sera potentiellement une cible pour les attaquants - et si vous ne savez même pas comment commencer à résoudre le problème, alors vous faites à vos utilisateurs un mauvais service. Vous n'êtes pas en mesure de comprendre comment protéger les données.

la plupart des vulnérabilités de sécurité ne se produisent pas parce que l'algorithme sous - jacent est défectueux ou non sûr-mais en raison de problèmes avec la façon dont l'algorithme est utilisé dans le code de l'application.

cela étant dit, il est possible de construire un système.

vous ne devez envisager le cryptage asymétrique que si vous avez l'obligation pour un utilisateur de créer un message sécurisé lisible par un autre utilisateur (spécifique). La raison étant que son calcul coûteux. Si vous voulez juste fournir un dépôt pour les utilisateurs d'entrer et de récupérer leurs propres données, le cryptage symétrique est adéquat.

si, cependant, vous stockez la clé pour déchiffrer le message au même endroit que le message chiffré (ou si le message chiffré est stocké), alors le système n'est pas sécurisé. Utilisez le même token pour authentifier l'utilisateur que pour la clé de déchiffrement (ou dans le cas du cryptage assymétrique, utilisez le token comme la phrase de passe de clé privée). Puisque vous aurez besoin de stocker le token sur le serveur où le déchiffrement a lieu au moins temporairement, vous pourriez vouloir envisager d'utiliser un substrat de stockage de session non-searchable, ou passer le token directement à un démon associé à la session qui stockerait le jeton en mémoire et effectuerait le déchiffrement des messages à la demande.

2
répondu symcbean 2011-03-01 10:33:05

Utiliser 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écrypter:

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

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}
?>
1
répondu jvitoroc 2017-09-24 22:51:26