Add password encryption (#6)

Add password encryption
This commit is contained in:
RicomenPL 2017-07-31 16:52:34 +02:00 committed by GitHub
parent a7cd6e7983
commit 3271065263
18 changed files with 419 additions and 111 deletions

View File

@ -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'
}

View File

@ -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<Grade> 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();

View File

@ -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<String, Integer, Integer> {
@ -80,10 +82,12 @@ public class LoginTask extends AsyncTask<String, Integer, Integer> {
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<String, Integer, Integer> {
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<String, String> cookiesList = login.getJar();
@ -109,7 +117,7 @@ public class LoginTask extends AsyncTask<String, Integer, Integer> {
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);
}

View File

@ -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);

View File

@ -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<Void, Void, Void> {
@ -30,12 +32,10 @@ public class LoadingTask extends AsyncTask<Void, Void, Void> {
@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<Void, Void, Void> {
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<Void, Void, Void> {
} 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();
}
}
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.security;
public class CryptoException extends Exception {
public CryptoException() {
}
public CryptoException(String message) {
super(message);
}
}

View File

@ -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));
}
}
}

View File

@ -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<String> getAllAliases() throws CryptoException {
ArrayList<String> keyAliases = new ArrayList<>();
try {
Enumeration<String> 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<Byte> 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");
}
}
}

View File

@ -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;
}
}

View File

@ -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">
<FrameLayout
android:id="@+id/fragment_container"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fitsSystemWindows="true"
android:layout_weight="1"
/>
android:fitsSystemWindows="true"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior" />
<android.support.design.widget.BottomNavigationView
@ -25,8 +24,8 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:menu="@menu/navigation"
app:itemIconTint="@color/bottomnavi_color"
app:itemTextColor="@color/bottomnavi_color"/>
app:itemTextColor="@color/bottomnavi_color"
app:menu="@menu/navigation" />
</LinearLayout>

View File

@ -4,60 +4,60 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/appName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/appName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:text="@string/app_name"
android:textSize="40sp" />
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:gravity="center"
android:text="@string/app_name"
android:textSize="40sp" />
<EditText
android:id="@+id/emailText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:ems="10"
android:hint="@string/email_hint"
android:inputType="textEmailAddress"
/>
<EditText
android:id="@+id/emailText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:ems="10"
android:hint="@string/email_hint"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/passwordText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:ems="10"
android:hint="@string/pass_hint"
android:fontFamily="sans-serif"
android:inputType="textPassword" />
<EditText
android:id="@+id/passwordText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:ems="10"
android:fontFamily="sans-serif"
android:hint="@string/pass_hint"
android:inputType="textPassword" />
<AutoCompleteTextView
android:id="@+id/countyText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:hint="@string/county_hint"
android:imeOptions="actionDone"
android:inputType="text" />
<AutoCompleteTextView
android:id="@+id/countyText"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:hint="@string/county_hint"
android:imeOptions="actionDone"
android:inputType="text" />
<Button
android:id="@+id/agreeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:onClick="login"
android:text="@string/login_button" />
<Button
android:id="@+id/agreeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:onClick="login"
android:text="@string/login_button" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -7,48 +7,48 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.github.wulkanowy.activity.started.StartedActivity"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="0dp">
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="0dp">
<TextView
android:id="@+id/nameApp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="228dp"
android:text="@string/app_name"
android:textSize="40sp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.503"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:layout_constraintBottom_creator="1"
tools:layout_constraintLeft_creator="1"
android:layout_marginBottom="228dp"
app:layout_constraintHorizontal_bias="0.503" />
tools:layout_constraintRight_creator="1" />
<ImageView
android:id="@+id/logoImage"
android:layout_width="201dp"
android:layout_height="176dp"
android:src="@drawable/logo_image"
tools:layout_constraintRight_creator="1"
tools:layout_constraintBottom_creator="1"
app:layout_constraintBottom_toTopOf="@+id/nameApp"
app:layout_constraintRight_toRightOf="parent"
tools:layout_constraintLeft_creator="1"
android:layout_marginBottom="53dp"
android:src="@drawable/logo_image"
app:layout_constraintBottom_toTopOf="@+id/nameApp"
app:layout_constraintHorizontal_bias="0.503"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="0.503" />
app:layout_constraintRight_toRightOf="parent"
tools:layout_constraintBottom_creator="1"
tools:layout_constraintLeft_creator="1"
tools:layout_constraintRight_creator="1" />
<TextView
android:id="@+id/rawText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/under_logo"
tools:layout_constraintTop_creator="1"
tools:layout_constraintRight_creator="1"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="57dp"
android:text="@string/under_logo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/nameApp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintLeft_toLeftOf="parent" />
tools:layout_constraintRight_creator="1"
tools:layout_constraintTop_creator="1" />
</android.support.constraint.ConstraintLayout>

View File

@ -4,11 +4,9 @@
android:layout_height="match_parent"
tools:context="io.github.wulkanowy.activity.dashboard.board.BoardFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Fragment Dashboard" />
</android.support.design.widget.CoordinatorLayout>

View File

@ -6,15 +6,15 @@
<android.support.v7.widget.RecyclerView
android:id="@+id/card_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
android:scrollbars="vertical" />
<RelativeLayout
android:id="@+id/loadingPanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" >
android:gravity="center">
<ProgressBar
android:layout_width="wrap_content"

View File

@ -1,26 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="1dp"
android:layout_marginLeft="1dp"
android:layout_marginBottom="2dp"
>
android:layout_marginLeft="1dp"
android:layout_marginRight="1dp"
android:orientation="vertical">
<ImageView
android:id="@+id/img_android"
android:adjustViewBounds="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true" />
/>
<TextView
android:id="@+id/tv_android"
android:gravity="center"
android:textColor="#000000"
android:background="#FFFFFF"
android:textStyle="bold"
android:lines="2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:gravity="center"
android:lines="2"
android:textColor="#000000"
android:textStyle="bold" />
</LinearLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_marks"

View File

@ -23,4 +23,7 @@
<string name="noInternet_text">Brak połączenia z internetem</string>
<string name="login_cookies_save_failed">Nie udało się zapisać sesji</string>
<string name="SQLite_ioError_text">W bazie danych wystąpił błąd. Zrestartuj aplikacje a także sprawdź iość wolnego miejsca w pamięci wewnętrznej</string>
<string name="root_failed">To urządzenie posiada posiada podwyższone uprawnienia (root). Automatyczne logowanie zosatło wyłączone.</string>
<string name="encrypt_failed">Szyfrowanie nie powiodło się. Automatyczne logowanie zostało wyłączone</string>
<string name="decrypt_failed">Deszyfrowanie nie powiodło się. Automatyczne logowanie zostało wyłączone</string>
</resources>

View File

@ -23,4 +23,7 @@
<string name="noInternet_text">No internet connection</string>
<string name="login_cookies_save_failed">Failed to save session</string>
<string name="SQLite_ioError_text">An error occurred in the database. Restart the applications and check the free space in the internal memory</string>
<string name="root_failed">This device is rooted. Automatic login has been disabled</string>
<string name="encrypt_failed">Encryption failed. Automatic login has been disabled</string>
<string name="decrypt_failed">Decrypt is failed. Automatic login has been disable</string>
</resources>