How many rounds of hashing is enough for a password manager?

I'm currently writing my own little password manager

That's your first mistake. Something this complex has many subtle pitfalls that even experts sometimes fall into, without plenty of experience in this area you don't have a chance making something even close to secure.

stores the key in an SHA256 hash

Uh oh...

This doesn't necessarily indicate you're doing something wrong, but I have strong doubts that you're going to do it right. I assume you're talking about a master password being hashed here? The master password should be turned into a key using a KDF like PBKDF2, bcrypt, or Argon2, then this key is used to encrypt the stored passwords.

If you want to have a way to verify that the password is correct, storing a hash of the key should be fine, but you MUST NOT store the key itself...if you store the key anyone who gets access to your storage has everything they need to decrypt all the passwords!

If you aren't talking about hashing a master password and you do mean an actual randomly generated key then I have no idea what you're trying to accomplish here, but you shouldn't be using a slow KDF with a large number of iterations.

Alternatively you could be hashing the master password twice, once to store as a hash to later verify that the password the user enters is correct, and again to use as a key for encryption. Depending on how this is done it could range from a design flaw to completely giving away the key.

Edit: After seeing the full code it seems to be a fourth option: you store a hash of the password to later check if the entered password is correct, then you hash this hash to use as the key, which is nearly as bad as just storing the key itself.

I create the hash by doing the following:

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

It's not really clear what raw is here, but I'm assuming it's the password. What you're doing is an unsalted hash using SHA256. Don't try to create your own KDF!

After it is created it is stored with the following:

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

So, you're creating the key by hashing the password, then adding a random salt to the front and back? Not only is concatenating 2 different salts to the front and back non-standard, it's not accomplishing anything here because it's done after the KDF has already finished! You're just adding in some random values for the sake of having them there.


To show just how bad this is (as of commit 609fdb5ce976c7e5aa1832670505da60012b73bc), all it takes to dump all stored passwords without requiring any master password is this:

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

While it may be a good learning experience to try creating a password manager, please please don't ever use it for anything remotely important. As @Xenos suggests, it doesn't seem like you have enough experience that creating your own password manager would really be beneficial anyway, it would likely be a better learning opportunity to take a look at an existing open source password manager.


First let me disclose that I work for 1Password, a password manager, and although it may seem self-serving, I have to add my voice to those telling you that writing a secure password manager is harder than it may first appear. On the other hand, it is good that you are trying and asking about it in public. This is a good way to learn that it is harder than it may first appear.

Don't authenticate. Encrypt instead

I've taken a quick glance at the source you link to in your update, and am delighted to see that you have taken on some advice that has been offered so far. But unless I'm misreading your code, you still have a fundamental design error making it massively insecure.

You appear to be treating the master password entry as an authentication question instead of as encryption key derivation. That is, you are checking the entered password (after hashing) with something that is stored, and if that check passes you are using that stored key to decrypt the data.

What you should be doing is storing an encrypted encryption key. Let's call it the master key. This should be generated randomly when you first set up a new instance. This master key is what you use to encrypt and decrypt the actual data.

The master key is never stored unencrypted. It should be encrypted with what is sometimes called a Key Encryption Key (KEK). This KEK is derived via your Key Derivation Function (KDF) from the user's master password and a salt.

So the output of your use of PBKDF2 (thank you for using that instead of just repeated hashing) will be your KEK, and you use the KEK to decrypt the master key, and then you use decrypted master key to decrypt your data.

Now perhaps that is what you are doing and I've misread your code. But if you are just comparing what you derive through your KDF to something stored and then making a decision whether to decrypt, then your system is massively and horrendously insecure.

Please make the boldest biggest first line of the project README scream the fact that it is massively insecure. And add that to every source file as well.

Some other points

Here are just a few other things that I noticed when glancing at the source

  • Use an authenticated encryption mode such as GCM instead of CBC.

  • Your approach of just encrypting the whole thing will work for small amounts to data, but it won't scale once you have larger data sets.

    When it comes time to encrypting (parts of) records separately, keep in mind that you will need a unique nonce (for GCM) or unique initializate vectors (if you unwisely stick with CBC) for each record.

  • Your data destruction after 3 failures is dangerous and adds no security.

    A sophisticated attacker will just copy your data file and write their own script for trying to crack the password. They will also make their own copy of the captured data. Your data destruction only makes it easy for someone to accidentally or maliciously destroy your data.

How many rounds of PBKDF2

So to your original question. The answer is that it depends. On one side, it depends on how strong the master password is and what sorts of resources an attacker will throw at the problem. And it also depends on what resources the defender can throw at it. For example, will you be running your KDF on a mobile device with limited battery capacity?

But as it happens we at 1Password are trying to gauge the cost of cracking by offering prizes to people or groups who crack a 42.5 bit password hashed with 100,000 rounds of PBKDF2-HMAC-SHA256.

Diminishing gains from slow hashing

But what's more important than figuring out how to weigh all of those is to understand that slow-hashing, while absolutely essential for a password manager KDF, yields diminishing marginal gains once it is tuned high enough.

Say you are using 100K rounds and you have some master password, P. If you picked a random digit, 0–9 and appended it to P you would get a ten-fold increase in cracking resistence. To get the same increase via increasing the rounds of PBKDF2, you would need to go up to 1 million rounds. The point is that once you have a decent amount of slow hashing, you get far more strength for defender effort by increasing the strength of the password than you do my increasing the number of rounds.

I wrote about this some years back in Bcrypt is great, but is password cracking unfeasible?


Do not use raw SHA256 - it is able to be accelerated using GPUs and therefore prone to brute force. It isn't broken, by any stretch, it just isn't best practice any longer. PBKDF2 works to counteract that to some extent, but you should use bcrypt or scrypt preferentially.

Reinventing the wheel (which is pretty much what you're doing if you aren't using PBKDF2-SHA256, bcrypt or scrypt) is never a good idea in crypto.