Sunday, April 15, 2018

What will they think?


I sometimes wonder, when some alien archaeologist comes upon the ruins of our species, what will they decide was the deadly mistake we made as a civilization?

Nuclear War? AI? Asteroid? Disease?  Global Warming?

Nope, I think I've figured it out.  An ad for the following just appeared in my browser, and suddenly, it's all clear to me:



I think I even know who Satan is, and how he won his final battle with God.



Knowing that the end is near, clearly I need to go out and max out my credit cards in a final act of debauchery.






OK, I trust that by now you know I'm joking.  Instead of debauchery, I would max out my credit cards on a bitcoin mining rig.  ☺



OK, OK.  While it looks crazy at first, the SALT (https://saltlending.com/) business model is only mildly insane.  I think the reference to blockchain is mostly name dropping.  Their business model seems to be simply providing cash loans using bitcoin, etherium, and perhaps other "crypto-currency", as collateral.




Still, you have to admit, we live in interesting times...





Tuesday, April 10, 2018

Maybe they're not as stupid as we think ...

Earlier today, the infosec corner of Twitter noticed what could be another significant player who seemed like they were doing everything wrong.

Specifically, when calling customer service, Fidelity Investments asks you to type in your password using your phone. Folks quickly concluded that Fidelity is either storing your password in plain-text or at least storing a hash of the phone-digits equivalent of your password.  Both are really bad, since if Fidelity's password database ever get's compromised either would allow a bad actor to easily compromise your account.



Pouring fuel on the fire is that, to date, Fidelity has not responded with what they actually do. No surprise, the piling on which followed was predictable and perhaps even well deserved.

As I read the Twitter thread however, a gnawing thought occurred to me .... while it's great fun to feel superior to others, what if the security folks at Fidelity aren't as stupid as we think?  What if they're not doing either of those bad things?  I mean it's a long shot, but it could happen. 😀

Here's what I was wondering: Is it possible that by knowing the phone-digits which map to an otherwise well hashed and salted password, they could still authenticate your phone call?  Put another way, if I know the phone digits that map to your password, and the hash for your password, can I use the phone digits to dramatically reduce the search space necessary to find the password which generated the hash? Can I do this in whatever passes for real time while waiting on hold for customer support?

Hmm, I wonder.

Maybe now's a good time to list my cryptography credentials:

In other words, I have none.  This could be a crazy, and totally impractical idea, but I'm too ignorant to know.  So here goes.

A good cryptographer would do the math and come up with a powerful argument for whether this is a practical explanation of what Fidelity might be doing.  However, I suck at probability and combinatoral analysis, so instead I threw together a quick and dirty program to try it out.

It takes a password, converts it to its phone digit equivalent, and then use the phone digits to optimize a dictionary attack.  The optimization is simply to not try to hash words which didn't map to  the phone number.  The idea is that checking whether a possible password  maps to a known set of phone digits is way cheaper than using a good hash function.  Especially if Fidelity is following best practice, and using a hash function designed to be slow (such as bcrypt or Argon2.) Just for grins, I also tried a naive brute force attack as well.

The program is written in incredibly bad Python, with absolutely no attempt at optimization (beyond using the phone digits to avoid wasted hashing.)  So the elapsed time is relatively useless for this analysis, the only metric I collected was to count the total times I ended up calculating a hash versus the total number of guesses I made.

The numbers were surprising to me.   Unless I'm making a mistake somewhere, using the phone digits is a remarkably effective way to narrow the search space.  In each of the cases below, the password was cracked both by a dictionary attack and a naive brute force attack.  In all cases I was able to "crack" the password.

The '# of phone digit matches' is how many times the phone digits I knew map'd to a potential password.  This is also the number of times I had to calculate the hash of a potential password to find the actual password. The '# of total words checked' is how many words in total I considered.  I used the rockyou.txt password list for the dictionary attack.

Password: abcde
Dictionary Attack
# of phone digit matches:  1
# of total words checked: 1511  (note: 'abcde' is the 1511'th word in the file)
Brute Force
# of phone digit matches: 885
# of total words checked: 1011525

Password: !DRO!
Dictionary Attack
# of phone digit matches: 1
# of total words checked: 14341288
Brute Force
# of phone digit matches: 1752
# of total words checked: 2418577084

Password: abcd
Dictionary Attack
# of phone digit matches: 2
# of total words checked: 35544
Brute Force
# of phone digit matches: 351
# of total words checked: 1050160,



So at first glance it looks like using the phone digits dramatically reduces the search space when trying to crack a password.

Is that enough to allow Fidelity to retain only properly hashed passwords, but still authenticate callers who provide the phone digits for their password?

I honestly don't have a clue.  Throw enough money at fast hardware, hire folks who understand how to do this stuff quickly, factor in that customers sit on the phone for awhile after they punch in their password and it might be.  Finally, keep in mind, it's not the end of the world if they can't authenticate a customer before they get to an operator.  In that case, the operator  might expend another minute authenticating them by hand, but there was no harm in trying before the call is answered.

Here's the program I used.  Please understand that it's just a simple POC.  I'm sure there are bugs, and unintended limitations.  For example, I don't know what complexity rules Fidelity enforces, or even what constitutes a special character for them.  I just winged it, with the sole intention to get a feeling for the efficiencies possible by using the phone digit version of the password.


 
#!/usr/bin/python

# Question: if a user types in a password using the digit keys on a
# phone (which is ambigious since each key maps to several letters,
# how much easier is it to brute force the password with those "hints"

# To install bcrypt
# sudo apt-get install build-essential libffi-dev python-dev
# pip install bcrypt

import bcrypt

# for each phone digit, list the potential characters
K = {}

K[0] = ['0']
K[1] = ['1']
K[2] = ['2','a','A','b','B','c','C']
K[3] = ['3','d','D','e','E','f','F']
K[4] = ['4','g','G','h','H','i','I']
K[5] = ['5','j','J','k','K','l','L']
K[6] = ['6','m','M','n','N','o','O']
K[7] = ['7','p','P','q','Q','r','R','s','S']
K[8] = ['8','t','T','u','U','v','V']
K[9] = ['9','w','W','x','X','y','Y','z','Z']
K[10] = ['!','@','#','$','%','^','&','*','(',')','-','_','=','+','?','<','>']

# test whether a word maps to a set of phone digits
def inDigits(testWord, testDigits):
    if len(testWord) != len(testDigits):
        return False

    for wordIndex in range (0,len(testWord)):
        if testDigits[wordIndex] == '*':  # since can't use this one 'digit' to index into K
            kIndex = 10
        else:
            kIndex = int(testDigits[wordIndex])
        if testWord[wordIndex] not in K[kIndex]:  # char doesn't map to corresponding phone-digit. We're done
            return False

    return True
    # done with inDigits
        
password = raw_input ("Provide the password: ")

hash = bcrypt.hashpw(password, bcrypt.gensalt())

digits = ''
for char in password:
    if char in K[0]: digits = digits + '0'
    if char in K[1]: digits = digits + '1'
    if char in K[2]: digits = digits + '2'
    if char in K[3]: digits = digits + '3'
    if char in K[4]: digits = digits + '4'
    if char in K[5]: digits = digits + '5'
    if char in K[6]: digits = digits + '6'
    if char in K[7]: digits = digits + '7'
    if char in K[8]: digits = digits + '8'
    if char in K[9]: digits = digits + '9'
    if char in K[10]: digits = digits + '*'
    
print ("\"phone\" digits: " + digits)

fileName = raw_input("Dictionary File: ")
if len(fileName) == 0:
    fileName = '/usr/share/wordlists/rockyou.txt'
    print ("using " + fileName)

file = open(fileName, "r")

totalGuesses = 0
digitsMatchCount = 0
notFound = True
for guess in file.readlines():
    guess = guess.strip()
    totalGuesses += 1
    if inDigits (guess, digits):
        digitsMatchCount += 1
        if bcrypt.checkpw(guess, hash):
            print ("found it, password is: " + guess)
            print ("totalGuesses = "+str(totalGuesses)+", digitsMatchCount = "+str(digitsMatchCount))
            notFound = False
            continue

if notFound:
    print ("password not found")
    print ("totalGuesses = "+str(totalGuesses)+", digitMatchCount = "+str(digitsMatchCount))

print ("starting brute force")

charset = ['a','A','b','B','c','C','d','D','e','E','f','F','g','G','h','H','i','I','j','J','k','K','l','L','m','M','n','N','o','O','p','P','q','Q','r','R','s','S','t','T','u','U','v','V','w','W','x','X','y','Y','z','Z','0','1','2','3','4','5','6','7','8','9','!','@','#','$','%','^','&','*','(',')','-','_','=','+','?','<','>','']

totalBruteGuesses = 0
bruteDigitsMatchCount = 0

# Will only work for up to 5 character passwords.  I can't imagine a dumber way to do this (but I'll keep trying.)
for c1 in charset:
    for c2 in charset:
        for c3 in charset:
            for c4 in charset:
                for c5 in charset:
                    guess = c1 + c2 + c3 + c4 + c5
                    totalBruteGuesses += 1
                    if inDigits (guess, digits):
                        bruteDigitsMatchCount += 1
                        if bcrypt.checkpw(guess, hash):
                            print ("found it via brute force, password is: " + guess)
                            print ("totalBruteGuesses = "+str(totalBruteGuesses)+", bruteDigitsMatchCount = "+str(bruteDigitsMatchCount))
                            exit()


print ("Brute force attack failed.")
print ("totalBruteGuesses = "+str(totalBruteGuesses)+", BruteDigitsMatchCount = "+str(bruteDigitsMatchCount))





Well that's it!  It sure would be nice if Fidelity came out and said what they do.  But they may feel that it's better to keep what they do hidden.

One last point:  If Fidelity does do something like this, the next question is how carefully do they protect the phone-digits their customers type into the customer-support system?  If the phone-digits and the hashed database were to both be compromised, it would be almost as bad as not hashing the passwords at all.

Any suggestions or comments would be welcome.


Update (4/10/2018): I received a first-hand report: "I auth'd to my personal account via phone and it was instantaneous, so I think we can completely rule that one out."  I agree, if authentication is instant, than Fidelity has clearly retained either the clear text password, or perhaps a hash of the phone-digits.  The problem with the latter is that the search space of phone-digits is so small, it can easily be brute-forced - allowing an attacker to obtain the phone-digits which correspond to your password.  They can then get at your account via phone, or if they have also stolen the hashes for your real passwords, use the technique I describe above to compromise your password.  Ouch!