Skip to content

Instantly share code, notes, and snippets.

@fpadoan
Created November 14, 2016 16:41
Show Gist options
  • Save fpadoan/88c5171fbeba17cf82d4f35074b10f03 to your computer and use it in GitHub Desktop.
Save fpadoan/88c5171fbeba17cf82d4f35074b10f03 to your computer and use it in GitHub Desktop.
A secure token/hash generator and storage for Android.
package your_app_package.security;
import android.content.Context;
import android.util.Base64;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
* A secure token/hash generator for Android, based on
* http://android-developers.blogspot.com.br/2013/02/using-cryptography-to-store-credentials.html
* @author http://fpadoan.github.io/
*/
public class Tokenizer {
private static final String ALGORITHM = "PBKDF2WithHmacSHA1"; // PBKDF2 algorithm to generate hashes
private static final String SALT_FILENAME = "7c9a14601ddad4b790222c9dfd0fc8ad"; // MD5 for "salt file name"
private static final String HASH_FILENAME = "88420aaba94a1879af71fd2abdde9bcb"; // MD5 for "hash file name"
/**
* Hash generator based on code provided by Android Teams.
* @param original What is to be hashed.
* @return A properly (secure) salt+hashed+base64 encoded string.
*/
public static String generateSaltHashedKey(Context context, String original) {
String finalHash = null;
try {
SecretKey secretKey = generateKey(original.toCharArray(), getSalt(context));
finalHash = Base64.encodeToString(secretKey.getEncoded(), Base64.URL_SAFE + Base64.NO_WRAP);
writeHashFile(context, finalHash.getBytes());
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return finalHash;
}
/**
* Provided by Trevor Johns, of the Android Developers Team.
* @param passphraseOrPin What to hash
* @param salt To season up our hash sauce
* @return Properly hashed secret key
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
// TODO test for a value above 100ms, but not too large to freeze app
final int iterations = 1000;
// Generate a 256-bit key
final int outputKeyLength = 256;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM);
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
return secretKeyFactory.generateSecret(keySpec);
}
private static byte[] getSalt(Context context) {
byte saltBytes[] = new byte[20];
if (!findSaltFile(context, saltBytes)) {
// Do not seed Secure Random!
// Automatically seeded from system entropy.
SecureRandom sRandom = new SecureRandom();
sRandom.nextBytes(saltBytes);
writeSaltFile(context, saltBytes);
}
return saltBytes;
}
private static boolean findSaltFile(Context context, byte[] bytes) {
FileInputStream stream = null;
try {
stream = context.openFileInput(SALT_FILENAME);
stream.read(bytes);
stream.close();
return true;
}
catch (FileNotFoundException e) {
return false;
}
catch (IOException e) { e.printStackTrace(); }
return false;
}
private static void writeSaltFile(Context context, byte[] bytes) {
FileOutputStream stream = null;
try {
stream = context.openFileOutput(SALT_FILENAME, Context.MODE_PRIVATE);
stream.write(bytes);
stream.close();
}
catch (FileNotFoundException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
}
private static void writeHashFile(Context context, byte[] bytes) {
FileOutputStream stream = null;
try {
stream = context.openFileOutput(HASH_FILENAME, Context.MODE_PRIVATE);
stream.write(bytes);
stream.close();
}
catch (FileNotFoundException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment