30

I am presented with the following scenario:

I have a MySQL-Database with a table of users. The table has two fields: username and password. The password is stored as an unsalted hash.

An over 15 year old application uses this database to authenticate access to its services. It is only accessible internally.

Our team is tasked to modernize these services by offering a new application following best practices and lessons learned of the last 15 years (and potentially more that were ignored in the original application). This application shall be accessible from the wide open internet.

We are supposed to reuse the current database for authentication purposes to allow both applications to run in parallel on the underlying same authentication database.

I voiced several concerns regarding security as this application is about handling personal details of my coworkers and me.

We decided that part of our modernization is that password policies are put in place and that passwords are stored both salted and peppered.

This means our database table would get a third field salt and the password field now stores our salted and peppered hash.

The problem here is that this will break the authentication in our old application. As the code is very much legacy and we don't even have a working compiler for it anymore changing the code of the original application is out of question.

My question therefore is:

  • Is there a secure way to add salt (and pepper) to our authentication database while maintaining the old application's ability to authenticate users? Keep in mind that while the old application is inherently insecure it will not be accessible outside of our intranet, the new application however will be.
Ben
  • 403
  • 4
  • 10
  • 2
    Can you edit your question and say how the other application is accessing the database to authenticate users? Is it via a direct query against the table, a query to a view, or executing a stored procedure? – Freiheit Dec 02 '19 at 21:39
  • 1
    @Freiheit I would love to but I honestly do not know that. I never got access to the application. I can try to ask around. – Ben Dec 03 '19 at 07:33
  • @Ben does it matter what flavour of db server you use or is it just the object name and schema that matters? – Frank Dec 03 '19 at 08:20
  • Probably not smart enough for it's own answer: On login you can attempt to auth via the new way, if it fails attempt to auth via the old way. Slowly push users to re-register their old passwords via the new way. You can also get the salt - hash it and add it at the end of the password hash in the DB ("pwd"+"newhash") and auth against this on login. – Иво Недев Dec 03 '19 at 09:45
  • @Frank it was not important until 2 hours ago where it was decided that it will be a MySQL Database (as it was previously). I updated the question accordingly. – Ben Dec 03 '19 at 10:30
  • 2
    @Ben You shouldn't need access to the application to find out what queries it is running since you control the database. Review how to turn on query logging or auditing in MySQL and that will let you observe what the application does without having access to the application source. – Freiheit Dec 03 '19 at 14:12
  • is there any reason you can't simply have 2 separate "password" fields. One that stores the weakly hashed version that is used exclusively by the old internal system, and one that stores a secure version that is used exclusively by the new public system? – Darren H Dec 03 '19 at 15:48
  • @DarrenH or maybe just a `BIT` column like `UseOldHash`? – Ivan García Topete Dec 03 '19 at 17:57
  • Did something like you describe - 1) introduce new column to database to flag new passwords. Change login-code to alter the password on first successful login (so you have the password un-hashed) use the new salted method and store password for the user - set flag that this one has a new salted password-hash .. done – eagle275 Dec 04 '19 at 13:53
  • 1
    Is your requirement to keep the old application working without any code changes? If that is the case, then no, you cannot change the user table without breaking the old application, as the old application requires an unsalted password. If you can change the old application and the requirement is for people to continue to use their passwords without having to change them, you can authenticate against Hash(salt+Hash(password)) instead of Hash(salt+password) as you these are no less secure and you can calculate them without knowing the original password. – Simon G. Dec 04 '19 at 14:50

9 Answers9

50

You have conflicting requirements here. The compatibility requirements forces you to keep the old hashes. The security requirements forces you to drop them. You will have to make a choice here about what requirements to fulfill.

If you decide to keep the backwards compatibility, try making the best out of a bad situation:

  • The old hash and the new hash should be stored in different tables, and the database user that the web application uses should not have read access to the old hash. Use table and/or column permissions for this.
  • As soon as you no longer need the old application, drop the table with the old hash. Ashley Madison famously failed at this point - they upgraded to bcrypt, and then for some idiotic reason they left the old MD5 hashes lying around in the database. When the database was leaked, that fancy bcrypt did not help much...

Or, alternatively, if you are not afraid to create a bit of a mess:

  • Drop the old hashes. In the new application, add on option "Create temporary password for old application". It gives you a long, random password that is hashed in the old way and only kept in the database for X minutes. The user can then logg in to the old application, and the password is then automatically deleted.
Steve Sether
  • 21,480
  • 8
  • 50
  • 76
Anders
  • 64,406
  • 24
  • 178
  • 215
  • 1
    I changed the accepted answer to this as it shines light on the thing that there are ways to work around the problem - just not a perfect one. But I guess there is always a compromise we have to live with. I would love to accept several answers that shined light on potential solutions (such as the answer by Brian) or showed that there is no "perfect secure way" such as the one by mti2935, but as I can't I think this one here is the most important takeaway - in combination with all the other great answers provided. – Ben Dec 03 '19 at 10:32
  • 5
    @Ben Disagree respectfully - you can easily solve this with hash(hash+salt) and a table view/triggers. Though I would add the problem is probably organizational. Not sure why you'd want the same auth db for an internal LoB app driving a public facing one. – Frank Dec 03 '19 at 10:42
  • An alternative way that I've done was to detect the first character of the stored password. If it was a `"$"`, I knew that I was supposed to use [`password_verify()`](https://www.php.net/manual/en/function.password-verify.php). Otherwise, it was an MD5 password, and I just had to re-hash the password after confirming the MD5 hash is the same, and store in the database. This gets rid of the MD5 and keeps your pristine new hash, with 0 usability issues. In my case, I did this re-hashing on login. – Ismael Miguel Dec 04 '19 at 10:16
  • @IsmaelMiguel The old application would not be able to handle the new passwords, so once a password is upgraded that user would not be able to use the old application. – Anders Dec 04 '19 at 10:19
  • @Anders Oh, if compatibility with the old one must be kept, then yeah. Forcing the read-only on the old table and forcing users to use the new one for the increased security is a good idea. – Ismael Miguel Dec 04 '19 at 10:35
24

Is there a secure way to add salt (and pepper) to our authentication database while maintaining the old application's ability to authenticate users?

Yes, this can be done. Below are high-level implementation instructions. The basic technique is to hash all the passwords, then MITM the connection between the client and the legacy server in order to replace the unhashed password with a hashed password. Note that you'll need to come up with a roll-out plan; blindly running step 1 in production will break everything.

Step 1: Salt all existing passwords, then store the salt somewhere. Overwrite the password field of the legacy database with the salted password.

Step 2: Create a shim. The shim will accept identical parameters as the legacy APIs.
So, if the Legacy API is implemented as:

LegacyDoStuff(username,password,argument)
{
    if(!VerifyCredentials(username,password)) return AuthenticationError();
    result = DoStuff(argument);
    return result;
}

The new API is implemented as:

NewDoStuff(username,password,argument)
{
    hashedpassword = DoHash(password+getSalt(username))
    return LegacyDoStuff(username,hashedpassword,argument);
}

Step 3: Point the legacy client at the shim, instead of the main server (or equivalently, move the legacy server to new IP/DNS, then put the shim at the old IP/DNS).

This approach does allow you to treat the internals of the legacy code as a black box, but it requires you to be aware of the public surface area of the legacy code, since your shim will need to send requests/responses between the client and the legacy server.

This approach, unlike the approaches described in other answers, completely avoids storing the old password. However, this approach is much more difficult to do and is far more likely to introduce bugs.

Bergi
  • 277
  • 2
  • 10
Brian
  • 932
  • 5
  • 17
  • 1
    How does step #1 work if the current database has hashes of the original passwords, and not the original passwords themselves? There's no way to generate `hash(existingPassword + salt)` without knowing existingPassword. – Sawyer Knoblich Dec 03 '19 at 02:08
  • 2
    If it's old quick hashing function they could likely crack them with not much effort. Alternatively they could require all users to re-set their passwords. – Dracs Dec 03 '19 at 02:50
  • 2
    From a purely technical standpoint I love this idea. And yes, indeed it does fulfill basically all the requirements we have. And still I doubt that my boss would let us ever do this when we explain to him that we want to MITM our own system... Really great answer though imho! – Ben Dec 03 '19 at 07:40
  • 13
    @Ben This is a really clever idea, indeed. If you call it "proxy" instead of "MITM" maybe your boss would be more inclined to agree? – Anders Dec 03 '19 at 10:35
  • 9
    @SawyerKnoblich Assuming the old hash function was `Hash(password)`, then the new function can be `pbkdf2(sha1, Hash(password), salt, 1000*1000)` - ie make the final hashing function be a two stage function. – Martin Bonner supports Monica Dec 03 '19 at 14:17
  • I agree with Anders that calling it a proxy (or more precisely, a reverse proxy) is a better way to present it to management. I intentionally used the term MITM because I felt like the security.SE audience would be faster to understand the approach if I explained it using that term. All that being said, you could also just call it a shim. The fact that the shim is implemented via a reverse proxy is an implementation detail (and one that you could possibly avoid outright, if the legacy code can be used as a library rather than a web application). – Brian Dec 03 '19 at 14:38
  • Note that the old password is _hashed but unsalted_, so I think the new password hash actually stored in the DB would need to be `OldHash(NewHash($salt + OldHash($password)))` - the innermost `OldHash($password)` being the data previously stored, and the outermost `OldHash(...)` call being what the unmodified `VerifyCredentials` will calculate. Or have I misunderstood something? – IMSoP Dec 03 '19 at 19:41
  • 1
    Sorry, I don't get this. What exactly do you mean by "*Legacy API*"? The API of the database to the legacy application? The legacy application's own API/frontend (could be user-facing HTML, could be nothing at all in a desktop application)? Something else? If I understood the OP correctly, then both the `LegacyDoStuff` function and its caller would be unmodifiable. – Bergi Dec 04 '19 at 01:41
  • 1
    @Bergi I understood it to mean "somewhere between the user and the old application". For a web application, the shim would accept the input from the login form, manipulate the password value, and post it on to the real application. – IMSoP Dec 04 '19 at 06:24
  • @IMSoP Ah, that would make sense, thanks! – Bergi Dec 04 '19 at 13:32
11

Is there a secure way to add salt (and pepper) to our authentication database while maintaining the old application's ability to authenticate users?

No. The reason for salting and hashing passwords is so that if the user database is hacked/leaked/compromised, the users' passwords are not accessible to the attacker (see What is the point of hashing passwords?). In the solution that you describe, the users' passwords are still stored in the user database, in an adjacent column, in plaintext. This completely defeats the purpose of salting and hashing passwords.

mti2935
  • 19,868
  • 2
  • 45
  • 64
  • Meaning that as we only hash to make a potential databreach less impactful we are simply opening ourselves up to a way bigger world of hurt by now also having the data accessible over the open internet instead of internally only, making the chance for such a breach to happen in the first place just way more likely, correct? I don't see how this is anything but a massive security disaster about to happen right now... – Ben Dec 02 '19 at 11:56
  • 9
    I agree. I think your organization needs to seriously think about updating or replacing the legacy application such that it follows modern security standards. If that is not possible in the near future, then you might want to consider using two separate databases (one for the internal legacy application, with the passwords stored in plaintext; and another separate database for the new public-facing application with the passwords salted and hashed), and running a process that keeps the data synced between the two databases. – mti2935 Dec 02 '19 at 12:06
  • 8
    The op does not say there is any plaintext password. – Frank Dec 02 '19 at 23:52
  • 8
    @Frank: Unsalted with weak hash is, for all intents and purposes, plaintext. – R.. GitHub STOP HELPING ICE Dec 03 '19 at 01:06
  • @R.. agreed but it changes the parameters of the problem. We know now that the problem can be solved, because a password reset does not store plaintext. See my answer below for solution – Frank Dec 03 '19 at 08:00
  • What about migrating from `hash(plain)` to `hash(hash(plain), salt)`? Just out of interest, as this would break the legacy application. – Num Lock Dec 03 '19 at 09:43
  • @NumLock Yep, that would work, which is the answer below. Not sure why is this marked as the accepted answer as there are several ways of implementing this. – Frank Dec 03 '19 at 10:10
  • It's true that you can't get rid of the problem while the old app is running, but it sounds like the plan is to bring up the new app, run them in parallel until feature parity is reached, and then discontinue the new app. In that case, the answer might be better phrased as "yes, eventually." If I'm wrong and the plan is to run both of them indefinitely, then "no" is absolutely the correct answer. – GrandOpener Dec 03 '19 at 18:40
7

Clearly the current table needs to remain so the old application can work.

Maybe add a new table with salted+peppered hashes for the new application to use, then:

  • Make the old table inaccessible to the new application!
  • Update both tables when updating a user's password
  • Require all users of the new system to update/reset their password
  • The new table can store a copy of the old hash initially, then update it to be properly hashed when the user updates their password.

This assumes that the password control system is separate from either of the content systems.

This means that any vulnerability in the new system can't access the old hashes (once migrated). The database could have a vulnerability that allows the new system's database credentials have access to a table it shouldn't, but that's much less likely than your new application having a vulnerability.

Douglas Leeder
  • 1,939
  • 14
  • 9
  • From a technological standpoint, when the table is inaccessible for the new application, is there any way I could leak the table content without someone getting access to my internal network? – Ben Dec 02 '19 at 11:50
  • Only if the database has a vulnerability that allows the new system's account to access tables it shouldn't. – Douglas Leeder Dec 02 '19 at 11:54
  • You could use a completely separate database I suppose, but that makes the password update program more complicated. – Douglas Leeder Dec 02 '19 at 11:55
  • Okay. So we could at least keep the potential vulnerability level on a comparably similar level it already was before the new application (which is probably way too high already) by restricting access of everything new to the security vulnerability. I guess that is something. – Ben Dec 02 '19 at 11:58
5
  1. Add two new columns, a salt column, and a new hash column, initially null.
  2. When an authentication request comes in, check the salt field.
  3. If the field has a value then there is a new-style hash that has pepper and salt added. Handle accordingly.
  4. If not, then this is an old-style hash. Verify using the old mechanism. Assuming it succeeds, you now have the plaintext password from the original request; generate and store a salt and you now have everything needed to compute and store a new-style hash. The legacy system won't know to do any of this, so only the new system can do it. (It would have been better if neither system had to deal with it but could delegate the job to the database itself. The new system can, the old system can't.)

Eventually either everyone has logged in at least once and had their hashes transparently updated (there are no NULL salts left in the table), or the only ones left are those who never log in at all.

Putting the unsalted and salted hashes right next to each other in the table just illustrates the fact that for as long as the legacy system continues to be used, the use of best practices in the new system will continue to be irrelevant from the standpoint of security.

Hayley
  • 59
  • 2
  • why you need a new hash column? – kevinSpaceyIsKeyserSöze Dec 03 '19 at 09:53
  • @kevinSpaceyIsKeyserSöze We need to keep the unsalted hash to keep the old app running, and a salted hash (which will be different) for the new app to run securely. – GrandOpener Dec 03 '19 at 18:41
  • While this is one way to generate the new salted hashes, another is simply to double-hash all the existing entries. Neither is particularly relevant to the main part of this question, which is how to keep the legacy app working; the only part of this answer that addresses that is "create a new column and leave the old one". – IMSoP Dec 03 '19 at 19:44
2

Yes this is possible.

In the new application, have a hash-of-hash double hash algorithm eg:step1 : hash the salted password , step2 : hash that hash (no salt)

Approach #1:

Develop a simple standalone app that takes a password and generates the salted hash of a user password and displays to screen or silently to clipboard.

Ask all internal users to reset their password in the old application to the output of the standalone app, and from now on when logging in use the process of entering the password into the standalone app to generate the password hash that will be entered into the login screen. The output could be generated to clipboard to avoid visual clues.

Approach #2

If you can somehow use the db, do the hash of hash in the database. Make step 2 the salted hash and only store that.

Edit to clarify: example: if using a DB that supports custom operators then declare a custom equality operator on a column storing the newsaltedhash(oldhash) as a custom type. When the app runs a query for row with matching username and oldhash, the custom operator will execute newsaltedhash on the incoming hash and do the comparison. Password reset is handled by insert triggers.

Summary: In both scenarios your apps function as originally designed and the stored password hash is a hash+salt double hash.(Pepper optionally added in either implementation)

Frank
  • 159
  • 5
  • While this works, it's replacing a bad system with a better, but still a bit hackish and very non-standard one. IMHO it's better to do just a clean break, and use standardized components like bcrypt rather than trying to roll your own, yet again. – Steve Sether Dec 03 '19 at 16:35
  • @SteveSether I would wholeheartedly agree. But that does run contrary to the spirit of the question. The whole premise of the question is strange. More importantly is the issue of why an internal line-of-business authentication database will be driving a public facing one, and the risk of exposing internet DDoS attacks on internal auth dbs. Yes. a clean break is essential, but the OP has not made clear why s/he is in this very unorthodox situation. – Frank Dec 03 '19 at 16:37
  • Note to others: "using 'standard' components like bcrypt" should not be taken to mean "use bcrypt". For example, in .NET there is no audited safe implementation of bcrypt AFAIK, and it is better to use the library hash functions from a sec pov. – Frank Dec 03 '19 at 17:02
  • Indeed, I don't think you have to use bcrypt. But I also don't think that "fully audited" is absolutely necessary either. FWIW there's a SO question about bcrypt on .net https://stackoverflow.com/questions/873403/net-implementation-of-bcrypt – Steve Sether Dec 03 '19 at 17:06
  • 1
    This sounds like a variant of [Brian's answer](https://security.stackexchange.com/a/222180/51961), but using the users to do some of the work. Note that you can't just have a tool to generate "a salted hash", it needs to generate _the correct salted hash using the stored salt for that user_. Also, you need people to be able to log into the new system anyway, so it's probably easier to bulk generate hashes of the form `NewHashFunction($salt + $oldHashValue)`; you can then bulk update the old table with `OldHashFunction($newHashValue)` rather than making users reset their passwords. – IMSoP Dec 03 '19 at 19:54
  • @IMSoP no Brian's answer is different, he is talking about keeping the original hashing algorithm but limiting the exposure through timeouts. Depending on the db functionality the above could work perfectly with no additional tool, it just depends on if the hash equality can be intercepted at the level of db or not. – Frank Dec 03 '19 at 19:58
  • @Frank Huh? Where do you see anything about "timeouts" in the answer I linked? It proposes the same basic idea as this: instead of `OldHash($password)` you store `OldHash($newPasswordHash)`. In Brian's version, a shim or reverse proxy generates the correct value for `$newPasswordHash` and passes it through automatically; in yours, an interactive tool does so and the user enters it manually. – IMSoP Dec 03 '19 at 20:02
  • @IMSoP oh right yes I was looking at a different answer. But still I would say the architecture / concept is different – Frank Dec 03 '19 at 22:53
  • @IMSoP eg just guessing but if this were postgres or oracle could you not do this using a custom operator? – Frank Dec 03 '19 at 23:53
  • 1
    @Frank No, the database could not do this for you, it sits at the wrong end of the legacy hashing function. – Bergi Dec 04 '19 at 01:47
  • @Frank Perhaps you could make clearer what value you think would be stored in the database, and how it would be checked by the legacy application. As far as I can see, the only way is to store `OldHash($newPasswordHash)`, because that's what the old application will calculate. Which as I say makes this a variant of the other answer - which isn't a bad thing as such, just an observation which could be used to expand it perhaps. – IMSoP Dec 04 '19 at 05:44
  • @Bergi does that not depend on how the app is implemented? Eg : if app is doing 1. Hash password 2. Ask for count of rows where username = X and passwordhash = y then if it were postgres could you do a custom operator that does newpasswordhash(incoming hash) to compare with what would be in the dB , ie newpasswordhash (oldhash) – Frank Dec 04 '19 at 06:56
  • @IMSoP edited with example – Frank Dec 04 '19 at 07:15
  • @Frank Ah, OK, I assumed that part was related to the first, but it's actually a different, and rather ingenious, idea. It will only work if the old application puts the hash in the where clause of a query, though, which isn't a particularly sensible design (it's not even possible once you start salting your hashes) but is fairly common. – IMSoP Dec 04 '19 at 08:09
  • @IMSoP That's right, it does make assumptions about the the way the code is designed, but I think on the whole the concept I was trying to convey was just this intuition that newhash(oldhash) hash-of-hash opens up some different approaches. Gut feel tells me there're probably other clever methods involving stored procs/insertable views etc., .NET t-sql modules and the like. If the app is just loading the passwordhash in as soon as the username is entered the db side approach wont work. – Frank Dec 04 '19 at 08:12
  • @Frank Yes, I think with the example it's an interesting suggestion. Before that example, it was too vague to be useful, especially because `newhash(oldhash)` is often used as a migration path anyway (because you can migrate all the existing data without knowing the plaintext passwords). – IMSoP Dec 04 '19 at 08:17
  • @IMSoP Agreed. I realise now that on Stackoverflow the answers have to be precise, rather than guidelines. I think if OP absolutely must go this way, he should use a db that supports custom operators instead of MySQL, as this method actually directly and cleanly solves his case. (well would, if we knew for sure the app was checking against hash) – Frank Dec 04 '19 at 08:19
1

This answer is inspired by Brian's answer and Frank's Approach #1 which both essentially use the new salted hash as the password for the old application. Since that requires an application to retrieve the salt from the database to generate the hash, it would be simpler and more secure to have that application authenticate the user, and use a completely separate random token as the password for the old system.

Step 1: Choose a modern password hashing solution

Pick a well-supported password hashing library for your language of choice, which should handle generating secure salt for you. It will probably output a single string containing the hash, salt, and other parameters needed to verify the password, allowing you to gradually roll out a stronger algorithm in future. For instance, in the case of PHP, use the built-in password hashing functions.

Create a new column in the database to store this single string. One way to populate this initially is to calculate NewHashFunction(OldStoredHash), and then run NewVerifyFunction(NewStoredHash, OldHashFunction(UserEnteredPassword)). There are plenty of existing discussions of this, and it doesn't make a difference to the rest of this answer.

Step 2: Blank the old password column

Permanently wipe all the old insecure hashes, but don't drop the column. At this point, nobody can log into the old application, so we need to fix that...

Step 3: Build a tool to log into the old application

Build a page in your new application (where the user is already authenticated) or a standalone page (which authenticates the user using the new password library) which does the following:

  • Generates a long random password
  • Stores the unsalted hash of this random password into the old password column for the authenticated user
  • Either displays the password to the user to enter into the old application, or directly submits it to log the user in automatically

Note that this random password doesn't need to be stored anywhere, because if the user wants to log in again, they can simply generate a new one.

Step 4: Make it more secure by timing out the tokens

The random passwords are bound to be much stronger than most users would have set unless using a password manager, but they will still be stored unsalted, and probably using a fast hash. There is also a risk that the password will be copied somewhere when shown to the user. We can make both attacks much harder by only having the random passwords valid for a short period of time.

Add an extra column to the DB for "random password expiry date". This can be extremely short if logging in automatically, and a few minutes if the user has to enter the password themselves. A scheduled job should then run once a minute, and blank the old password field for all rows which have expired.

IMSoP
  • 3,780
  • 1
  • 15
  • 19
  • Not only is this an excellent answer, it should be used as a good example of how seemingly insurmountable problems often have unobvious solutions – Frank Dec 04 '19 at 22:48
-1

As pat of the upgrade, add a column to the user table indicating whether or not the account has been upgraded. If it hasn't then use the old code to generate and check the hash. If that's OK then generate the salt and the new hash and update the user record to the new hash, salt, and set the Upgraded flag.

If the Upgraded flag is set on login, the use the new code to read the salt, make the hash and check that.

simon at rcl
  • 109
  • 4
  • 1
    While this obviously works for upgrading the current solution towards a new one this will still break the old login functionality as soon as an account is marked as `Upgraded` correct? The importance is that an account can still be accessed using the old login method even after changes have been made. Both applications have to run in parallel to support old legacy clients where the new application can't be run (no modern OS e.g.) – Ben Dec 02 '19 at 11:30
  • 1
    If I assume that the old site is separate from new, then don't upgrade the old - especially if you don't want to change it. If they share a common database, then instead of an Upgrade flag, add an AccountVersion flag and set the existing accounts to 1. Added new accounts would be version 2 and have a salt and hash in the new way. Any competent developer should be able to do this, or variations on it. – simon at rcl Dec 02 '19 at 11:42
  • The problem is that any users should be enabled to login both using the old legacy application and the new application. There is no "either or", it's always "both". Even new users added shall be able to login both ways. In my thoughts that means the only way is to store both the unsalted and the salted hash - and that defeats the purpose completely and I can directly just scrap the solution. If we were simply writing a new application you could trust me that I would never setup on a 15y old database and make something new... It's sadly the constraints I am left with. – Ben Dec 02 '19 at 11:45
  • How does the user specify which way they want to logon? This is a very unclear situation: you are in control of the hashing process not the user; you need to ensure that the hashing process matches that of the stored hash not the user. You are in control. These are fairly elementary ideas of how to pick the right process and/or convert from one to the other. If this doesn't give a direction for a solution then I have completely failed to understand the problem - sorry! – simon at rcl Dec 02 '19 at 11:51
  • It is specified by the application they use. App A (the legacy app) can only work with unsalted hashes. We are not in control here - the application can't be changed anymore. App B (the new app) can work with whatever - we are in control here as rightfully pointed out. Still a user `Peter` shall be able to be authenticated by both App A and App B to access the same data. I don't think you missunderstood anything - I'm afraid, the situation is just entirely unreasonable from the get go... – Ben Dec 02 '19 at 11:53
  • 1
    That sounds like you should go with version 1 and 2 accounts and hash accordingly. This would require no change to the old app as it would just be doing v1 hashes anyway. – simon at rcl Dec 02 '19 at 11:58
  • Yes, that goes in the direction of Douglass Leeder's answer above - making two separate accounts that are somehow linked and additional restricting access to the relevant parts in some way. I guess this is the lesser evil we have to live with. – Ben Dec 02 '19 at 12:00
  • Actually it doesn't (necessarily). A v1 account would be hashed by v1 on the old and the new. A v2 account would be hashed by v2 in the new and would always fail to login on the old. People using old and new would have t setup accounts on the old system before the new (if they haven't already got them). – simon at rcl Dec 02 '19 at 12:04
-4

Storing salted passwords is soooooooo 1995. Therefore you should (if nothing better is possible) at least store a salted hash with many iterations of the hash. This is actually less troublesome than you think.

One concern about this scheme is that you don't know how many iterations are enough, and as computers get faster, you will eventually need more. Over-hashing too much is very expensive, though... especially for a large user database. How to solve this?

The commonly applied solution to that question is at the same time the solution to your primary problem.

In order to account for growing CPU (or GPU, really) power, you add another column with the number of iterations. Every now and then, you add one iteration to a user (for example, 5% random chance at every login). This is very little work because you only need to do one extra hash. Update both the count, and the stored hash.

Now, one needs to realize that a hashed password is still only a password. It's kinda "unreadable", but it is still only a password, nothing else. Nobody prevents you from salting that and hashing it again. Even hash it many times. No harm done.

So, whenever your authentication layer pulls a row from the database where the hash count is zero, it knows that this is an "old" entry where the password has not been salted (it has zero extra hash rounds). So... salt it, hash it a thousand times, and store 1000 for the hash count as well as the salted hash. Done.

The login procedure would thus be something like (in pseudocode):

hash_entered = H(password_entered);

[hash, count] = sql_query(SELECT WHERE username=blah);

if(count == 0)
{
    login_success = compare(hash_entered, hash);
    for(i = 0; i < 1000; ++i) hash = H(hash);
    sql_query(UPDATE hash = hash, count = 1000);
    return login_success;
}
else
{
    for(i = 0; i < count; ++i) hash_entered = H(hash_entered);
    login_success = compare(hash_entered, hash);

    if(random(100) <= 5)
    {
        hash = H(hash);
        sql_query(UPDATE hash = hash, count = count + 1);
    }
    return login_success;
}
Damon
  • 5,001
  • 1
  • 19
  • 26
  • 2
    While some of this answer is reasonable general advice, it doesn't address the specific situation in this question, which is how to keep the old application working. – IMSoP Dec 03 '19 at 19:57
  • 1
    What's better than a salted password? I don't think you mention that. – schroeder Dec 04 '19 at 09:10