Java Key Store (JKS) format is weak and insecure (CVE-2017-10356)

While preparing my talk for the marvelous BSides Zurich I noticed again how nearly nobody on the Internet warns you that Java’s JKS file format is weak and insecure. While users only need to use very strong passwords and keep the Key Store file secret to be on the safe side (for now!), I think it is important to tell people when a technology is weak. People should stop using JKS now, as I predict a very long phase-out period. JKS was around and the default since Java had its first Key Store. Your security relies on a single SHA-1 calculation here.

Please note that I’m not talking about any other Key Store type (BKS, PKCS#12, etc.), but see the cryptosense website for articles about them.

I don’t want to go into the details “why” JKS is insecure, you can read all about it here:

I wrote an email to the Oracle security team, as I think assigning a CVE number would help people to refer to this issue and raise awareness for developers. My original email sent on September, 18 2017:

I would like to ask Oracle to assign a CVE Number for Java’s weak
encryption in JKS files for secure storage of private keys (Java Key
Store files). JKS uses a weak encryption scheme based on SHA1.

I think it is important to raise awareness that JKS is weak by assigning
a CVE number, even when it is going to be replaced in Java 1.9 with PKCS#12.

The details of the weakness are published on the following URLs:

– As an article in the POC||GTFO 0x15 magazine, I attached it to this
email, the full magazine can also be found on
https://www.alchemistowl.org/pocorgtfo/pocorgtfo15.pdf
– https://cryptosense.com/mighty-aphrodite-dark-secrets-of-the-java-keystore/
– https://github.com/floyd-fuh/JKS-private-key-cracker-hashcat

As the article states, no documentation anywhere in the Java world
mentions that JKS is a weak storage format. I would like to change this,
raise awareness and a CVE assignment would help people refer to this issue.

The timeline so far:

  • September, 18 2017: Notified Oracle security team via email
  • September, 18 2017: Generic response that my email was forwarded to the Oracle team that investigates these issues
  • September, 20 2017: Oracle assigned a tracking number (S0918336)
  • September, 25 2017: Automated email status report: Under investigation / Being fixed in main codeline
  • October, 10 2017: Requested an update and asked if they could assign a CVE number
  • October, 11 2017: Response, they are still investigating.
  • October, 13 2017: Oracle writes “We have confirmed the issue and will be addressing it in a future release”. In an automated email I get Oracle states “The following issue reported by you is fixed in the upcoming Critical Patch Update, due to be released at 1:00 PM, U.S. Pacific Time, on October 17, 2017.”.
  • October 17, 2017: Oracle assigned a CVE in their Oracle Critical Patch Update Advisory – October 2017: CVE-2017-10356. The guys from Cryptosense got credited too it seems. However, the documentation of Oracle so far didn’t change anywhere I could see it.
  • November 16, 2017: I asked again to clarify what the countermeasures are and what they are planning to do with JKS. They seem to be mixing my CVE and the JKS issues with other issue in other Key Store types.
  • November 17, 2017: Oracle replied (again, mixing-in issues of other Key Store types): “In JDK 9 the default keystore format is PKCS#11 which doesn’t have the limits of the JKS format — and we’ve put in some migration capability also. For all versions we have increased the iteration counts [sic!] used significantly so that even though the algorithms are weak, a brute-force search will take a lot longer. For older versions we will be backporting the missing bits of PKCS#11 so that it can be used as the keystore type.”. That was the good part of the answer, even though JKS has no iteration count. The second part where I asked if they could add some links to their Critical Path Update Advisory was: “In order to prevent undue risks to our customers, Oracle will not provide additional information about the specifics of vulnerabilities beyond what is provided in the Critical Patch Update (or Security Alert) advisory and pre-release note, the pre-installation notes, the readme files, and FAQs.”.

That’s it for me for now. I’m too tired to start arguing about keeping technical details secret. So basically I have to hope that everyone finds this blog posts when searching for CVE-2017-10356.

Cracking Java’s weak encryption – Nail in the JKS coffin

POC||GTFO journal edition 0x15 came out a while ago and I’m happy to have contributed the article “Nail in the JKS coffin”. You should really read the article, I’m not going to repeat myself here. I’ve also made the code available on my “JKS private key cracker hashcat” github repository.

For those who really need a TL;DR, the developed cracking technique relies on three main issues with the JKS format:

  1. Due the unusual design of JKS the key store password can be ignored and the private key password cracked directly.
  2. By exploiting a weakness of the Password Based Encryption scheme for the private key in JKS described by cryptosense, the effort to try a password is minimal (one SHA-1 calculation).
  3. As public keys are not encrypted in the JKS file format, we can determine the algorithm and key size of the public key to know the PKCS#8 encoded fingerprint we have to expect in step 2.

For a practical TL;DR, see the github repository on how JksPrivkPrepare.jar can be used together with the hashcat password cracking tool to crack passwords.

Not affected of the described issues are other key store file formats such as JCEKS, PKCS12 or BKS. It is recommended to use the PKCS12 format to store private keys and to store the files in a secure location. For example it is recommended to store Android app release JKS files somewhere else than a repository such as git.

Android app disassembling, modification and reassembling

This is actually nothing very new, but what probably a lot of people do for a long time already. You can use this technique to do security reviews, to crack license mechanisms of apps, check how easy it is to modify your own app or do malware research. I’m not saying you should or shouldn’t do any of these. As usually tested on Mac OSX only but should work on Linux or other Unix, too.

You need the following folder structure (or simply download the Android-app-disassembling-reassembling.zip):

  • Folder called “apks-to-process”
  • Folder called “external-tools”
  • File “disassemble.sh” (see below)
  • File “reassemble.sh” (see below)
  • In the “external-tools” put the apktool.jar
  • In the “apks-to-process” folder put your Android app apk file

After you run the disassemble.sh file you find the smali code for your app in the “outputs/smali-output” directory. Now you can change the app as you like. Here are three suggestions:

  • I recommend to add the android:debuggable=”true” attribute in the AndroidManifest.xml to your application tag. Afterwards you will be able to see the log messages of the application in logcat (“adb logcat” command when your phone is connected via USB).
  • Replace one of the png files in the ressources folder
  • If your application is making a new instance of a SecreKeySpec for encryption (something like “new-instance v1, Ljavax/crypto/spec/SecretKeySpec” in smali, grep for it), try to dump the contents of the secret key. That’s pretty easy with IGLogger. Download the IGLogger files and put the iglogger.smali file in the folder “outputs/smali-output//smali/”. Then open the file where you found the SecreKeySpec intialisation. Add a new instruction after the invoke-direct line which will initialize the SecretKeySpec (e.g. “invoke-direct {v4, v5, v6}, Ljavax/crypto/spec/SecretKeySpec;->([BLjava/lang/String;)V”). This is the place where the secret key is passed to the SecretKeySpec constructor. As we know that the first argument is the secret key, we have to log the Dalvik VM’s register v4. Add “invoke-static {v4}, Liglogger;->d([B)I” after the initialisation statement.

After you have done all your modifications, run reassemble.sh. There will be an apk file you can install on your device (see the last message that reassemble.sh will print). If you have added IGLogger, you will see a line in logcat that prints the secret key (for example run “adb logcat|grep -i IGLogger”).

Happy hacking
floyd

Here’s the disassemble.sh that will disassemble your apk file to smali code:

#!/bin/bash
ORGWD=`pwd`

#Configurable Parameters
APKLOCATION=$ORGWD/apks-to-process #where the APK files are stored that should be processed

#Disassembling
SMALI_TARGET=$ORGWD/outputs/smali-output #Where to save the results
APKTOOLSTART="java -jar $ORGWD/external-tools/apktool.jar" #The apktool

########
#Normally you should not need to change anything below here
########

#Look for the files to dissassemble
cd $APKLOCATION
FILES=`ls *.apk`

if [ -e $SMALI_TARGET ]
then
    echo "[ERROR] Please delete/rename $SMALI_TARGET folder first!"
    exit
else
    mkdir $SMALI_TARGET
fi

for f in $FILES
do
  echo "[INFO] Disassembling $f"  
  $APKTOOLSTART d $f $SMALI_TARGET/$f
done

cd $ORGWD

Here’s the reassemble.sh code that will reassemble your app to a signed and ready to be installed Android app apk file:

#!/bin/bash
ORGWD=`pwd`

#Configurable Parameters
APKLOCATION="$ORGWD/outputs/faked-apks" #where the APK files will be stored that should be produced

#Reassembling
SMALI_TARGET="$ORGWD/outputs/smali-output" #Where to get the apps to reassemble
APKTOOLSTART="java -jar $ORGWD/external-tools/apktool.jar" #The apktool

########
#Normally you should not need to change anything below here
########

#Look for the files to dissassemble
cd "$SMALI_TARGET"
FILES=`ls`

if [ -e "$APKLOCATION" ]
then
    echo "[ERROR] Please delete/rename $APKLOCATION folder first!"
    exit
else
    mkdir "$APKLOCATION"
fi

for f in $FILES
do
  echo "[INFO] Reassembling $f"  
  $APKTOOLSTART b "$SMALI_TARGET/$f" "$APKLOCATION/$f"
  if [ ! -f "$APKLOCATION/someone.keystore" ]
  then
    keytool -genkey -noprompt -dname "CN=example.ch, OU=floydsReassembling, O=example, L=example, S=example, C=CH" -storepass password -keypass password -alias someone -validity 100000 -keystore "$APKLOCATION/someone.keystore" -keyalg RSA -keysize 2048
  fi
  jarsigner -verbose -storepass password -keypass password -sigalg SHA1withRSA -digestalg SHA1 -keystore "$APKLOCATION/someone.keystore" "$APKLOCATION/$f" someone
  mv "$APKLOCATION/$f" "$APKLOCATION/$f.unaligned"
  zipalign -v 4 "$APKLOCATION/$f.unaligned" "$APKLOCATION/$f"
done

echo "TODO:"
echo "adb install \"$APKLOCATION/$f\""

cd "$ORGWD"

How to store credentials on Android

There is a lot of discussion going on how to store credentials on the Android platform. What I’m going to discuss is focused on the Android platform, but you’ll notice that most of the things can be applied to all mobile plattforms. There are some differences, e.g. that you should use the much more evolved secure keystore of the iOS. There will be a keychain in Android 4.0. But of course you can screw up the secure storage on all platforms or even examples of the vendor show you how to screw up.

Before you start storing credentials the most important question is:

  • Do I really want to store that data and why?

Before you simply answer “yes”, consider the following:

  • Do you want to authenticate the user inside the app to check if he is allowed to access the app and other data?
    • Then use a secure cryptographic hash function (e.g. SHA-2). Use a salt which is long enough and randomly generated on the fly. You can store the salt in plain along with the generated hash. Use multiple hashing rounds (e.g. 10’000).
    • Don’t forget to check in every Activity that the user is already authenticated, because Activities can be invoked directly.
  • Do you want to authenticate the user against a server?
    • Before I go back to how you should store the credentials: Please use a secure channel to the server. One possibility is to use SSL (e.g. a HTTPS connection), but make sure you check the server certificate. Another possibility is to include a public key and have the corresponding private key on the server (actually I like this version even better, because you don’t have to trust hackable CA root companies).
    • Change the server side. Talk to your customer if you are developing an app for someone else, it’s not a big deal to change something on the server side. Try to store a session token instead of the password. Even increasing the expiry time for sessions is better than storing passwords on the client. Only increase it if the request is coming from the Android app, but not for all clients (like standard browser authentication).
    • If your really can’t change the server side and you can not use a token, you have a problem. Whatever you are doing from now, you have to store the password in reversable form, but the client isn’t a good option for that. Attackers equipped with root exploits and reverse engineering skills will always be able to get that password from somewhere (most of the time from the code or the filesystem). Consider that people often reuse passwords, which is a bad habit and if the password for your application is extracted, there might be other services that have to suffer from the laziness of the user.
      • There are several ways of how you could try to protect the credentials, but again, they’re all useless against a sophisticated attacker: E.g. encryption (key in the source code) and good obfuscation (some are just useless). EDIT: With the new Android KeyChain you are even better off if you have a hardware-backed storage on the device. Simply use the KeyChain to store a private key that you use to encrypt the password. The password can only be decrypted when the attacker is in the posession of the device, as he can not extract the private key from the device. Well of course he can extract the key, but root permissions are not sufficient and it means messing with the hardware (which is very expensive).
      • At least tell the user how the credentials are stored, why it could be a problem and what they can do to be protected (e.g. disable “remember password”).
      • Don’t screw up and write the password somewhere to the logs…

0sec talk

Two weeks ago I had a talk about “Reversing Android Apps – Hacking and cracking Android apps is easy” at 0sec. You can download the slides. The video on slide 6 (circumventing the Android lock screen with button mashing) is available here. If you’re interested in the topic, you should check out the other posts in the Android category.

AES encryption/decryption in python

Sometimes I just need some encryption, so I wrote a script that fits some cases. The functions use the python Crypto library.

The security of the used encryption is ok, I wrote a PBKDF2-like Key Derivation Function, that hashes the password before truncating and using it as the AES key. The encryption function does not add random padding. This means an attacker can guess how long the plaintext was. Additionally, CBC is a non-authenticated mode, therefore if somebody flips a bit in your ciphertext the decryption routine won’t notice. This usually means an attacker can flip one bit, but the remaining blocks will be corrupted. So flipping a bit in the last block is easy. Moreover 13’370 derivation rounds might be too much or not enough for you.

def AESencrypt(password, plaintext, base64=False):
    import hashlib, os
    from Crypto.Cipher import AES
    SALT_LENGTH = 32
    DERIVATION_ROUNDS=13370
    BLOCK_SIZE = 16
    KEY_SIZE = 32
    MODE = AES.MODE_CBC
    
    salt = os.urandom(SALT_LENGTH)
    iv = os.urandom(BLOCK_SIZE)
    
    paddingLength = 16 - (len(plaintext) % 16)
    paddedPlaintext = plaintext+chr(paddingLength)*paddingLength
    derivedKey = password
    for i in range(0,DERIVATION_ROUNDS):
        derivedKey = hashlib.sha256(derivedKey+salt).digest()
    derivedKey = derivedKey[:KEY_SIZE]
    cipherSpec = AES.new(derivedKey, MODE, iv)
    ciphertext = cipherSpec.encrypt(paddedPlaintext)
    ciphertext = ciphertext + iv + salt
    if base64:
        import base64
        return base64.b64encode(ciphertext)
    else:
        return ciphertext.encode("hex")

def AESdecrypt(password, ciphertext, base64=False):
    import hashlib
    from Crypto.Cipher import AES
    SALT_LENGTH = 32
    DERIVATION_ROUNDS=13370
    BLOCK_SIZE = 16
    KEY_SIZE = 32
    MODE = AES.MODE_CBC
    
    if base64:
        import base64
        decodedCiphertext = base64.b64decode(ciphertext)
    else:
        decodedCiphertext = ciphertext.decode("hex")
    startIv = len(decodedCiphertext)-BLOCK_SIZE-SALT_LENGTH
    startSalt = len(decodedCiphertext)-SALT_LENGTH
    data, iv, salt = decodedCiphertext[:startIv], decodedCiphertext[startIv:startSalt], decodedCiphertext[startSalt:]
    derivedKey = password
    for i in range(0, DERIVATION_ROUNDS):
        derivedKey = hashlib.sha256(derivedKey+salt).digest()
    derivedKey = derivedKey[:KEY_SIZE]
    cipherSpec = AES.new(derivedKey, MODE, iv)
    plaintextWithPadding = cipherSpec.decrypt(data)
    paddingLength = ord(plaintextWithPadding[-1])
    plaintext = plaintextWithPadding[:-paddingLength]
    return plaintext
    
a = AESencrypt("password", "ABC")
print AESdecrypt("password", a)