One of the things that has always fascinated me is when you are watching some Hollywood movie and they talk about stuff like “rotating string cipher encryption with a strength of 228 bits”. Now this has always sounded both funny and interesting to me. Like what if you could convert data that is normally static into actual continuous rotating data? This morning it hit me, this is something we can actually accomplish with password hashes. We can rotate them continuously, without the user even noticing and hopefully make it harder for attackers to crack the hash. Which is the main goal of this “idea/solution” making it harder or hopefully impossible for attacker to crack the hash when they obtain it. Here is a PoC preview output, screenshot OTP and usage OTP don’t match because they were obtained at different times:
./hashrotate_poc.py S3cretP4ssword 545130 Current password hash: 224bbe14e25287f781eb94d57a49eb8e064f3857e2b369ea8f413ed22e6190e568e2107aaaa8ef60aa7db9413f930a7d6e715c83bd06243dd2f26cf86a9828b9 Login OK
./hashrotate_poc.py S3cretP4ssword 545130 Current password hash: 12abaded0aebd3269229ea53c85be4fe78d5dd876b4feac6b21bd87c4f0235fee01990e600f5664085e9139f108903446376a2f6fab1770c9add86ad4168fbea Login Failed
Like you can see we attempt to login with the same credentials both times and yet the second time this fails. If you are impatient then you’ve probably guessed that this is due to the added 2FA data in the login credentials and as usual you can jump straight to the code on my github. Now if you are curious how this works and how you could set this up in a real life setup keep on reading, do note that I haven’t tested this yet on a real life environment however. The setup is actually one of the important parts for this to work and actually slow down or hopefully prevent an attacker from successfully cracking the obtained hashes.
Now after you’ve read that you are probably thinking “shut up already with those ‘uncrackable’ claims” and yeah you are correct. It can probably be cracked, but hopefully we can settle for the second best which is (tremendously) slowing down the attacker.
For the remainder of this entry we will be focusing on further protecting the stored hashes and not on how to protect the in transit plain text password. This since obviously if you have full control of a server where the SSL is terminated you could just intercept all passwords in plain text. This can be solved with fancy challenge-response protocols or maybe (in browser) public-key cryptography. For now, let’s dig further into this ‘continuous changing hashes’ concept.
Let’s first look at a general login system and how it’s normally setup. For all images the arrows indicate the actual data flow:
With the above setup if you have an SQL injection or other kind of breach an attacker is able to retrieve the hashes and start cracking them. Of course you can slow down the attacker tremendously by hashing the password correctly. Good hashing includes for example a strong key derivation function (KDF) like bcrypt, scrypt or pbkdf2. In addition you usually want to have some kind of unique per user salt, a pepper (additional global hard coded salt) and the correct parameters for the KDF like for example enough iterations. A nice read on this subject is the latest entry on the matasano blog which you can find here.
So let’s say you want to take this one step further to hopefully prevent an attacker from obtaining the hashes you could for example setup the following, again arrows indicate the actual data flow:
With the above setup and having applied the previously mentioned measures, it becomes a lot harder to obtain the hashes. When an attacker breaches the normal application (frontend & application layer) he still has no access to the hashes. These are now only reachable from the authentication layer which of course has a minimal attack surface compared to the web application that the attacker breached. Still let’s assume for a moment that the attacker is also able to breach the authentication layer, he then has full access to the hashes and all the salts and peppers needed to successfully crack them offline. After all the stored hashes are at this moment just ‘static data’ sitting there for anyone to take them.
Which brings us to the title of this blog post, what if the hashes are continuously rotated on a value that the attacker doesn’t know? Let’s tackle the ‘continuous rotating’ challenge first, how would we do this? A couple of years back this would have been pretty improbable, but nowadays 2FA is heavily pushed towards end users by companies like Google. The normal process of 2FA is to add an additional layer but ONLY to the login process. So what if we add this additional 2FA layer to the hash as well? Which is exactly how you obtain rotating hashing, since in a way they are continuously salted with the OTP token. Which in my proof of concept code boils down to:
def otp_rotate_hash(): """ Imitates a separate process which mixes 2FA into the hash """ current_otp_token = str(GLOBAL_TOTP.now()) return password_hash(USER_PASSWORD, current_otp_token)
Which, although the variable names are a bit confusing, works on top of the already hashed user password. To test out the PoC you need to install the Google authenticator app on your phone or create your own python script to generate the tokens. If you do use the Google authenticator app you need to setup the account with the option “enter provided key” which looks like this:
You’re probably thinking “but ermm dude, the original hashes are still available”. Yeah! that’s exactly why the surrounding setup is so important, let’s have a look at it:
The big thing to note in the above image is not so much the additional rotation layer, but the fact that it’s a one way data flow, which I do admit is a bit cumbersome. For a one way guarantee you could even place a data diode in between the credentials database and the rotation layer. Basically you’d need to pre-load all the user hashes in the rotation layer. After that you’d be feeding the credentials database with the modified mixed in 2FA hashes.
How does this slow down an attacker you may ask? Well the whole idea is based on two things:
- The attacker is not able to get the secret which generates the one time tokens
- The attacker does not know which OTP was used to generate the hash
So the first item depends on getting the setup right which if implemented with a data diode is pretty solid. The second item is a bit tricky, in principle the token used to generate the ‘continuous rotating hash’ is never stored so the attacker is not able to retrieve it, even if he has obtained the OTP-secret. However a lot of websites include a ‘your last login was […]’ enabling an attacker to guess which OTP token was used to generate the hash. Of couse from a practical standpoint the attacker could just brute force the OTP range since the Google implementation is only six digits. This however means, that for every hash in the database the attacker has to perform the same brute force therefore greatly reducing the speed at which he can crack hashes.
You might be wondering how practical this really is? Well I don’t really have an answer for that, but I do like the idea of continuously changing hashes in a database. Unless I’ve overlooked something it should slow down an attacker significantly, specially if combined with the regular advice on how to properly hash passwords. Probably this exact implementation or idea doesn’t scale to real world websites, but I hope it inspires someone to create a similar system or attempt to implement this one.