How secure is BCRYPT(SHA1(Password))

First of all, thank you for taking the time to determine how to do this correctly and improve security for your users!

Migrating password storage while taking legacy hashes into account is relatively common.

For your migration scenario, bcrypt(base64(sha1(password))) would be a reasonable balance. It avoids the null problem (important - you definitely don't want to leave out the base64 stage!), sidesteps bcrypt's native 72-character limit, and is 100% compatible with your existing hashes.

You would simply hash all existing SHA1s with bcrypt(base64(sha1)), and then hash all new passwords with the full sequence. (You could also use SHA256 instead, though this would increase your code complexity slightly, to check to see if SHA1 or SHA25 was used (or to just try them both and pass if either succeeds) Long term, SHA256 would be more resistant to collisions, so would be a better choice).

For resistance to bruteforce, this would not only be equivalent to bcrypt, but would be theoretically superior (though in practice, 72 characters is so large for password storage than they are effectively the same).

Bonus advice:

  • Be sure to use a bcrypt work factor that is high enough to be resistant to offline attacks - the highest value that your users can tolerate, 100ms or higher (probably at least a work factor of 10). For speeds under 1 second, bcrypt may actually be slower for the attacker (better for the defender) than its modern replacements scrypt and Argon2 (YMMV).
  • Store the default value for the work factor as a system-wide configurable variable, so you can periodically increase it as your underlying hardware speeds get faster (or more distributed).

This will limit the keyspace to roughly 2160, as SHA-1 outputs 20 byte digests, but it is otherwise fine. In fact, bcrypt has an input limit of 72 bytes, so it is not at all uncommon for people to hash a password first using a fast, cryptographically secure hash. Because the limit is larger than the digest size of SHA-1, you may instead want to use a hash with a larger digest. Note that the null problem requires the digest be converted into another encoding, such as base64.

If you already have a database full of SHA-1 hashes, it is perfectly fine to run them through bcrypt.