diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt b/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt index 0389f454..9f93abc6 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt @@ -3,6 +3,7 @@ package com.yogeshpaliyal.keypass import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.yogeshpaliyal.keypass.data.AccountModel import com.yogeshpaliyal.keypass.db.DbDao @@ -31,8 +32,6 @@ abstract class AppDatabase : RoomDatabase() { if (!this::_instance.isInitialized) - - synchronized(this) { @@ -43,18 +42,25 @@ abstract class AppDatabase : RoomDatabase() { ).addCallback(object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) - - } override fun onDestructiveMigration(db: SupportSQLiteDatabase) { super.onDestructiveMigration(db) onCreate(db) - } }) - .fallbackToDestructiveMigration() + /* .addMigrations(object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + val cursor = database.query("SELECT * FROM account") + while(cursor.moveToNext()) { + val id = cursor.getLong(cursor.getColumnIndex("id")) + val password = cursor.getLong(cursor.getColumnIndex("password")) + //-- Hash your password --// + database.execSQL("UPDATE account SET password = hashedPassword WHERE id = $id;") + } + } + })*/ .build() } @@ -62,4 +68,6 @@ abstract class AppDatabase : RoomDatabase() { return _instance } } + + } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/data/AccountModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/data/AccountModel.kt index 7af50c41..13f93b6b 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/data/AccountModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/data/AccountModel.kt @@ -16,7 +16,7 @@ import com.yogeshpaliyal.universal_adapter.model.BaseDiffUtil class AccountModel( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") - val id: Long? = null, + var id: Long? = null, @ColumnInfo(name = "title") var title: String? = null, @ColumnInfo(name = "username") diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt b/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt index 3ea91937..91de9f0f 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt @@ -21,6 +21,9 @@ abstract class DbDao { @Insert(onConflict = OnConflictStrategy.REPLACE) abstract fun insertOrUpdateAccount(accountModel: AccountModel) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertOrUpdateAccount(accountModel: List) + @Query("SELECT * from account") abstract fun getAllAccounts() : Flow> diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt b/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt index b0dec343..6cbc2421 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt @@ -2,6 +2,9 @@ package com.yogeshpaliyal.keypass.db_helper import android.content.ContentResolver import android.net.Uri +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import androidx.room.withTransaction import com.google.gson.Gson import com.yogeshpaliyal.keypass.AppDatabase import com.yogeshpaliyal.keypass.data.AccountModel @@ -12,6 +15,8 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext import java.io.File import java.io.OutputStream +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey /* @@ -36,7 +41,14 @@ suspend fun AppDatabase.createBackup(contentResolver : ContentResolver,fileUri: suspend fun AppDatabase.restoreBackup(contentResolver : ContentResolver,fileUri: Uri?) = withContext(Dispatchers.IO){ fileUri ?: return@withContext false - EncryptionHelper.doCryptoDecrypt(getOrCreateBackupKey(), contentResolver.openInputStream(fileUri)).logD("DecryptedFile") - + val restoredFile = EncryptionHelper.doCryptoDecrypt(getOrCreateBackupKey(), contentResolver.openInputStream(fileUri)) + val data = Gson().fromJson(restoredFile, Array::class.java).toList() + data.forEach { + it.id = null + } + withTransaction { + getDao().insertOrUpdateAccount(data.toList()) + } return@withContext true -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/EncryptionHelper.kt b/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/EncryptionHelper.kt index 65050653..a8a8082f 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/EncryptionHelper.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/EncryptionHelper.kt @@ -1,13 +1,17 @@ package com.yogeshpaliyal.keypass.db_helper -import java.io.* +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.io.UnsupportedEncodingException +import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException import java.security.Key import java.security.NoSuchAlgorithmException -import java.security.SecureRandom +import java.security.spec.InvalidKeySpecException +import java.security.spec.InvalidParameterSpecException import javax.crypto.* -import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -20,20 +24,28 @@ import javax.crypto.spec.SecretKeySpec */ object EncryptionHelper { private const val ALGORITHM = "AES" - private const val TRANSFORMATION = "AES/GCM/NoPadding" + // private const val TRANSFORMATION = "AES/GCM/PKCS5Padding" + private const val TRANSFORMATION = "AES" // private const val TRANSFORMATION = "DES/CBC/PKCS5Padding" + private const val TAG_LENGTH_BIT = 128 + private const val IV_LENGTH_BYTE = 12 @Throws(CryptoException::class) - fun doCryptoEncrypt(key: String, data: String, + fun doCryptoEncrypt( + key: String, data: String, outputFile: OutputStream? ) { try { + val iv = ByteArray(IV_LENGTH_BYTE) + val secretKey: Key = SecretKeySpec(key.toByteArray(), ALGORITHM) val cipher = Cipher.getInstance(TRANSFORMATION) + // val spec = GCMParameterSpec(TAG_LENGTH_BIT, iv) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) data.byteInputStream().use { @@ -76,6 +88,8 @@ object EncryptionHelper { val secretKey: Key = SecretKeySpec(key.toByteArray(), ALGORITHM) val cipher = Cipher.getInstance(TRANSFORMATION) + + cipher.init(Cipher.DECRYPT_MODE, secretKey) inputFile.use { @@ -112,4 +126,37 @@ object EncryptionHelper { + fun encryptPassword(message: String, password: String): String{ + /* Encrypt the message. */ + var cipher: Cipher? = null + cipher = Cipher.getInstance("AES/ECB/PKCS5Padding") + cipher.init(Cipher.ENCRYPT_MODE, generateKey(password)) + return String(cipher.doFinal(message.encodeToByteArray())) + } + + @Throws( + NoSuchPaddingException::class, + NoSuchAlgorithmException::class, + InvalidParameterSpecException::class, + InvalidAlgorithmParameterException::class, + InvalidKeyException::class, + BadPaddingException::class, + IllegalBlockSizeException::class, + UnsupportedEncodingException::class + ) + fun decryptMsg(encryptedMessage: String, password: String): String { + /* Decrypt the message, given derived encContentValues and initialization vector. */ + var cipher: Cipher? = null + cipher = Cipher.getInstance("AES/ECB/PKCS5Padding") + cipher.init(Cipher.DECRYPT_MODE, generateKey(password)) + return String(cipher.doFinal(encryptedMessage.toByteArray())) + } + + + @Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class) + fun generateKey(passLockKey: String): SecretKey { + return SecretKeySpec(passLockKey.encodeToByteArray(), "AES") + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt index 40e690db..71108dc5 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt @@ -1,7 +1,5 @@ package com.yogeshpaliyal.keypass.ui.auth -import android.R.attr.description -import android.app.KeyguardManager import android.content.Intent import android.os.Bundle import android.widget.Toast @@ -11,7 +9,6 @@ import androidx.core.content.ContextCompat import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.databinding.ActivityAuthenticationBinding import com.yogeshpaliyal.keypass.ui.nav.DashboardActivity -import com.yogeshpaliyal.keypass.utils.SharedPrefHelper import java.util.concurrent.Executor @@ -23,9 +20,6 @@ class AuthenticationActivity : AppCompatActivity() { private lateinit var biometricPrompt: BiometricPrompt private lateinit var promptInfo: BiometricPrompt.PromptInfo - private val userPin by lazy { - SharedPrefHelper.getString(SharedPrefHelper.SharedPrefKeys.USER_PIN) - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -78,7 +72,6 @@ class AuthenticationActivity : AppCompatActivity() { // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. - biometricPrompt.authenticate(promptInfo) binding.btnRetry.setOnClickListener { diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt index 155ad9d7..89323161 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt @@ -11,9 +11,11 @@ import androidx.navigation.fragment.navArgs import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.data.AccountModel import com.yogeshpaliyal.keypass.databinding.FragmentHomeBinding +import com.yogeshpaliyal.keypass.db_helper.EncryptionHelper import com.yogeshpaliyal.keypass.listener.UniversalClickListener import com.yogeshpaliyal.keypass.ui.detail.DetailActivity import com.yogeshpaliyal.keypass.utils.initViewModel +import com.yogeshpaliyal.keypass.utils.logD import com.yogeshpaliyal.universal_adapter.adapter.UniversalAdapterViewType import com.yogeshpaliyal.universal_adapter.adapter.UniversalRecyclerAdapter import com.yogeshpaliyal.universal_adapter.utils.Resource diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt index 2ce2a6bb..1e06dcb9 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt @@ -2,18 +2,22 @@ package com.yogeshpaliyal.keypass.ui.settings import android.app.Activity import android.content.Intent +import android.net.Uri import android.os.Bundle -import android.provider.DocumentsContract import android.widget.Toast import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.yogeshpaliyal.keypass.AppDatabase +import com.yogeshpaliyal.keypass.BuildConfig import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.db_helper.createBackup import com.yogeshpaliyal.keypass.db_helper.restoreBackup +import com.yogeshpaliyal.keypass.utils.canUserAccessBackupDirectory import com.yogeshpaliyal.keypass.utils.email +import com.yogeshpaliyal.keypass.utils.getBackupDirectory +import com.yogeshpaliyal.keypass.utils.setBackupDirectory import kotlinx.coroutines.launch class MySettingsFragment : PreferenceFragmentCompat() { @@ -40,31 +44,45 @@ class MySettingsFragment : PreferenceFragmentCompat() { selectRestoreFile() return true } + + "share" -> { + val sendIntent = Intent() + sendIntent.action = Intent.ACTION_SEND + sendIntent.putExtra( + Intent.EXTRA_TEXT, + "KeyPass Password Manager\n Offline, Secure, Open Source https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID + ) + sendIntent.type = "text/plain" + startActivity(Intent.createChooser(sendIntent, "Share KeyPass")) + return true + } } return super.onPreferenceTreeClick(preference) } private fun selectBackupDirectory(){ - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + val selectedDirectory = Uri.parse(getBackupDirectory()) - /*if (Build.VERSION.SDK_INT >= 26) { - intent.putExtra( - DocumentsContract.EXTRA_INITIAL_URI, - SignalStore.settings().getLatestSignalBackupDirectory() - ) - }*/ + context?.let { + if(it.canUserAccessBackupDirectory()){ + backup(selectedDirectory) + }else{ + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags( - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or - Intent.FLAG_GRANT_READ_URI_PERMISSION - ) + intent.addFlags( + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) - try { - startActivityForResult(intent, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE) - }catch (e: Exception){ - e.printStackTrace() + try { + startActivityForResult(intent, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE) + } catch (e: Exception) { + e.printStackTrace() + } + } } + } @@ -73,15 +91,8 @@ class MySettingsFragment : PreferenceFragmentCompat() { intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "*/*" - /*if (Build.VERSION.SDK_INT >= 26) { - intent.putExtra( - DocumentsContract.EXTRA_INITIAL_URI, - SignalStore.settings().getLatestSignalBackupDirectory() - ) - }*/ - intent.addFlags( - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION ) @@ -103,12 +114,8 @@ class MySettingsFragment : PreferenceFragmentCompat() { Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) - val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile("*/*","key_pass_backup_${System.currentTimeMillis()}.keypass") - - lifecycleScope.launch { - AppDatabase.getInstance().createBackup(contentResolver, tempFile?.uri) - Toast.makeText(context, "File saved", Toast.LENGTH_SHORT).show() - } + setBackupDirectory(selectedDirectory.toString()) + backup(selectedDirectory) } } else if (requestCode == CHOOSE_RESTORE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK){ val contentResolver = context?.contentResolver @@ -120,10 +127,30 @@ class MySettingsFragment : PreferenceFragmentCompat() { lifecycleScope.launch { AppDatabase.getInstance().restoreBackup(contentResolver, selectedFile) - Toast.makeText(context, "File saved", Toast.LENGTH_SHORT).show() + Toast.makeText(context, getString(R.string.backup_restored), Toast.LENGTH_SHORT).show() } } } } + + fun backup(selectedDirectory: Uri){ + + val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile( + "*/*", + "key_pass_backup_${System.currentTimeMillis()}.keypass" + ) + + lifecycleScope.launch { + context?.contentResolver?.let { AppDatabase.getInstance().createBackup( + it, + tempFile?.uri + ) + Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show() + + } + + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt index 21ecaf60..269785b9 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt @@ -1,7 +1,10 @@ package com.yogeshpaliyal.keypass.utils +import android.content.Context +import android.net.Uri +import android.text.TextUtils +import androidx.documentfile.provider.DocumentFile import java.security.SecureRandom -import java.util.* /* @@ -12,7 +15,8 @@ import java.util.* */ fun getRandomString(sizeOfRandomString: Int): String { - val ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%&*_+" + val ALLOWED_CHARACTERS = + "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%&*_+" val random = SecureRandom() val sb = StringBuilder(sizeOfRandomString) for (i in 0 until sizeOfRandomString) sb.append( @@ -22,3 +26,20 @@ fun getRandomString(sizeOfRandomString: Int): String { ) return sb.toString() } + + +fun Context.canUserAccessBackupDirectory(): Boolean { + val backupDirectoryUri = getUri(getBackupDirectory()) ?: return false + val backupDirectory = DocumentFile.fromTreeUri(this, backupDirectoryUri) + return backupDirectory != null && backupDirectory.exists() && backupDirectory.canRead() && backupDirectory.canWrite() +} + + +private fun getUri(string: String?): Uri? { + val uri = string + return if (TextUtils.isEmpty(uri)) { + null + } else { + Uri.parse(uri) + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPrefHelper.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPrefHelper.kt deleted file mode 100644 index e1294a35..00000000 --- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPrefHelper.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.yogeshpaliyal.keypass.utils - -import android.content.Context -import androidx.annotation.StringDef -import androidx.core.content.edit -import com.yogeshpaliyal.keypass.MyApplication -import com.yogeshpaliyal.keypass.R -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy - - -/* -* @author Yogesh Paliyal -* techpaliyal@gmail.com -* https://techpaliyal.com -* created on 22-01-2021 22:40 -*/ - -object SharedPrefHelper { - - @StringDef(open = false,value = [ - SharedPrefKeys.USER_PIN - ]) - annotation class SharedPrefKeys{ - companion object{ - const val USER_PIN = "USER_PIN" - } - } - - - - - private val sp by lazy { - MyApplication.instance.getSharedPreferences( - MyApplication.instance.getString(R.string.app_name), - Context.MODE_PRIVATE - ); - } - - - fun setString(@SharedPrefKeys key: String,value : String?){ - sp.edit { putString(key,value)} - } - - fun getString(@SharedPrefKeys key: String, default : String = "") = sp.getString(key,default) - -} \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt index faac5e7d..d20b3660 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt @@ -59,4 +59,18 @@ fun getOrCreateBackupKey(): String{ } } -private const val BACKUP_KEY = "backup_key" \ No newline at end of file + +fun setBackupDirectory(string: String){ + getSharedPreferences().edit { + putString(BACKUP_DIRECTORY, string) + } + +} + +fun getBackupDirectory(): String{ + val sp = getSharedPreferences() + return sp.getString(BACKUP_DIRECTORY,"") ?: "" +} + +private const val BACKUP_KEY = "backup_key" +private const val BACKUP_DIRECTORY = "backup_directory" \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_backup_24.xml b/app/src/main/res/drawable/ic_baseline_backup_24.xml new file mode 100644 index 00000000..28965168 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_backup_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/layout_no_accounts.xml b/app/src/main/res/layout/layout_no_accounts.xml index 6af6eec5..c7f04364 100644 --- a/app/src/main/res/layout/layout_no_accounts.xml +++ b/app/src/main/res/layout/layout_no_accounts.xml @@ -18,6 +18,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@+id/illustrator" app:layout_constraintVertical_bias="0.7" + android:gravity="center" android:text="No Accounts added, please add from below button" app:layout_constrainedWidth="true" style="@style/TextStyle.Heading" diff --git a/app/src/main/res/layout/layut_backup_keypharse.xml b/app/src/main/res/layout/layut_backup_keypharse.xml new file mode 100644 index 00000000..b8131361 --- /dev/null +++ b/app/src/main/res/layout/layut_backup_keypharse.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index f115e113..a6ed40ce 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,6 +1,6 @@ -