Biometric encryption
This commit is contained in:
parent
8fbf44b75a
commit
ffba642fa0
17
.idea/deploymentTargetDropDown.xml
Normal file
17
.idea/deploymentTargetDropDown.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<Target>
|
||||||
|
<type value="RUNNING_DEVICE_TARGET" />
|
||||||
|
<deviceKey>
|
||||||
|
<Key>
|
||||||
|
<type value="SERIAL_NUMBER" />
|
||||||
|
<value value="R38N50464FV" />
|
||||||
|
</Key>
|
||||||
|
</deviceKey>
|
||||||
|
</Target>
|
||||||
|
</runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<timeTargetWasSelectedWithDropDown value="2023-09-18T17:20:49.048823275Z" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -10,8 +10,8 @@
|
|||||||
<option name="gradleJvm" value="jbr-17" />
|
<option name="gradleJvm" value="jbr-17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="/mnt/sshd/Files/Desktop/testing/android/Cringe-Authenticator" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="/mnt/sshd/Files/Desktop/testing/android/Cringe-Authenticator/app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
|
@ -74,7 +74,6 @@ dependencies {
|
|||||||
implementation 'com.google.mlkit:barcode-scanning:17.1.0'
|
implementation 'com.google.mlkit:barcode-scanning:17.1.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
|
|
||||||
|
|
||||||
def camerax_version = "1.2.3"
|
def camerax_version = "1.2.3"
|
||||||
implementation "androidx.camera:camera-core:${camerax_version}"
|
implementation "androidx.camera:camera-core:${camerax_version}"
|
||||||
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.crypto;
|
||||||
|
|
||||||
|
public class BiometricKey {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final byte[] encryptedKey;
|
||||||
|
private final byte[] iv;
|
||||||
|
|
||||||
|
public BiometricKey(String id, byte[] encryptedKey, byte[] iv) {
|
||||||
|
this.id = id;
|
||||||
|
this.encryptedKey = encryptedKey;
|
||||||
|
this.iv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncryptedKey() {
|
||||||
|
return encryptedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getIV() {
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,37 @@
|
|||||||
package com.cringe_studios.cringe_authenticator.crypto;
|
package com.cringe_studios.cringe_authenticator.crypto;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
||||||
import org.bouncycastle.crypto.params.Argon2Parameters;
|
import org.bouncycastle.crypto.params.Argon2Parameters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.GCMParameterSpec;
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
@ -19,6 +39,8 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
|
|
||||||
public class Crypto {
|
public class Crypto {
|
||||||
|
|
||||||
|
private static final String KEY_STORE = "AndroidKeyStore";
|
||||||
|
|
||||||
public static byte[] generateHash(CryptoParameters parameters, String password) throws CryptoException {
|
public static byte[] generateHash(CryptoParameters parameters, String password) throws CryptoException {
|
||||||
Argon2Parameters params = new Argon2Parameters.Builder()
|
Argon2Parameters params = new Argon2Parameters.Builder()
|
||||||
.withVersion(parameters.getArgon2Version())
|
.withVersion(parameters.getArgon2Version())
|
||||||
@ -55,12 +77,16 @@ public class Crypto {
|
|||||||
return generateBytes(parameters.getEncryptionIVLength());
|
return generateBytes(parameters.getEncryptionIVLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] encrypt(CryptoParameters parameters, byte[] bytes, SecretKey key) throws CryptoException {
|
public static CryptoResult encryptWithResult(CryptoParameters parameters, byte[] bytes, SecretKey key, boolean useIV) throws CryptoException {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance(parameters.getEncryptionAlgorithm());
|
Cipher cipher = Cipher.getInstance(parameters.getEncryptionAlgorithm());
|
||||||
GCMParameterSpec spec = new GCMParameterSpec(parameters.getEncryptionGCMTagLength() * 8, parameters.getIV());
|
GCMParameterSpec spec = new GCMParameterSpec(parameters.getEncryptionGCMTagLength() * 8, parameters.getIV());
|
||||||
|
if(useIV) {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||||
return cipher.doFinal(bytes);
|
}else {
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||||
|
}
|
||||||
|
return new CryptoResult(cipher.doFinal(bytes), cipher.getIV());
|
||||||
}catch(NoSuchAlgorithmException | NoSuchPaddingException |
|
}catch(NoSuchAlgorithmException | NoSuchPaddingException |
|
||||||
InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException |
|
InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException |
|
||||||
IllegalBlockSizeException e) {
|
IllegalBlockSizeException e) {
|
||||||
@ -68,10 +94,14 @@ public class Crypto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] decrypt(CryptoParameters parameters, byte[] bytes, SecretKey key) throws CryptoException {
|
public static byte[] encrypt(CryptoParameters parameters, byte[] bytes, SecretKey key) throws CryptoException {
|
||||||
|
return encryptWithResult(parameters, bytes, key, true).getEncrypted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] decrypt(CryptoParameters parameters, byte[] bytes, SecretKey key, byte[] overrideIV) throws CryptoException {
|
||||||
try {
|
try {
|
||||||
Cipher cipher = Cipher.getInstance(parameters.getEncryptionAlgorithm());
|
Cipher cipher = Cipher.getInstance(parameters.getEncryptionAlgorithm());
|
||||||
GCMParameterSpec spec = new GCMParameterSpec(parameters.getEncryptionGCMTagLength() * 8, parameters.getIV());
|
GCMParameterSpec spec = new GCMParameterSpec(parameters.getEncryptionGCMTagLength() * 8, overrideIV != null ? overrideIV : parameters.getIV());
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||||
return cipher.doFinal(bytes);
|
return cipher.doFinal(bytes);
|
||||||
}catch(NoSuchAlgorithmException | NoSuchPaddingException |
|
}catch(NoSuchAlgorithmException | NoSuchPaddingException |
|
||||||
@ -81,4 +111,53 @@ public class Crypto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] decrypt(CryptoParameters parameters, byte[] bytes, SecretKey key) throws CryptoException {
|
||||||
|
return decrypt(parameters, bytes, key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
|
public static BiometricKey createBiometricKey(CryptoParameters parameters) throws CryptoException {
|
||||||
|
try {
|
||||||
|
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE);
|
||||||
|
|
||||||
|
String keyID = UUID.randomUUID().toString();
|
||||||
|
generator.init(new KeyGenParameterSpec.Builder(keyID,
|
||||||
|
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||||
|
.setKeySize(parameters.getEncryptionAESKeyLength() * 8)
|
||||||
|
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||||
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||||
|
.setUserAuthenticationRequired(true)
|
||||||
|
.setUserAuthenticationValidityDurationSeconds(60)
|
||||||
|
.setRandomizedEncryptionRequired(true)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
SecretKey biometricKey = generator.generateKey();
|
||||||
|
SecretKey key = OTPDatabase.getLoadedKey();
|
||||||
|
CryptoResult encryptedKey = Crypto.encryptWithResult(parameters, key.getEncoded(), biometricKey, false);
|
||||||
|
return new BiometricKey(keyID, encryptedKey.getEncrypted(), encryptedKey.getIV());
|
||||||
|
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecretKey getBiometricKey(BiometricKey key) throws CryptoException {
|
||||||
|
try {
|
||||||
|
KeyStore store = KeyStore.getInstance(KEY_STORE);
|
||||||
|
store.load(null);
|
||||||
|
return (SecretKey) store.getKey(key.getId(), null);
|
||||||
|
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteBiometricKey(BiometricKey key) throws CryptoException {
|
||||||
|
try {
|
||||||
|
KeyStore ks = KeyStore.getInstance(KEY_STORE);
|
||||||
|
ks.load(null);
|
||||||
|
ks.deleteEntry(key.getId());
|
||||||
|
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.crypto;
|
||||||
|
|
||||||
|
public class CryptoResult {
|
||||||
|
|
||||||
|
private byte[] encrypted;
|
||||||
|
private byte[] iv;
|
||||||
|
|
||||||
|
public CryptoResult(byte[] encrypted, byte[] iv) {
|
||||||
|
this.encrypted = encrypted;
|
||||||
|
this.iv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncrypted() {
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getIV() {
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package com.cringe_studios.cringe_authenticator.fragment;
|
|||||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -16,10 +17,12 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.biometric.BiometricManager;
|
import androidx.biometric.BiometricManager;
|
||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.MainActivity;
|
import com.cringe_studios.cringe_authenticator.MainActivity;
|
||||||
|
import com.cringe_studios.cringe_authenticator.crypto.BiometricKey;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.FragmentSettingsBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.FragmentSettingsBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.BiometricUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.FabUtil;
|
import com.cringe_studios.cringe_authenticator.util.FabUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
||||||
@ -28,6 +31,7 @@ import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
@ -106,9 +110,35 @@ public class SettingsFragment extends NamedFragment {
|
|||||||
binding.settingsEnableIntroVideo.setChecked(SettingsUtil.isIntroVideoEnabled(requireContext()));
|
binding.settingsEnableIntroVideo.setChecked(SettingsUtil.isIntroVideoEnabled(requireContext()));
|
||||||
binding.settingsEnableIntroVideo.setOnCheckedChangeListener((view, checked) -> SettingsUtil.setEnableIntroVideo(requireContext(), checked));
|
binding.settingsEnableIntroVideo.setOnCheckedChangeListener((view, checked) -> SettingsUtil.setEnableIntroVideo(requireContext(), checked));
|
||||||
|
|
||||||
if(BiometricManager.from(requireContext()).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
if(SettingsUtil.isDatabaseEncrypted(requireContext())
|
||||||
binding.settingsBiometricLock.setChecked(SettingsUtil.isBiometricLock(requireContext()));
|
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
binding.settingsBiometricLock.setOnCheckedChangeListener((view, checked) -> SettingsUtil.setBiometricLock(requireContext(), checked));
|
&& BiometricManager.from(requireContext()).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||||
|
binding.settingsBiometricLock.setChecked(SettingsUtil.isBiometricEncryption(requireContext()));
|
||||||
|
binding.settingsBiometricLock.setOnCheckedChangeListener((view, checked) -> {
|
||||||
|
if(checked) {
|
||||||
|
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||||
|
Log.i("PROMPT", "§BERIIO");
|
||||||
|
BiometricUtil.promptBiometricAuth(requireActivity(), () -> {
|
||||||
|
try {
|
||||||
|
BiometricKey biometricKey = Crypto.createBiometricKey(SettingsUtil.getCryptoParameters(requireContext()));
|
||||||
|
SettingsUtil.enableBiometricEncryption(requireContext(), biometricKey);
|
||||||
|
} catch (CryptoException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
DialogUtil.showErrorDialog(requireContext(), "Failed to enable: " + e);
|
||||||
|
}
|
||||||
|
}, () -> view.setChecked(false));
|
||||||
|
}, null);
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
BiometricKey key = SettingsUtil.getBiometricKey(requireContext());
|
||||||
|
if(key != null) Crypto.deleteBiometricKey(key);
|
||||||
|
} catch (CryptoException e) {
|
||||||
|
DialogUtil.showErrorDialog(requireContext(), "Failed to delete key: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsUtil.disableBiometricEncryption(requireContext());
|
||||||
|
}
|
||||||
|
});
|
||||||
}else {
|
}else {
|
||||||
binding.settingsBiometricLock.setChecked(false);
|
binding.settingsBiometricLock.setChecked(false);
|
||||||
binding.settingsBiometricLock.setEnabled(false);
|
binding.settingsBiometricLock.setEnabled(false);
|
||||||
|
@ -18,9 +18,11 @@ import androidx.core.content.ContextCompat;
|
|||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.MainActivity;
|
import com.cringe_studios.cringe_authenticator.MainActivity;
|
||||||
import com.cringe_studios.cringe_authenticator.R;
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
|
import com.cringe_studios.cringe_authenticator.crypto.BiometricKey;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.ActivityUnlockBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.ActivityUnlockBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.BiometricUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
||||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException;
|
import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException;
|
||||||
@ -36,6 +38,7 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class UnlockActivity extends AppCompatActivity {
|
public class UnlockActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@ -57,47 +60,20 @@ public class UnlockActivity extends AppCompatActivity {
|
|||||||
binding = ActivityUnlockBinding.inflate(getLayoutInflater());
|
binding = ActivityUnlockBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
if(SettingsUtil.isBiometricEncryption(this) && BiometricUtil.isSupported(this)) {
|
||||||
|
binding.unlockBiometrics.setOnClickListener(view -> BiometricUtil.promptBiometricAuth(this, this::success, () -> {}));
|
||||||
|
BiometricUtil.promptBiometricAuth(this, () -> {
|
||||||
|
BiometricKey biometricKey = SettingsUtil.getBiometricKey(this);
|
||||||
try {
|
try {
|
||||||
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
|
SecretKey biometricSecretKey = Crypto.getBiometricKey(biometricKey);
|
||||||
ks.load(null);
|
byte[] keyBytes = Crypto.decrypt(SettingsUtil.getCryptoParameters(this), biometricKey.getEncryptedKey(), biometricSecretKey, biometricKey.getIV());
|
||||||
SecretKey key = KeyGenerator.getInstance("AES").generateKey();
|
SecretKey key = new SecretKeySpec(keyBytes, "AES");
|
||||||
ks.setEntry("amogus", new KeyStore.SecretKeyEntry(key), new KeyStoreParameter.Builder(this).build());
|
OTPDatabase.loadDatabase(this, key);
|
||||||
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsUtil.isBiometricLock(this)) {
|
|
||||||
Executor executor = ContextCompat.getMainExecutor(this);
|
|
||||||
BiometricPrompt prompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
|
||||||
failure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
|
||||||
success();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean supportsBiometricAuth = BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS;
|
|
||||||
boolean recentlyUnlocked = savedInstanceState != null && (System.currentTimeMillis() - savedInstanceState.getLong("pauseTime", 0L) < LOCK_TIMEOUT);
|
|
||||||
|
|
||||||
if(!recentlyUnlocked && SettingsUtil.isBiometricLock(this) && supportsBiometricAuth) {
|
|
||||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
|
||||||
.setTitle(getString(R.string.app_name))
|
|
||||||
.setSubtitle(getString(R.string.biometric_lock_subtitle))
|
|
||||||
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
//prompt.authenticate(info);
|
|
||||||
|
|
||||||
binding.unlockBiometrics.setOnClickListener(view -> {
|
|
||||||
//prompt.authenticate(info);
|
|
||||||
});
|
|
||||||
}else {
|
|
||||||
success();
|
success();
|
||||||
|
} catch (CryptoException | OTPDatabaseException e) {
|
||||||
|
DialogUtil.showErrorDialog(this, "Failed to load database: " + e);
|
||||||
}
|
}
|
||||||
|
}, () -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.unlockButton.setOnClickListener(view -> {
|
binding.unlockButton.setOnClickListener(view -> {
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.util;
|
||||||
|
|
||||||
|
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||||
|
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.biometric.BiometricManager;
|
||||||
|
import androidx.biometric.BiometricPrompt;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
public class BiometricUtil {
|
||||||
|
|
||||||
|
public static boolean isSupported(Context context) {
|
||||||
|
return BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void promptBiometricAuth(FragmentActivity context, Runnable success, Runnable failure) {
|
||||||
|
if(!isSupported(context)) {
|
||||||
|
failure.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Executor executor = ContextCompat.getMainExecutor(context);
|
||||||
|
BiometricPrompt prompt = new BiometricPrompt(context, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||||
|
failure.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
|
success.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(context.getString(R.string.app_name))
|
||||||
|
.setSubtitle(context.getString(R.string.biometric_lock_subtitle))
|
||||||
|
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
prompt.authenticate(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -182,6 +182,10 @@ public class OTPDatabase {
|
|||||||
return loadedDatabase;
|
return loadedDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SecretKey getLoadedKey() {
|
||||||
|
return loadedKey;
|
||||||
|
}
|
||||||
|
|
||||||
public static void encrypt(Context ctx, SecretKey key, CryptoParameters parameters) throws OTPDatabaseException, CryptoException {
|
public static void encrypt(Context ctx, SecretKey key, CryptoParameters parameters) throws OTPDatabaseException, CryptoException {
|
||||||
if(!isDatabaseLoaded()) throw new IllegalStateException("Database is not loaded");
|
if(!isDatabaseLoaded()) throw new IllegalStateException("Database is not loaded");
|
||||||
loadedKey = key;
|
loadedKey = key;
|
||||||
|
@ -5,6 +5,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.R;
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
|
import com.cringe_studios.cringe_authenticator.crypto.BiometricKey;
|
||||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
@ -120,6 +121,8 @@ public class SettingsUtil {
|
|||||||
ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit()
|
ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit()
|
||||||
.putBoolean("encryption", false)
|
.putBoolean("encryption", false)
|
||||||
.remove("encryption.parameters")
|
.remove("encryption.parameters")
|
||||||
|
.remove("encryption.biometric")
|
||||||
|
.remove("encryption.biometric.key")
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,10 +130,10 @@ public class SettingsUtil {
|
|||||||
return GSON.fromJson(ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.parameters", "{}"), CryptoParameters.class);
|
return GSON.fromJson(ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.parameters", "{}"), CryptoParameters.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enableBiometricEncryption(Context ctx, byte[] encryptedBiometricKey) {
|
public static void enableBiometricEncryption(Context ctx, BiometricKey biometricKey) {
|
||||||
ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit()
|
ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit()
|
||||||
.putBoolean("encryption.biometric", true)
|
.putBoolean("encryption.biometric", true)
|
||||||
.putString("encryption.biometric.key", Base64.encodeToString(encryptedBiometricKey, Base64.DEFAULT))
|
.putString("encryption.biometric.key", GSON.toJson(biometricKey))
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,21 +144,16 @@ public class SettingsUtil {
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] getEncryptedBiometricKey(Context ctx) {
|
public static boolean isBiometricEncryption(Context ctx) {
|
||||||
|
return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("encryption.biometric", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BiometricKey getBiometricKey(Context ctx) {
|
||||||
String encoded = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.biometric.key", null);
|
String encoded = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("encryption.biometric.key", null);
|
||||||
if(encoded == null) return null;
|
if(encoded == null) return null;
|
||||||
return Base64.decode(encoded, Base64.DEFAULT);
|
return GSON.fromJson(encoded, BiometricKey.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public static void setBiometricLock(Context ctx, boolean biometricLock) {
|
|
||||||
SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE);
|
|
||||||
prefs.edit().putBoolean("biometricLock", biometricLock).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isBiometricLock(Context ctx) {
|
|
||||||
return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("biometricLock", true);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public static void setEnableIntroVideo(Context ctx, boolean enableIntroVideo) {
|
public static void setEnableIntroVideo(Context ctx, boolean enableIntroVideo) {
|
||||||
SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE);
|
SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
prefs.edit().putBoolean("enableIntroVideo", enableIntroVideo).apply();
|
prefs.edit().putBoolean("enableIntroVideo", enableIntroVideo).apply();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.1.0' apply false
|
id 'com.android.application' version '8.1.1' apply false
|
||||||
id 'com.android.library' version '8.1.0' apply false
|
id 'com.android.library' version '8.1.1' apply false
|
||||||
id 'com.google.protobuf' version '0.9.3' apply false
|
id 'com.google.protobuf' version '0.9.3' apply false
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user