Created
November 14, 2016 16:41
-
-
Save fpadoan/88c5171fbeba17cf82d4f35074b10f03 to your computer and use it in GitHub Desktop.
A secure token/hash generator and storage for Android.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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