diff options
author | Kevin T. Berstene <kberstene@gmail.com> | 2019-04-01 14:30:43 -0400 |
---|---|---|
committer | Kevin T. Berstene <kberstene@gmail.com> | 2019-04-01 14:30:43 -0400 |
commit | 0e5c95e5cb6f7db5cc1c3ae711f622378d8ef786 (patch) | |
tree | 252846940f1e75a34b8da511d1975c68fa00c1f1 /app/src/main/java/github/daneren2005/dsub/util | |
parent | 2b26df335ccff17e3970ac94f4c0abfbd6898a47 (diff) | |
download | dsub-0e5c95e5cb6f7db5cc1c3ae711f622378d8ef786.tar.gz dsub-0e5c95e5cb6f7db5cc1c3ae711f622378d8ef786.tar.bz2 dsub-0e5c95e5cb6f7db5cc1c3ae711f622378d8ef786.zip |
Added password encryption for SDK 23 and higher
Diffstat (limited to 'app/src/main/java/github/daneren2005/dsub/util')
4 files changed, 154 insertions, 0 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/util/Constants.java b/app/src/main/java/github/daneren2005/dsub/util/Constants.java index 7f5ff3f1..017ba2f3 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Constants.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Constants.java @@ -85,6 +85,7 @@ public final class Constants { public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"; public static final String PREFERENCES_KEY_USERNAME = "username"; public static final String PREFERENCES_KEY_PASSWORD = "password"; + public static final String PREFERENCES_KEY_ENCRYPTED_PASSWORD = "encryptedPassword"; public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime"; public static final String PREFERENCES_KEY_THEME = "theme"; public static final String PREFERENCES_KEY_FULL_SCREEN = "fullScreen"; diff --git a/app/src/main/java/github/daneren2005/dsub/util/KeyStoreUtil.java b/app/src/main/java/github/daneren2005/dsub/util/KeyStoreUtil.java new file mode 100644 index 00000000..10ec9497 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/util/KeyStoreUtil.java @@ -0,0 +1,147 @@ +package github.daneren2005.dsub.util; + +import android.annotation.TargetApi; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.support.annotation.NonNull; +import android.util.Base64; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableEntryException; +import java.security.cert.CertificateException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; + +@TargetApi(23) +public class KeyStoreUtil { + private static String TAG = KeyStoreUtil.class.getSimpleName(); + private static final String KEYSTORE_ALIAS = "DSubKeyStoreAlias"; + private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; + private static final String KEYSTORE_CIPHER_PROVIDER = "AndroidKeyStoreBCWorkaround"; + private static final String KEYSTORE_TRANSFORM = "AES/CBC/PKCS7Padding"; + private static final String KEYSTORE_BYTE_ENCODING = "UTF-8"; + + public static void loadKeyStore() throws KeyStoreException, IOException, + CertificateException, NoSuchAlgorithmException { + + // Load keystore + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); + keyStore.load(null); + + // Check if keystore has been used before + if (!keyStore.containsAlias(KEYSTORE_ALIAS)) { + // If alias does not exist, keystore hasn't been used before + // Create a new secret key to store in the keystore + try { + Log.w(TAG, "Generating keys."); + generateKeys(); + } catch (Exception e) { + Log.w(TAG, "Key generation failed."); + Log.w(TAG, Log.getStackTraceString(e)); + } + } + } + + private static void generateKeys() throws InvalidAlgorithmParameterException, + NoSuchAlgorithmException, NoSuchProviderException { + KeyGenerator keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER); + keyGen.init(new KeyGenParameterSpec.Builder(KEYSTORE_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .build()); + keyGen.generateKey(); + } + + private static Key getKey() throws KeyStoreException, CertificateException, + NoSuchAlgorithmException, IOException, UnrecoverableEntryException { + + // Attempt to load keystore + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); + keyStore.load(null); + + // Fetch and return secret key + return keyStore.getKey(KEYSTORE_ALIAS, null); + } + + public static String encrypt(@NonNull String plainTextString) { + Log.d(TAG, "Encrypting password..."); + try { + // Retrieve secret key + final Key key = getKey(); + + // Initialize cipher + Cipher cipher = Cipher.getInstance(KEYSTORE_TRANSFORM, KEYSTORE_CIPHER_PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, key); + + // Create stream for storing data + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + // Write the IV length first so the IV can be split from the encrypted password + outputStream.write(cipher.getIV().length); + + // Write the auto-generated IV + outputStream.write(cipher.getIV()); + + // Encrypt the plaintext and write the encrypted string + outputStream.write(cipher.doFinal(plainTextString.getBytes(KEYSTORE_BYTE_ENCODING))); + + // Encode the return full stream for storage + Log.d(TAG, "Password encryption successful"); + return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP); + + } catch (Exception e) { + Log.w(TAG, "Password encryption failed"); + Log.d(TAG, Log.getStackTraceString(e)); + return null; + } + } + + public static String decrypt(@NonNull String encryptedString) { + Log.d(TAG, "Decrypting password..."); + try { + // Retrieve secret key + final Key key = getKey(); + + // Decode the string from Base64 + byte[] decodedBytes = Base64.decode(encryptedString, Base64.NO_WRAP); + int ivLength = decodedBytes[0]; + int encryptedLength = decodedBytes.length - (ivLength + 1); + + // Get IV from decoded string + byte[] ivBytes = new byte[ivLength]; + System.arraycopy(decodedBytes, 1, ivBytes, 0, ivLength); + + // Get encrypted password from decoded string + byte[] encryptedBytes = new byte[encryptedLength]; + System.arraycopy(decodedBytes, ivLength + 1, encryptedBytes, 0, encryptedLength); + + // Initialize cipher using the IV from the dencoded string + Cipher cipher = Cipher.getInstance(KEYSTORE_TRANSFORM, KEYSTORE_CIPHER_PROVIDER); + IvParameterSpec ivParamSpec = new IvParameterSpec(ivBytes); + cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec); + + // Decrypt the password + String decryptedString = new String(cipher.doFinal(encryptedBytes)); + + // Return the decrypted password string + Log.d(TAG, "Password successfully decrypted"); + return decryptedString; + + } catch (Exception e) { + Log.w(TAG, "Password decryption failed"); + Log.w(TAG, Log.getStackTraceString(e)); + return null; + } + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java index db1c628f..0775c956 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java +++ b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java @@ -272,6 +272,10 @@ public final class UserUtil { SharedPreferences prefs = Util.getPreferences(context); String correctPassword = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + Util.getActiveServer(context), null); + if (prefs.getBoolean(Constants.PREFERENCES_KEY_ENCRYPTED_PASSWORD + instance, false)) { + correctPassword = KeyStoreUtil.decrypt(correctPassword); + } + return password != null && password.equals(correctPassword); } diff --git a/app/src/main/java/github/daneren2005/dsub/util/Util.java b/app/src/main/java/github/daneren2005/dsub/util/Util.java index 78f3e2d6..791fea91 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Util.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Util.java @@ -199,6 +199,7 @@ public final class Util { String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); + if ((password != null) && (prefs.getBoolean(Constants.PREFERENCES_KEY_ENCRYPTED_PASSWORD + instance, false))) password = KeyStoreUtil.decrypt(password); String musicFolderId = prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + newInstance, null); // Store the +1 server details in the to be deleted instance @@ -364,6 +365,7 @@ public final class Util { String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); + if ((password != null) && (prefs.getBoolean(Constants.PREFERENCES_KEY_ENCRYPTED_PASSWORD + instance, false))) password = KeyStoreUtil.decrypt(password); builder.append(serverUrl); if (builder.charAt(builder.length() - 1) != '/') { |