web-dev-qa-db-fra.com

Combien de tours de hachage suffisent pour un gestionnaire de mots de passe?

J'écris actuellement mon propre petit gestionnaire de mots de passe qui stocke la clé dans un SHA256 haschich, avec du sel. Je crée le hachage en procédant comme suit:

def sha256_rounds(raw, rounds=100001):
    obj = hashlib.sha256()
    for _ in xrange(rounds):
        obj.update(raw)
        raw = obj.digest()
    return obj.digest()

Une fois créé, il est stocké avec les éléments suivants:

    key = base64.urlsafe_b64encode(provided_key)
    length = len(key)
    with open(key_file, "a+") as key_:
        front_salt, back_salt = os.urandom(16), os.urandom(16)
        key_.write("{}{}{}:{}".format(front_salt, key, back_salt, length))

Les questions/préoccupations que j'ai sont:

  • Est-ce une façon acceptable de stocker une clé hachée?
  • Dois-je utiliser plus d'itérations?
  • Ma technique de salage est-elle suffisante? Ou devrais-je utiliser une technique de salage différente?

Si ce n'est pas une approche acceptable pour stocker un mot de passe/clé haché, quelles autres mesures puis-je prendre pour rendre cela plus sûr?


MISE À JOUR:

J'ai pris beaucoup de conseils de votre gars, et si vous souhaitez voir le résultat, jusqu'à présent , de mon petit gestionnaire de mots de passe, vous pouvez le trouver - ici . Les conseils de chacun sont très appréciés et je continuerai à m'efforcer de rendre cela aussi excellent que possible. (si ce lien ne fonctionne pas, utilisez celui-ci https://github.com/Ekultek/letmein )

15
CertifcateJunky

J'écris actuellement mon propre petit gestionnaire de mots de passe

C'est ta première erreur. Quelque chose dans ce complexe a de nombreux pièges subtils dans lesquels même les experts tombent parfois, sans beaucoup d'expérience dans ce domaine, vous n'avez aucune chance de faire quelque chose de même à sécuriser.

stocke la clé dans un hachage SHA256

Euh oh ...

Cela ne signifie pas nécessairement que vous faites quelque chose de mal, mais je doute fort que vous le fassiez correctement. Je suppose que vous parlez d'un mot de passe principal haché ici? Le mot de passe principal doit être transformé en clé à l'aide d'un KDF comme PBKDF2, bcrypt ou Argon2, puis cette clé est utilisée pour crypter les mots de passe stockés.

Si vous voulez avoir un moyen de vérifier que le mot de passe est correct, le stockage d'un hachage de la clé devrait être bien, mais vous NE DOIT PAS stocker la clé elle-même ... si vous stockez la clé tous ceux qui ont accès à votre stockage ont tout ce dont ils ont besoin pour décrypter tous les mots de passe!

Si vous ne parlez pas de hacher un mot de passe principal et que vous voulez dire une clé générée de manière aléatoire, alors je n'ai aucune idée de ce que vous essayez d'accomplir ici, mais vous ne devriez pas utiliser un KDF lent avec un grand nombre d'itérations .

Vous pouvez également hacher le mot de passe principal deux fois, une fois pour le stocker en tant que hachage afin de vérifier plus tard que le mot de passe saisi par l'utilisateur est correct, et à nouveau pour l'utiliser comme clé de cryptage. Selon la façon dont cela est fait, cela peut aller d'un défaut de conception à la remise complète de la clé.

Edit: Après avoir vu le code complet, il semble que ce soit une quatrième option: vous stockez un hachage du mot de passe pour vérifier plus tard si le mot de passe entré est correct, puis vous hachez ceci hachage à utiliser comme clé, ce qui est presque aussi mauvais que de simplement stocker la clé elle-même.

Je crée le hachage en procédant comme suit:

def sha256_rounds(raw, rounds=100001):
    obj = hashlib.sha256()
    for _ in xrange(rounds):
        obj.update(raw)
        raw = obj.digest()
    return obj.digest()

On ne sait pas vraiment ce que raw est ici, mais je suppose que c'est le mot de passe. Ce que vous faites est un hachage non salé utilisant SHA256. N'essayez pas de créer votre propre KDF!

Une fois créé, il est stocké avec les éléments suivants:

key = base64.urlsafe_b64encode(provided_key)
length = len(key)
with open(key_file, "a+") as key_:
    front_salt, back_salt = os.urandom(16), os.urandom(16)
    key_.write("{}{}{}:{}".format(front_salt, key, back_salt, length))

Donc, vous créez la clé en hachant le mot de passe, alors en ajoutant un sel aléatoire à l'avant et à l'arrière? Non seulement la concaténation de 2 sels différents à l'avant et à l'arrière n'est pas standard, mais elle n'accomplit rien ici car elle est effectuée après que le KDF a déjà terminé! Vous ajoutez simplement des valeurs aléatoires pour les y avoir.


Pour montrer à quel point c'est mauvais (à partir de la validation 609fdb5ce976c7e5aa1832670505da60012b73bc), tout ce qu'il faut pour vider tous les mots de passe stockés sans nécessitant un mot de passe principal est le suivant:

from encryption.aes_encryption import AESCipher
from lib.settings import store_key, MAIN_DIR, DATABASE_FILE, display_formatted_list_output
from sql.sql import create_connection, select_all_data

conn, cursor = create_connection(DATABASE_FILE)
display_formatted_list_output(select_all_data(cursor, "encrypted_data"), store_key(MAIN_DIR))

Bien que cela puisse être une bonne expérience d'apprentissage pour essayer de créer un gestionnaire de mots de passe, s'il vous plaît s'il vous plaît ne l'utilisez jamais pour quelque chose d'important à distance. Comme le suggère @Xenos, il ne semble pas que vous ayez suffisamment d'expérience pour créer votre propre gestionnaire de mots de passe de toute façon, ce serait probablement une meilleure opportunité d'apprentissage pour jeter un œil à un gestionnaire de mots de passe open source existant.

80
AndrolGenhald

Permettez-moi d'abord de révéler que je travaille pour 1Password, un gestionnaire de mots de passe, et même si cela peut sembler égoïste, je dois ajouter ma voix à ceux qui vous disent que l'écriture d'un gestionnaire de mots de passe sécurisé est plus difficile qu'il n'y paraît. D'un autre côté, il est bon que vous essayiez et posiez des questions à ce sujet en public. C'est un bon moyen d'apprendre qu'il est plus difficile qu'il n'y paraît à première vue.

Ne vous authentifiez pas. Chiffrer à la place

J'ai jeté un rapide coup d'œil à la source vers laquelle vous liez dans votre mise à jour, et je suis ravi de voir que vous avez pris certains conseils qui ont été proposés jusqu'à présent. Mais à moins que j'interprète mal votre code, vous avez toujours une erreur de conception fondamentale qui le rend massivement non sécurisé .

Vous semblez traiter l'entrée de mot de passe principal comme une question authentification plutôt que comme une dérivation de clé de chiffrement. Autrement dit, vous vérifiez le mot de passe entré (après le hachage) avec quelque chose qui est stocké, et si cette vérification réussit, vous utilisez cette clé stockée pour décrypter les données.

Ce que vous devez faire, c'est stocker une clé de chiffrement chiffrée. Appelons cela la clé principale. Cela doit être généré de manière aléatoire lors de la première configuration d'une nouvelle instance. Cette clé principale est ce que vous utilisez pour crypter et décrypter les données réelles.

La clé principale n'est jamais stockée non chiffrée. Il doit être chiffré avec ce qu'on appelle parfois une clé de chiffrement de clé (KEK). Cette KEK est dérivée via votre fonction de dérivation de clé (KDF) du mot de passe principal de l'utilisateur et d'un sel.

Ainsi, la sortie de votre utilisation de PBKDF2 (merci de l'utiliser au lieu du hachage simplement répété) sera votre KEK, et vous utilisez la KEK pour déchiffrer la clé principale, puis vous utilisez la clé principale déchiffrée pour déchiffrer vos données.

Maintenant, c'est peut-être ce que vous faites et j'ai mal lu votre code. Mais si vous comparez simplement ce que vous dérivez via votre KDF à quelque chose de stocké et que vous décidez ensuite de décrypter, votre système est massivement et horriblement insécurisé.

Veuillez faire la première ligne la plus audacieuse du projet README criez le fait qu'il est massivement non sécurisé. Et ajoutez cela à chaque fichier source également.

Quelques autres points

Voici quelques autres choses que j'ai remarquées en regardant la source

  • Utilisez un mode de chiffrement authentifié tel que GCM au lieu de CBC.

  • Votre approche consistant à chiffrer le tout fonctionnera pour de petites quantités de données, mais elle ne sera pas mise à l'échelle une fois que vous aurez de plus grands ensembles de données.

    Quand vient le temps de chiffrer (des parties de) les enregistrements séparément, gardez à l'esprit que vous aurez besoin d'un nonce unique (pour GCM) ou de vecteurs d'initialisation uniques (si vous vous en tenez à CBC) pour chaque enregistrement.

  • La destruction de vos données après 3 échecs est dangereuse et n'ajoute aucune sécurité.

    Un attaquant sophistiqué copiera simplement votre fichier de données et écrira son propre script pour essayer de casser le mot de passe. Ils feront également leur propre copie des données capturées. La destruction de vos données ne fait que faciliter la destruction accidentelle ou malveillante de vos données par quelqu'un.

Combien de tours de PBKDF2

Donc, à votre question d'origine. La réponse est que cela dépend. D'un côté, cela dépend de la force du mot de passe principal et des types de ressources qu'un attaquant jettera contre le problème. Et cela dépend aussi des ressources que le défenseur peut lui donner. Par exemple, exécuterez-vous votre KDF sur un appareil mobile avec une capacité de batterie limitée?

Mais il se trouve que chez 1Password, nous essayons d'évaluer le coût du cracking en offrant des prix aux personnes ou aux groupes qui crackent un mot de passe de 42,5 bits haché avec 100000 tours de PBKDF2-HMAC-SHA256.

Diminuer les gains du hachage lent

Mais ce qui est plus important que de trouver comment peser tout cela, c'est de comprendre que le hachage lent, bien qu'absolument essentiel pour un gestionnaire de mots de passe KDF, donne une diminution des gains marginaux une fois qu'il est réglé suffisamment haut.

Supposons que vous utilisez 100 000 tours et que vous avez un mot de passe principal, [~ # ~] p [~ # ~]. Si vous choisissez un chiffre aléatoire, 0–9 et que vous l'ajoutez à [~ # ~] p [~ # ~] vous obtiendrez une multiplication par dix de la résistance à la fissuration. Pour obtenir la même augmentation en augmentant les tours de PBKDF2, vous devez aller jusqu'à 1 million de tours. Le fait est qu'une fois que vous avez une quantité décente de hachage lent, vous obtenez beaucoup plus de force pour l'effort du défenseur en augmentant la force du mot de passe que vous n'augmentez le nombre de tours.

J'ai écrit à ce sujet il y a quelques années Bcrypt est génial, mais le craquage de mot de passe est-il impossible?

19
Jeffrey Goldberg

N'utilisez pas de SHA256 brut - il peut être accéléré à l'aide de GPU et donc sujet à la force brute. Il n'est pas cassé, par aucun tronçon, ce n'est tout simplement plus la meilleure pratique. PBKDF2 fonctionne pour contrer cela dans une certaine mesure, mais vous devez utiliser bcrypt ou scrypt de préférence.

Réinventer la roue (ce qui est à peu près ce que vous faites si vous n'utilisez pas PBKDF2-SHA256, bcrypt ou scrypt) n'est jamais une bonne idée en crypto.

6
crovers

En regardant le code dans le lien que vous avez publié (reproduit ci-dessous, en partie), je suis un peu confus.

Si je le lis correctement, main() appelle store_key() pour charger une clé à partir d'un fichier sur le disque, puis utilise compare() pour comparer une clé donnée par l'utilisateur avec celle une. Les deux s'exécutent via sha256_rounds() qui exécute PBKDF2 sur eux.

Ensuite, vous utilisez le même stored_key, Celui chargé à partir du fichier, pour faire le chiffrement?

C'est complètement et complètement à l'envers. Quiconque accède au fichier de clé peut simplement charger la clé stockée et l'utiliser pour décrypter les données stockées ou stocker de nouvelles données cryptées sous la même clé, sans avoir à passer par le script to " vérifier "le mot de passe.

Au mieux, je pense que vous confondez en utilisant le mot de passe/phrase de passe donné par l'utilisateur pour produire un clé de cryptage, et en authentifiant l'utilisateur contre un hachage de mot de passe stocké. Vous ne pouvez pas faire les deux avec le même hachage, le cryptage est complètement hors de propos si vous avez stocké la clé de cryptage avec les données cryptées.

Bien sûr, les mots de passe sont traités avec une sorte de hachage/KDF à la fois lorsqu'ils sont utilisés pour l'authentification et lorsqu'ils sont utilisés pour produire une clé de chiffrement. Mais cela ressemble plus à une étape de prétraitement nécessaire simplement parce que les humains ne peuvent pas mémoriser des clés 256 bits, et nous devons plutôt compter sur des mots de passe à faible entropie. Ce que vous faites avec le hachage/la clé que vous avez obtenu diffère entre les deux cas d'utilisation.


Ensuite, il y a la chose que vous avez un sel codé en dur dans sha256_rounds(), ce qui va à peu près à l'encontre du but d'un sel en général. Je pourrais également commenter le code de manière plus générale, mais ce n'est pas codereview.SE.

Veuillez me dire que j'ai mal lu le code.


Les parties du code que j'ai examinées:

def main():
    stored_key = store_key(MAIN_DIR)    
    if not compare(stored_key):
        ...
    else:
            ... # later
            password = Prompt("enter the password to store: ", hide=True)
            encrypted_password = AESCipher(stored_key).encrypt(password)



def store_key(path):
    key_file = "{}/.key".format(path)
    if not os.path.exists(key_file):
        ...
    else:
        retval = open(key_file).read()
        amount = retval.split(":")[-1]
        edited = retval[16:]
        edited = edited[:int(amount)]
        return edited
3
ilkkachu

Utilisez un outil maintenu qui décidera pour vous et augmentera le nombre par défaut ou même l'algorithme au fil du temps. Je suggère libsodium, mais il y en a d'autres. Libsodium a des valeurs par défaut pour "interactif" hachage de mot de passe et pour le cryptage symétrique. Assurez-vous que votre logiciel peut un jour "mettre à niveau" les paramètres selon vos besoins. Un outil comme libsodium changera leurs valeurs par défaut de temps en temps afin que vous puissiez les suivre.

Je fais du développement pour un open source gestionnaire de mots de passe et j'utilise libsodium. J'ai aussi trouvé que Mitro document de sécurité mérite d'être lu.

2
Bufke