From 32710652630e3b4b182526f57c0f881bc422f50b Mon Sep 17 00:00:00 2001 From: RicomenPL Date: Mon, 31 Jul 2017 16:52:34 +0200 Subject: [PATCH] Add password encryption (#6) Add password encryption --- app/build.gradle | 1 + .../dashboard/marks/MarksFragment.java | 3 +- .../wulkanowy/activity/main/LoginTask.java | 12 +- .../wulkanowy/activity/main/MainActivity.java | 9 +- .../activity/started/LoadingTask.java | 19 +- .../wulkanowy/security/CryptoException.java | 12 ++ .../io/github/wulkanowy/security/Safety.java | 43 ++++ .../github/wulkanowy/security/Scrambler.java | 198 ++++++++++++++++++ .../wulkanowy/utilities/RootUtilities.java | 42 ++++ .../main/res/layout/activity_dashboard.xml | 13 +- app/src/main/res/layout/activity_main.xml | 98 ++++----- app/src/main/res/layout/activity_started.xml | 40 ++-- app/src/main/res/layout/fragment_board.xml | 2 - app/src/main/res/layout/fragment_marks.xml | 6 +- app/src/main/res/layout/row_layout.xml | 23 +- app/src/main/res/menu/navigation.xml | 3 +- app/src/main/res/values-pl/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + 18 files changed, 419 insertions(+), 111 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/security/CryptoException.java create mode 100644 app/src/main/java/io/github/wulkanowy/security/Safety.java create mode 100644 app/src/main/java/io/github/wulkanowy/security/Scrambler.java create mode 100644 app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java diff --git a/app/build.gradle b/app/build.gradle index 12137661..e9db9542 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,6 +38,7 @@ dependencies { compile 'com.android.support:support-v4:25.3.1' compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.squareup.picasso:picasso:2.5.2' + compile 'org.apache.commons:commons-lang3:3.6' testCompile 'junit:junit:4.12' } diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/marks/MarksFragment.java b/app/src/main/java/io/github/wulkanowy/activity/dashboard/marks/MarksFragment.java index 2a927418..0bee2648 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/marks/MarksFragment.java +++ b/app/src/main/java/io/github/wulkanowy/activity/dashboard/marks/MarksFragment.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -91,7 +92,7 @@ public class MarksFragment extends Fragment { GradesList gradesList = new GradesList(cookies, "powiatjaroslawski"); List grades = gradesList.getAll(); for (Grade item : grades) { - System.out.println(item.getSubject() + ": " + item.getValue()); + Log.d("MarksFragment", item.getSubject() + ": " + item.getValue()); } } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java b/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java index 7db6f64b..1b1e85b2 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java +++ b/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java @@ -22,6 +22,8 @@ import io.github.wulkanowy.api.user.BasicInformation; import io.github.wulkanowy.api.user.PersonalData; import io.github.wulkanowy.database.accounts.AccountData; import io.github.wulkanowy.database.accounts.DatabaseAccount; +import io.github.wulkanowy.security.CryptoException; +import io.github.wulkanowy.security.Safety; public class LoginTask extends AsyncTask { @@ -80,10 +82,12 @@ public class LoginTask extends AsyncTask { PersonalData personalData = userInfo.getPersonalData(); String firstAndLastName = personalData.getFirstAndLastName(); + Safety safety = new Safety(activity); + AccountData accountData = new AccountData() .setName(firstAndLastName) .setEmail(credentials[0]) - .setPassword(credentials[1]) + .setPassword(safety.encrypt(credentials[0], credentials[1])) .setCounty(credentials[2]); DatabaseAccount databaseAccount = new DatabaseAccount(activity); @@ -95,6 +99,10 @@ public class LoginTask extends AsyncTask { return R.string.SQLite_ioError_text; } catch (IOException | LoginErrorException e) { return R.string.login_denied; + } catch (CryptoException e) { + return R.string.encrypt_failed; + } catch (UnsupportedOperationException e) { + return R.string.root_failed; } } //Map cookiesList = login.getJar(); @@ -109,7 +117,7 @@ public class LoginTask extends AsyncTask { Toast.makeText(activity, activity.getString(messageID), Toast.LENGTH_LONG).show(); - if (messageID == R.string.login_accepted) { + if (messageID == R.string.login_accepted || messageID == R.string.root_failed || messageID == R.string.encrypt_failed) { Intent intent = new Intent(activity, DashboardActivity.class); activity.startActivity(intent); } diff --git a/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java b/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java index 527f5d0f..f91f9a5a 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java +++ b/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java @@ -54,12 +54,9 @@ public class MainActivity extends AppCompatActivity { } public void login(View a) { - EditText emailText = (EditText) findViewById(R.id.emailText); - EditText passwordText = (EditText) findViewById(R.id.passwordText); - EditText countyText = (EditText) findViewById(R.id.countyText); - String password = passwordText.getText().toString(); - String email = emailText.getText().toString(); - String county = countyText.getText().toString(); + String password = ((EditText) findViewById(R.id.passwordText)).getText().toString(); + String email = ((EditText) findViewById(R.id.emailText)).getText().toString(); + String county = ((EditText) findViewById(R.id.countyText)).getText().toString(); String[] keys = this.getResources().getStringArray(R.array.counties); String[] values = this.getResources().getStringArray(R.array.counties_values); diff --git a/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java b/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java index 3b28e321..27147f7a 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java +++ b/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java @@ -16,6 +16,8 @@ import io.github.wulkanowy.activity.main.LoginTask; import io.github.wulkanowy.activity.main.MainActivity; import io.github.wulkanowy.database.accounts.AccountData; import io.github.wulkanowy.database.accounts.DatabaseAccount; +import io.github.wulkanowy.security.CryptoException; +import io.github.wulkanowy.security.Safety; public class LoadingTask extends AsyncTask { @@ -30,12 +32,10 @@ public class LoadingTask extends AsyncTask { @Override protected Void doInBackground(Void... voids) { - if (!SAVE_DATA) { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); } isOnline = isOnline(); @@ -81,9 +81,12 @@ public class LoadingTask extends AsyncTask { databaseAccount.close(); if (accountData != null) { + + Safety safety = new Safety(activity); + new LoginTask(activity, false).execute( accountData.getEmail(), - accountData.getPassword(), + safety.decrypt(accountData.getEmail(), accountData.getPassword()), accountData.getCounty() ); @@ -92,6 +95,8 @@ public class LoadingTask extends AsyncTask { } catch (SQLException e) { Toast.makeText(activity, R.string.SQLite_ioError_text, Toast.LENGTH_LONG).show(); + } catch (CryptoException e) { + Toast.makeText(activity, R.string.decrypt_failed, Toast.LENGTH_LONG).show(); } } } diff --git a/app/src/main/java/io/github/wulkanowy/security/CryptoException.java b/app/src/main/java/io/github/wulkanowy/security/CryptoException.java new file mode 100644 index 00000000..89254a49 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/security/CryptoException.java @@ -0,0 +1,12 @@ +package io.github.wulkanowy.security; + + +public class CryptoException extends Exception { + + public CryptoException() { + } + + public CryptoException(String message) { + super(message); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/security/Safety.java b/app/src/main/java/io/github/wulkanowy/security/Safety.java new file mode 100644 index 00000000..6c4823b3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/security/Safety.java @@ -0,0 +1,43 @@ +package io.github.wulkanowy.security; + +import android.content.Context; +import android.os.Build; +import android.util.Base64; + +import io.github.wulkanowy.utilities.RootUtilities; + +public class Safety extends Scrambler { + + public Safety(Context context) { + super(context); + } + + public String encrypt(String email, String plainText) throws CryptoException, UnsupportedOperationException { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + + loadKeyStore(); + generateNewKey(email); + return encryptString(email, plainText); + } else { + + if (!RootUtilities.isRooted()) { + return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); + } else { + throw new UnsupportedOperationException("Password store in this devices isn't safe because is rooted"); + } + } + } + + public String decrypt(String email, String encryptedText) throws CryptoException { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + + loadKeyStore(); + return decryptString(email, encryptedText); + } else { + return new String(Base64.decode(encryptedText, Base64.DEFAULT)); + } + + } +} diff --git a/app/src/main/java/io/github/wulkanowy/security/Scrambler.java b/app/src/main/java/io/github/wulkanowy/security/Scrambler.java new file mode 100644 index 00000000..70819e06 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/security/Scrambler.java @@ -0,0 +1,198 @@ +package io.github.wulkanowy.security; + + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.security.KeyPairGeneratorSpec; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.util.Base64; +import android.util.Log; + +import org.apache.commons.lang3.ArrayUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Enumeration; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.security.auth.x500.X500Principal; + +public class Scrambler { + + private KeyStore keyStore; + private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; + public final static String DEBUG_TAG = "KeyStoreSecurity"; + public Context context; + + public Scrambler(Context context) { + this.context = context; + } + + public void loadKeyStore() throws CryptoException { + + try { + keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); + keyStore.load(null); + } catch (Exception e) { + throw new CryptoException(e.getMessage()); + } + + } + + public ArrayList getAllAliases() throws CryptoException { + + ArrayList keyAliases = new ArrayList<>(); + try { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + keyAliases.add(aliases.nextElement()); + } + } catch (Exception e) { + throw new CryptoException(e.getMessage()); + } + + return keyAliases; + } + + @TargetApi(18) + public void generateNewKey(String alias) throws CryptoException { + + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + + AlgorithmParameterSpec spec; + + end.add(Calendar.YEAR, 10); + if (!alias.isEmpty()) { + try { + if (!keyStore.containsAlias(alias)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) + .setDigests(KeyProperties.DIGEST_SHA256) + .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + .setCertificateNotBefore(start.getTime()) + .setCertificateNotAfter(end.getTime()) + .build(); + + } else { + spec = new KeyPairGeneratorSpec.Builder(context) + .setAlias(alias) + .setSubject(new X500Principal("CN=" + alias)) + .setSerialNumber(BigInteger.TEN) + .setStartDate(start.getTime()) + .setEndDate(end.getTime()) + .build(); + } + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", ANDROID_KEYSTORE); + keyPairGenerator.initialize(spec); + keyPairGenerator.generateKeyPair(); + + } else { + Log.w(DEBUG_TAG, "GenerateNewKey - " + alias + " is exist"); + } + } catch (Exception e) { + throw new CryptoException(e.getMessage()); + } + } else { + throw new CryptoException("GenerateNewKey - String is empty"); + } + + + Log.d(DEBUG_TAG, "Key pair are create"); + + } + + public void deleteKey(String alias) throws CryptoException { + + if (!alias.isEmpty()) { + try { + keyStore.deleteEntry(alias); + Log.d(DEBUG_TAG, "Key" + alias + "is delete"); + } catch (Exception e) { + Log.e(DEBUG_TAG, e.getMessage()); + } + } else { + throw new CryptoException("DeleteKey - String is empty"); + } + } + + public String encryptString(String alias, String text) throws CryptoException { + + if (!alias.isEmpty() && !text.isEmpty()) { + try { + KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null); + RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey(); + + Cipher input = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + input.init(Cipher.ENCRYPT_MODE, publicKey); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CipherOutputStream cipherOutputStream = new CipherOutputStream( + outputStream, input); + cipherOutputStream.write(text.getBytes("UTF-8")); + cipherOutputStream.close(); + + Log.d(DEBUG_TAG, "String is encrypt"); + + byte[] vals = outputStream.toByteArray(); + + String encryptedText = Base64.encodeToString(vals, Base64.DEFAULT); + Log.d(DEBUG_TAG, encryptedText); + return encryptedText; + + } catch (Exception e) { + throw new CryptoException(e.getMessage()); + } + } else { + throw new CryptoException("EncryptString - String is empty"); + } + } + + public String decryptString(String alias, String text) throws CryptoException { + + if (!alias.isEmpty() && !text.isEmpty()) { + try { + KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null); + + Cipher output = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey()); + + CipherInputStream cipherInputStream = new CipherInputStream( + new ByteArrayInputStream(Base64.decode(text, Base64.DEFAULT)), output); + + ArrayList values = new ArrayList<>(); + + int nextByte; + + while ((nextByte = cipherInputStream.read()) != -1) { + values.add((byte) nextByte); + } + + Byte[] bytes = values.toArray(new Byte[values.size()]); + + Log.d(DEBUG_TAG, "String is decrypt"); + + return new String(ArrayUtils.toPrimitive(bytes), 0, bytes.length, "UTF-8"); + + } catch (Exception e) { + throw new CryptoException(e.getMessage()); + } + } else { + throw new CryptoException("EncryptString - String is empty"); + + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java b/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java new file mode 100644 index 00000000..596cd0e0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java @@ -0,0 +1,42 @@ +package io.github.wulkanowy.utilities; + + +import android.os.Build; + +import java.io.File; + +public class RootUtilities { + + public static boolean isRooted() { + + + String buildTags = Build.TAGS; + if (buildTags != null && buildTags.contains("test-keys")) { + return true; + } + + try { + File file = new File("/system/app/Superuser.apk"); + if (file.exists()) { + return true; + } + } catch (Exception e1) { + // ignore + } + + return canExecuteCommand("/system/xbin/which su") + || canExecuteCommand("/system/bin/which su") || canExecuteCommand("which su"); + } + + private static boolean canExecuteCommand(String command) { + boolean executedSuccesfully; + try { + Runtime.getRuntime().exec(command); + executedSuccesfully = true; + } catch (Exception e) { + executedSuccesfully = false; + } + + return executedSuccesfully; + } +} diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml index 4f974699..0de21abd 100644 --- a/app/src/main/res/layout/activity_dashboard.xml +++ b/app/src/main/res/layout/activity_dashboard.xml @@ -6,17 +6,16 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context="io.github.wulkanowy.activity.dashboard.DashboardActivity" - android:weightSum="1"> + android:weightSum="1" + tools:context="io.github.wulkanowy.activity.dashboard.DashboardActivity"> + android:fitsSystemWindows="true" + app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior" /> + app:itemTextColor="@color/bottomnavi_color" + app:menu="@menu/navigation" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 53d0d960..fc8c1c8a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,60 +4,60 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + - + android:layout_gravity="center" + android:layout_marginTop="15dp" + android:gravity="center" + android:text="@string/app_name" + android:textSize="40sp" /> - + - + - + -