Support for master password and storing password as hashes (HMAC) instead of plaintext,

additional crypto utils with tests (partialy for in-progress pref export encryption),
changed PasswordCheck to more common UI and to use lambdas (will need given password in lambda callback for prefs enc)
This commit is contained in:
Dominik Dzienia 2020-03-24 22:22:23 +01:00
parent 4716b04972
commit 6e9ccc593f
10 changed files with 369 additions and 27 deletions

View file

@ -50,8 +50,10 @@ import info.nightscout.androidaps.plugins.source.EversensePlugin
import info.nightscout.androidaps.plugins.source.GlimpPlugin import info.nightscout.androidaps.plugins.source.GlimpPlugin
import info.nightscout.androidaps.plugins.source.PoctechPlugin import info.nightscout.androidaps.plugins.source.PoctechPlugin
import info.nightscout.androidaps.plugins.source.TomatoPlugin import info.nightscout.androidaps.plugins.source.TomatoPlugin
import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.OKDialog.show import info.nightscout.androidaps.utils.OKDialog.show
import info.nightscout.androidaps.utils.SafeParse import info.nightscout.androidaps.utils.SafeParse
import info.nightscout.androidaps.utils.protection.PasswordCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -98,6 +100,8 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var wearPlugin: WearPlugin @Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var maintenancePlugin: MaintenancePlugin @Inject lateinit var maintenancePlugin: MaintenancePlugin
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> @Inject lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector override fun androidInjector(): AndroidInjector<Any> = androidInjector
@ -254,19 +258,19 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
// Preferences // Preferences
if (pref.getKey() == resourceHelper.gs(R.string.key_settings_protection)) { if (pref.getKey() == resourceHelper.gs(R.string.key_settings_protection)) {
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_settings_password)) val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_settings_password))
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString() if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
} }
// Application // Application
// Application // Application
if (pref.getKey() == resourceHelper.gs(R.string.key_application_protection)) { if (pref.getKey() == resourceHelper.gs(R.string.key_application_protection)) {
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_application_password)) val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_application_password))
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString() if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
} }
// Bolus // Bolus
// Bolus // Bolus
if (pref.getKey() == resourceHelper.gs(R.string.key_bolus_protection)) { if (pref.getKey() == resourceHelper.gs(R.string.key_bolus_protection)) {
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_bolus_password)) val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_bolus_password))
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString() if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
} }
} }
if (pref is EditTextPreference) { if (pref is EditTextPreference) {
@ -281,6 +285,16 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
} }
} }
} }
if (pref is Preference) {
if ((pref.getKey() != null) && (pref.getKey().contains("_password"))) {
if (sp.getString(pref.getKey(), "").startsWith("hmac:")) {
pref.setSummary("******")
} else {
pref.setSummary(resourceHelper.gs(R.string.password_not_set))
}
}
}
pref?.let { adjustUnitDependentPrefs(it) } pref?.let { adjustUnitDependentPrefs(it) }
} }
@ -294,4 +308,29 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
updatePrefSummary(p) updatePrefSummary(p)
} }
} }
// We use Preference and custom editor instead of EditTextPreference
// to hash password while it is saved and never have to show it, even hashed
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
if (preference != null) {
if (preference.key == resourceHelper.gs(R.string.key_master_password)) {
passwordCheck.setPassword(this.context!!, R.string.master_password, R.string.key_master_password)
return true;
}
if (preference.key == resourceHelper.gs(R.string.key_settings_password)) {
passwordCheck.setPassword(this.context!!, R.string.settings_password, R.string.key_settings_password)
return true;
}
if (preference.key == resourceHelper.gs(R.string.key_bolus_password)) {
passwordCheck.setPassword(this.context!!, R.string.bolus_password, R.string.key_bolus_password)
return true;
}
if (preference.key == resourceHelper.gs(R.string.key_application_password)) {
passwordCheck.setPassword(this.context!!, R.string.application_password, R.string.key_application_password)
return true;
}
}
return super.onPreferenceTreeClick(preference)
}
} }

View file

@ -0,0 +1,142 @@
package info.nightscout.androidaps.utils
import org.spongycastle.util.encoders.Base64
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.security.SecureRandom
import java.security.spec.KeySpec
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
private val HEX_CHARS = "0123456789abcdef"
private val HEX_CHARS_ARRAY = "0123456789abcdef".toCharArray()
fun String.hexStringToByteArray() : ByteArray {
val upperCased = this.toLowerCase()
val result = ByteArray(length / 2)
for (i in 0 until length step 2) {
val firstIndex = HEX_CHARS.indexOf(upperCased[i]);
val secondIndex = HEX_CHARS.indexOf(upperCased[i + 1]);
val octet = firstIndex.shl(4).or(secondIndex)
result.set(i.shr(1), octet.toByte())
}
return result
}
fun ByteArray.toHex() : String{
val result = StringBuffer()
forEach {
val octet = it.toInt()
val firstIndex = (octet and 0xF0).ushr(4)
val secondIndex = octet and 0x0F
result.append(HEX_CHARS_ARRAY[firstIndex])
result.append(HEX_CHARS_ARRAY[secondIndex])
}
return result.toString()
}
object CryptoUtil {
private const val IV_LENGTH_BYTE = 12
private const val TAG_LENGTH_BIT = 128
private const val AES_KEY_SIZE_BIT = 256
private const val PBKDF2_ITERATIONS = 50000 // check delays it cause on real device
private const val SALT_SIZE_BYTE = 32
private val secureRandom: SecureRandom = SecureRandom()
fun sha256(source: String): String {
val digest = MessageDigest.getInstance("SHA-256")
val hashRaw = digest.digest(source.toByteArray())
return hashRaw.toHex()
}
fun hmac256(str: String, secret: String): String? {
val sha256_HMAC = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256")
sha256_HMAC.init(secretKey)
return sha256_HMAC.doFinal(str.toByteArray()).toHex()
}
private fun prepCipherKey(passPhrase: String, salt:ByteArray, iterationCount:Int = PBKDF2_ITERATIONS, keyStrength:Int = AES_KEY_SIZE_BIT): SecretKeySpec {
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1")
val spec: KeySpec = PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, keyStrength)
val tmp: SecretKey = factory.generateSecret(spec)
return SecretKeySpec(tmp.getEncoded(), "AES")
}
fun mineSalt(len :Int = SALT_SIZE_BYTE): ByteArray {
val salt = ByteArray(len)
secureRandom.nextBytes(salt)
return salt
}
fun encrypt(passPhrase: String, salt:ByteArray, rawData: String ): String? {
val iv: ByteArray?
val encrypted: ByteArray?
return try {
iv = ByteArray(IV_LENGTH_BYTE)
secureRandom.nextBytes(iv)
val cipherEnc: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipherEnc.init(Cipher.ENCRYPT_MODE, prepCipherKey(passPhrase, salt), GCMParameterSpec(TAG_LENGTH_BIT, iv))
encrypted = cipherEnc.doFinal(rawData.toByteArray())
val byteBuffer: ByteBuffer = ByteBuffer.allocate(1 + iv.size + encrypted.size)
byteBuffer.put(iv.size.toByte())
byteBuffer.put(iv)
byteBuffer.put(encrypted)
String(Base64.encode(byteBuffer.array()))
} catch (e: Exception) {
null
}
}
fun decrypt(passPhrase: String, salt:ByteArray, encryptedData: String): String? {
val iv: ByteArray?
val encrypted: ByteArray?
return try {
val byteBuffer = ByteBuffer.wrap(Base64.decode(encryptedData))
val ivLength = byteBuffer.get().toInt()
iv = ByteArray(ivLength)
byteBuffer[iv]
encrypted = ByteArray(byteBuffer.remaining())
byteBuffer[encrypted]
val cipherDec: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipherDec.init(Cipher.DECRYPT_MODE, prepCipherKey(passPhrase, salt), GCMParameterSpec(TAG_LENGTH_BIT, iv))
val dec = cipherDec.doFinal(encrypted)
String(dec)
} catch (e: Exception) {
null
}
}
fun checkPassword(password: String, referenceHash: String): Boolean {
return if (referenceHash.startsWith("hmac:")) {
val hashSegments = referenceHash.split(":")
if (hashSegments.size != 3)
return false
return hmac256(password, hashSegments[1]) == hashSegments[2]
} else {
password == referenceHash
}
}
fun hashPassword(password: String): String {
return if (!password.startsWith("hmac:")) {
val salt = mineSalt().toHex()
return "hmac:${salt}:${hmac256(password, salt)}"
} else {
password
}
}
}

View file

@ -2,13 +2,17 @@ package info.nightscout.androidaps.utils.protection
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.view.ContextThemeWrapper
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject import javax.inject.Inject
@ -18,34 +22,74 @@ import javax.inject.Singleton
class PasswordCheck @Inject constructor(val sp: SP) { class PasswordCheck @Inject constructor(val sp: SP) {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: Runnable?, cancel: Runnable? = null, fail: Runnable? = null) { fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) {
val password = sp.getString(preference, "") val password = sp.getString(preference, "")
if (password == "") { if (password == "") {
ok?.run() ok?.invoke("")
return return
} }
val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null)
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = activity.getString(labelId)
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp)
val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null) val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null)
val alertDialogBuilder = AlertDialog.Builder(activity) val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme))
alertDialogBuilder.setView(promptsView) alertDialogBuilder.setView(promptsView)
val label = promptsView.findViewById<View>(R.id.passwordprompt_text) as TextView
label.text = activity.getString(labelId)
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
alertDialogBuilder alertDialogBuilder
.setCancelable(false) .setCancelable(false)
.setCustomTitle(titleLayout)
.setPositiveButton(activity.getString(R.string.ok)) { _, _ -> .setPositiveButton(activity.getString(R.string.ok)) { _, _ ->
val enteredPassword = userInput.text.toString() val enteredPassword = userInput.text.toString()
if (password == enteredPassword) ok?.run() if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword)
else { else {
ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword)) ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword))
fail?.run() fail?.invoke()
} }
} }
.setNegativeButton(activity.getString(R.string.cancel) .setNegativeButton(activity.getString(R.string.cancel)
) { dialog, _ -> ) { dialog, _ ->
cancel?.run() cancel?.invoke()
dialog.cancel()
}
alertDialogBuilder.create().show()
}
@SuppressLint("InflateParams")
fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int) {
val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null)
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = context.getText(labelId)
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp)
val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
alertDialogBuilder.setView(promptsView)
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
alertDialogBuilder
.setCancelable(false)
.setCustomTitle(titleLayout)
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
val enteredPassword = userInput.text.toString()
if (enteredPassword.isNotEmpty()) {
sp.putString(preference, CryptoUtil.hashPassword(enteredPassword))
} else {
if (sp.contains(preference)) {
sp.remove(preference)
}
}
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set))
}
.setNegativeButton(context.getString(R.string.cancel)
) { dialog, _ ->
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed))
dialog.cancel() dialog.cancel()
} }

View file

@ -21,7 +21,8 @@ class ProtectionCheck @Inject constructor(
enum class ProtectionType { enum class ProtectionType {
NONE, NONE,
BIOMETRIC, BIOMETRIC,
PASSWORD MASTER_PASSWORD,
CUSTOM_PASSWORD
} }
private val passwordsResourceIDs = listOf( private val passwordsResourceIDs = listOf(
@ -43,7 +44,8 @@ class ProtectionCheck @Inject constructor(
return when (ProtectionType.values()[sp.getInt(protectionTypeResourceIDs[protection.ordinal], ProtectionType.NONE.ordinal)]) { return when (ProtectionType.values()[sp.getInt(protectionTypeResourceIDs[protection.ordinal], ProtectionType.NONE.ordinal)]) {
ProtectionType.NONE -> false ProtectionType.NONE -> false
ProtectionType.BIOMETRIC -> true ProtectionType.BIOMETRIC -> true
ProtectionType.PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != "" ProtectionType.MASTER_PASSWORD -> sp.getString(R.string.key_master_password, "") != ""
ProtectionType.CUSTOM_PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != ""
} }
} }
@ -55,8 +57,10 @@ class ProtectionCheck @Inject constructor(
ok?.run() ok?.run()
ProtectionType.BIOMETRIC -> ProtectionType.BIOMETRIC ->
BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail) BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail)
ProtectionType.PASSWORD -> ProtectionType.MASTER_PASSWORD ->
passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], ok, cancel, fail) passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() })
ProtectionType.CUSTOM_PASSWORD ->
passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], { ok?.run() }, { cancel?.run() }, { fail?.run() })
} }
} }
} }

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="24"
android:viewportHeight="24"
android:width="24dp"
android:height="24dp">
<path
android:pathData="M22 18V22H18V19H15V16H12L9.74 13.74C9.19 13.91 8.61 14 8 14A6 6 0 0 1 2 8A6 6 0 0 1 8 2A6 6 0 0 1 14 8C14 8.61 13.91 9.19 13.74 9.74L22 18M7 5A2 2 0 0 0 5 7A2 2 0 0 0 7 9A2 2 0 0 0 9 7A2 2 0 0 0 7 5Z"
android:fillColor="#ffffff" />
</vector>

View file

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="24"
android:viewportHeight="24"
android:width="48dp"
android:height="48dp"
android:tint="#FFFFFF"
>
<group
android:scaleX="0.66"
android:scaleY="0.66"
android:pivotX="12"
android:pivotY="12">
<path
android:pathData="M22 18V22H18V19H15V16H12L9.74 13.74C9.19 13.91 8.61 14 8 14A6 6 0 0 1 2 8A6 6 0 0 1 8 2A6 6 0 0 1 14 8C14 8.61 13.91 9.19 13.74 9.74L22 18M7 5A2 2 0 0 0 5 7A2 2 0 0 0 7 9A2 2 0 0 0 9 7A2 2 0 0 0 7 5Z"
android:fillColor="#FF000000" />
</group>
</vector>

View file

@ -5,17 +5,13 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp" > android:padding="10dp" >
<TextView
android:id="@+id/passwordprompt_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText <EditText
android:id="@+id/passwordprompt_pass" android:id="@+id/passwordprompt_pass"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textPassword"> android:hint="@string/password_hint"
android:inputType="textPassword"
>
<requestFocus /> <requestFocus />

View file

@ -5,15 +5,22 @@
<string name="settings_protection">Settings protection</string> <string name="settings_protection">Settings protection</string>
<string name="application_protection">Application protection</string> <string name="application_protection">Application protection</string>
<string name="bolus_protection">Bolus protection</string> <string name="bolus_protection">Bolus protection</string>
<string name="master_password">Master password</string>
<string name="settings_password">Settings password</string> <string name="settings_password">Settings password</string>
<string name="application_password">Application password</string> <string name="application_password">Application password</string>
<string name="bolus_password">Bolus password</string> <string name="bolus_password">Bolus password</string>
<string name="unlock_settings">Unlock settings</string> <string name="unlock_settings">Unlock settings</string>
<string name="biometric">Biometric</string> <string name="biometric">Biometric</string>
<string name="password">Password</string> <string name="custom_password">Custom password</string>
<string name="noprotection">No protection</string> <string name="noprotection">No protection</string>
<string name="protection">Protection</string> <string name="protection">Protection</string>
<string name="password_set">Password set!</string>
<string name="password_not_set">Password not set</string>
<string name="password_not_changed">Password not changed</string>
<string name="password_hint">Enter password here</string>
<string name="key_master_password">master_password</string>
<string name="key_settings_password" translatable="false">settings_password</string> <string name="key_settings_password" translatable="false">settings_password</string>
<string name="key_application_password" translatable="false">application_password</string> <string name="key_application_password" translatable="false">application_password</string>
<string name="key_bolus_password">translatable="false"bolus_password</string> <string name="key_bolus_password">translatable="false"bolus_password</string>
@ -24,13 +31,15 @@
<string-array name="protectiontype"> <string-array name="protectiontype">
<item>@string/noprotection</item> <item>@string/noprotection</item>
<item>@string/biometric</item> <item>@string/biometric</item>
<item>@string/password</item> <item>@string/master_password</item>
<item>@string/custom_password</item>
</string-array> </string-array>
<string-array name="protectiontypeValues"> <string-array name="protectiontypeValues">
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>3</item>
</string-array> </string-array>
</resources> </resources>

View file

@ -22,6 +22,12 @@
<PreferenceCategory android:title="@string/protection"> <PreferenceCategory android:title="@string/protection">
<Preference
android:inputType="textPassword"
android:key="@string/key_master_password"
android:title="@string/master_password"
/>
<ListPreference <ListPreference
android:defaultValue="1" android:defaultValue="1"
android:entries="@array/protectiontype" android:entries="@array/protectiontype"
@ -29,7 +35,7 @@
android:key="@string/key_settings_protection" android:key="@string/key_settings_protection"
android:title="@string/settings_protection" /> android:title="@string/settings_protection" />
<EditTextPreference <Preference
android:inputType="textPassword" android:inputType="textPassword"
android:key="@string/key_settings_password" android:key="@string/key_settings_password"
android:title="@string/settings_password" /> android:title="@string/settings_password" />
@ -41,7 +47,7 @@
android:key="@string/key_application_protection" android:key="@string/key_application_protection"
android:title="@string/application_protection" /> android:title="@string/application_protection" />
<EditTextPreference <Preference
android:inputType="textPassword" android:inputType="textPassword"
android:key="@string/key_application_password" android:key="@string/key_application_password"
android:title="@string/application_password" /> android:title="@string/application_password" />
@ -53,7 +59,7 @@
android:key="@string/key_bolus_protection" android:key="@string/key_bolus_protection"
android:title="@string/bolus_protection" /> android:title="@string/bolus_protection" />
<EditTextPreference <Preference
android:inputType="textPassword" android:inputType="textPassword"
android:key="@string/key_bolus_password" android:key="@string/key_bolus_password"
android:title="@string/bolus_password" /> android:title="@string/bolus_password" />

View file

@ -0,0 +1,76 @@
package info.nightscout.androidaps.utils
import info.nightscout.androidaps.TestBase
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.modules.junit4.PowerMockRunner
@PowerMockIgnore("javax.crypto.*")
@RunWith(PowerMockRunner::class)
class CryptoUtilTest: TestBase() {
@Test
fun testFixedSaltCrypto() {
val salt = byteArrayOf(
-33, -29, 16, -19, 99, -111, -3, 2, 116, 106, 47, 38, -54, 11, -77, 28,
111, -15, -65, -110, 4, -32, -29, -70, -95, -88, -53, 19, 87, -103, 123, -15)
val password = "thisIsFixedPassword"
val payload = "FIXED-PAYLOAD"
val encrypted = CryptoUtil.encrypt(password, salt, payload)
Assert.assertNotNull(encrypted)
val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!)
Assert.assertEquals(decrypted, payload)
}
@Test
fun testStandardCrypto() {
val salt = CryptoUtil.mineSalt()
val password = "topSikret"
val payload = "{what:payloadYouWantToProtect}"
val encrypted = CryptoUtil.encrypt(password, salt, payload)
Assert.assertNotNull(encrypted)
val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!)
Assert.assertEquals(decrypted, payload)
}
@Test
fun testHashVector() {
val payload = "{what:payloadYouWantToProtect}"
val hash = CryptoUtil.sha256(payload)
Assert.assertEquals(hash, "a1aafe3ed6cc127e6d102ddbc40a205147230e9cfd178daf108c83543bbdcd13")
}
@Test
fun testHmac() {
val payload = "{what:payloadYouWantToProtect}"
val password = "topSikret"
val expectedHmac = "ea2213953d0f2e55047cae2d23fb4f0de1b805d55e6271efa70d6b85fb692bea" // generated using other HMAC tool
val hash = CryptoUtil.hmac256(payload, password)
Assert.assertEquals(hash, expectedHmac)
}
@Test
fun testPlainPasswordCheck() {
Assert.assertTrue(CryptoUtil.checkPassword("same", "same"))
Assert.assertFalse(CryptoUtil.checkPassword("same", "other"))
}
@Test
fun testHashedPasswordCheck() {
Assert.assertTrue(CryptoUtil.checkPassword("givenSecret", CryptoUtil.hashPassword("givenSecret")))
Assert.assertFalse(CryptoUtil.checkPassword("givenSecret", CryptoUtil.hashPassword("otherSecret")))
Assert.assertTrue(CryptoUtil.checkPassword("givenHashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
Assert.assertFalse(CryptoUtil.checkPassword("givenMashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
Assert.assertFalse(CryptoUtil.checkPassword("givenHashToCheck", "hmac:0fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
Assert.assertFalse(CryptoUtil.checkPassword("givenHashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:b0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
}
}