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

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