From 4552b92b8b91e9c834796cb41950b47887151864 Mon Sep 17 00:00:00 2001 From: Daniel Hugenroth Date: Fri, 23 Feb 2024 19:44:44 +0000 Subject: [PATCH] Android: prefer CharArray over String --- .../sloth/app/models/HiddenSlothViewModel.kt | 8 ++-- .../sloth/app/models/LongSlothViewModel.kt | 4 +- .../pwhash_libsodium/LibSodiumArgon2PwHash.kt | 35 +++++++++----- .../com/lambdapioneer/sloth/SlothLibTest.kt | 9 ++-- .../sloth/SlothParameterBenchmarkTest.kt | 2 +- .../sloth/crypto/Pbkdf2PwHashTest.kt | 20 ++++---- .../sloth/impl/HiddenSlothImplTest.kt | 4 +- .../sloth/impl/LongSlothImplTest.kt | 7 +-- .../com/lambdapioneer/sloth/HiddenSloth.kt | 6 +-- .../java/com/lambdapioneer/sloth/LongSloth.kt | 4 +- .../sloth/crypto/Pbkdf2PwHash.kt | 4 +- .../com/lambdapioneer/sloth/crypto/PwHash.kt | 2 +- .../sloth/impl/HiddenSlothImpl.kt | 21 +++++---- .../sloth/impl/HiddenSlothParams.kt | 7 +-- .../lambdapioneer/sloth/impl/LongSlothImpl.kt | 7 +-- .../sloth/impl/LongSlothParams.kt | 15 ++++-- .../sloth/utils/ByteArrayExtensions.kt | 46 +++++++++++++++++++ 17 files changed, 133 insertions(+), 68 deletions(-) diff --git a/android/app/src/main/java/com/lambdapioneer/sloth/app/models/HiddenSlothViewModel.kt b/android/app/src/main/java/com/lambdapioneer/sloth/app/models/HiddenSlothViewModel.kt index f2ca918..74744d6 100644 --- a/android/app/src/main/java/com/lambdapioneer/sloth/app/models/HiddenSlothViewModel.kt +++ b/android/app/src/main/java/com/lambdapioneer/sloth/app/models/HiddenSlothViewModel.kt @@ -47,14 +47,14 @@ class HiddenSlothViewModel( runLongTaskInBackground { val data = content.encodeToByteArray() if (useCachedSecrets) { - val cachedSecrets = hiddenSloth.computeCachedSecrets(pw = password) + val cachedSecrets = hiddenSloth.computeCachedSecrets(pw = password.toCharArray()) hiddenSloth.encryptToStorageWithCachedSecrets( cachedSecrets = cachedSecrets, data = data ) } else { hiddenSloth.encryptToStorage( - pw = password, + pw = password.toCharArray(), data = data, ) } @@ -64,10 +64,10 @@ class HiddenSlothViewModel( fun load(password: String, useCachedSecrets: Boolean) { runLongTaskInBackground { val contentBytes = if (useCachedSecrets) { - val cachedSecrets = hiddenSloth.computeCachedSecrets(pw = password) + val cachedSecrets = hiddenSloth.computeCachedSecrets(pw = password.toCharArray()) hiddenSloth.decryptFromStorageWithCachedSecrets(cachedSecrets) } else { - hiddenSloth.decryptFromStorage(pw = password) + hiddenSloth.decryptFromStorage(pw = password.toCharArray()) } this.output.value = contentBytes.decodeToString() } diff --git a/android/app/src/main/java/com/lambdapioneer/sloth/app/models/LongSlothViewModel.kt b/android/app/src/main/java/com/lambdapioneer/sloth/app/models/LongSlothViewModel.kt index 6841989..ad6e1a8 100644 --- a/android/app/src/main/java/com/lambdapioneer/sloth/app/models/LongSlothViewModel.kt +++ b/android/app/src/main/java/com/lambdapioneer/sloth/app/models/LongSlothViewModel.kt @@ -32,13 +32,13 @@ class LongSlothViewModel( fun generateKey(password: String) { runLongTaskInBackground { - key.value = longSloth.createNewKey(pw = password) + key.value = longSloth.createNewKey(pw = password.toCharArray()) } } fun deriveKey(password: String) { runLongTaskInBackground { - key.value = longSloth.deriveForExistingKey(pw = password) + key.value = longSloth.deriveForExistingKey(pw = password.toCharArray()) } } diff --git a/android/sloth-pwhash-libsodium/src/main/java/com/lambdapioneer/sloth/pwhash_libsodium/LibSodiumArgon2PwHash.kt b/android/sloth-pwhash-libsodium/src/main/java/com/lambdapioneer/sloth/pwhash_libsodium/LibSodiumArgon2PwHash.kt index 7ffa586..e435d22 100644 --- a/android/sloth-pwhash-libsodium/src/main/java/com/lambdapioneer/sloth/pwhash_libsodium/LibSodiumArgon2PwHash.kt +++ b/android/sloth-pwhash-libsodium/src/main/java/com/lambdapioneer/sloth/pwhash_libsodium/LibSodiumArgon2PwHash.kt @@ -3,6 +3,9 @@ package com.lambdapioneer.sloth.pwhash_libsodium import com.goterl.lazysodium.SodiumAndroid import com.lambdapioneer.sloth.crypto.PwHash import com.lambdapioneer.sloth.utils.secureRandomBytes +import java.nio.CharBuffer +import java.util.Arrays +import com.goterl.lazysodium.interfaces.PwHash as LibSodiumPwHash class LibSodiumArgon2PwHash( @@ -18,25 +21,33 @@ class LibSodiumArgon2PwHash( override fun deriveHash( salt: ByteArray, - password: String, + password: CharArray, outputLengthInBytes: Int, ): ByteArray { require(salt.size == SALT_LEN) require(outputLengthInBytes > 0) require(password.isNotEmpty()) - val passwordBytes = password.encodeToByteArray() + val passwordCharBuffer = CharBuffer.wrap(password) + val passwordBytes = Charsets.UTF_16.encode(passwordCharBuffer).array() + val result = ByteArray(outputLengthInBytes) - val libSodiumResult = libSodium.crypto_pwhash( - result, result.size.toLong(), - passwordBytes, passwordBytes.size.toLong(), - salt, - params.opsLimit, - params.memLimit, - com.goterl.lazysodium.interfaces.PwHash.Alg.PWHASH_ALG_ARGON2ID13.value, - ) - check(libSodiumResult == 0) + try { + val libSodiumResult = libSodium.crypto_pwhash( + /* outputHash = */ result, + /* outputHashLen = */ result.size.toLong(), + /* password = */ passwordBytes, + /* passwordLen = */ passwordBytes.size.toLong(), + /* salt = */ salt, + /* opsLimit = */ params.opsLimit, + /* memLimit = */ params.memLimit, + /* alg = */ LibSodiumPwHash.Alg.PWHASH_ALG_ARGON2ID13.value, + ) + check(libSodiumResult == 0) + } finally { + Arrays.fill(passwordBytes, 0x00) + } return result } @@ -46,7 +57,7 @@ class LibSodiumArgon2PwHash( } companion object { - private const val SALT_LEN = com.goterl.lazysodium.interfaces.PwHash.SALTBYTES + private const val SALT_LEN = LibSodiumPwHash.SALTBYTES } } diff --git a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothLibTest.kt b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothLibTest.kt index 3fbf24e..ec96545 100644 --- a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothLibTest.kt +++ b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothLibTest.kt @@ -34,7 +34,7 @@ class SlothLibTest { fun testLongSloth_runThrough() { val instance = createInstance() - val pw = "password" + val pw = "password".toCharArray() val storage = OnDiskStorage(context) val identifier1 = "longsloth_test_1" val longSloth1 = instance.getLongSlothInstance( @@ -90,7 +90,7 @@ class SlothLibTest { val data = "hello".toByteArray() val data2 = "hola".toByteArray() - val pw = "password" + val pw = "password".toCharArray() // create a HiddenSloth instance val params = HiddenSlothParams(payloadMaxLength = 1 * MIB) @@ -131,12 +131,13 @@ class SlothLibTest { // does not decrypt under a different passphrase assertThatExceptionOfType(SlothDecryptionFailed::class.java).isThrownBy { - hiddenSloth1.decryptFromStorage("wrong passphrase") + hiddenSloth1.decryptFromStorage("wrong passphrase".toCharArray()) } // does not decrypt under a different passphrase with pre-computed secrets assertThatExceptionOfType(SlothDecryptionFailed::class.java).isThrownBy { - val wrongCachedSecrets = hiddenSloth1.computeCachedSecrets("wrong passphrase") + val wrongCachedSecrets = + hiddenSloth1.computeCachedSecrets("wrong passphrase".toCharArray()) hiddenSloth1.decryptFromStorageWithCachedSecrets(wrongCachedSecrets) } diff --git a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothParameterBenchmarkTest.kt b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothParameterBenchmarkTest.kt index 6c63522..33d3a79 100644 --- a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothParameterBenchmarkTest.kt +++ b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/SlothParameterBenchmarkTest.kt @@ -69,7 +69,7 @@ class SlothParameterBenchmarkTest { ) val waitingTime = measureWaitingTime { - longSloth.createNewKey(pw = "passphrase") + longSloth.createNewKey(pw = "passphrase".toCharArray()) } assertThat(waitingTime).isGreaterThan(targetDuration) assertThat(waitingTime).isLessThan(targetDuration.multipliedBy(5)) diff --git a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHashTest.kt b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHashTest.kt index 6321414..7a1a084 100644 --- a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHashTest.kt +++ b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHashTest.kt @@ -15,8 +15,8 @@ class Pbkdf2PwHashTest { @Test fun testHash_whenSameInput_thenSameOutput() { - val h1 = instance.deriveHash(salt, "test", 32) - val h2 = instance.deriveHash(salt, "test", 32) + val h1 = instance.deriveHash(salt, "test".toCharArray(), 32) + val h2 = instance.deriveHash(salt, "test".toCharArray(), 32) assertThat(h1).isEqualTo(h2) } @@ -24,7 +24,7 @@ class Pbkdf2PwHashTest { @Test fun testHash_whenInteractiveParameters_thenSucceeds() { val instance = Pbkdf2PwHash(Pbkdf2PwHashParams.INTERACTIVE) - val h1 = instance.deriveHash(salt, "test", 32) + val h1 = instance.deriveHash(salt, "test".toCharArray(), 32) assertThat(h1).isNotEmpty() } @@ -32,7 +32,7 @@ class Pbkdf2PwHashTest { @Test fun testHash_whenOwaspParameters_thenSucceeds() { val instance = Pbkdf2PwHash(Pbkdf2PwHashParams.OWASP) - val h1 = instance.deriveHash(salt, "test", 32) + val h1 = instance.deriveHash(salt, "test".toCharArray(), 32) assertThat(h1).isNotEmpty() } @@ -40,7 +40,7 @@ class Pbkdf2PwHashTest { @Test fun testHash_whenOwaspSha256Parameters_thenSucceeds() { val instance = Pbkdf2PwHash(Pbkdf2PwHashParams.OWASP_SHA256) - val h1 = instance.deriveHash(salt, "test", 32) + val h1 = instance.deriveHash(salt, "test".toCharArray(), 32) assertThat(h1).isNotEmpty() } @@ -48,25 +48,25 @@ class Pbkdf2PwHashTest { @Test fun testHash_whenLongKeyLength_thenSucceeds() { val len = 10 * 1024 * 1024 // 10 MiB - val h = instance.deriveHash(salt, "test", len) + val h = instance.deriveHash(salt, "test".toCharArray(), len) assertThat(h).hasSize(len) } @Test fun testHash_whenDifferentSalt_thenDifferentOutput() { - val h1 = instance.deriveHash(salt, "test", 32) + val h1 = instance.deriveHash(salt, "test".toCharArray(), 32) val differentSalt = instance.createSalt() - val h2 = instance.deriveHash(differentSalt, "test", 32) + val h2 = instance.deriveHash(differentSalt, "test".toCharArray(), 32) assertThat(h1).isNotEqualTo(h2) } @Test fun testHash_whenDifferentPassword_thenDifferentOutput() { - val h1 = instance.deriveHash(salt, "test", 32) - val h2 = instance.deriveHash(salt, "different", 32) + val h1 = instance.deriveHash(salt, "test".toCharArray(), 32) + val h2 = instance.deriveHash(salt, "different".toCharArray(), 32) assertThat(h1).isNotEqualTo(h2) } diff --git a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/HiddenSlothImplTest.kt b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/HiddenSlothImplTest.kt index 58cbff1..689e172 100644 --- a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/HiddenSlothImplTest.kt +++ b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/HiddenSlothImplTest.kt @@ -18,7 +18,7 @@ import java.io.File import javax.crypto.AEADBadTagException private val DEFAULT_HANDLE = "handle".encodeToByteArray() -private const val DEFAULT_PASSWORD = "password" +private val DEFAULT_PASSWORD = "password".toCharArray() @RunWith(AndroidJUnit4::class) class HiddenSlothImplTest { @@ -121,7 +121,7 @@ class HiddenSlothImplTest { instance.init(storage, DEFAULT_HANDLE) instance.encrypt(storage, DEFAULT_PASSWORD, data) - instance.decrypt(storage, "wrong passphrase") + instance.decrypt(storage, "wrong passphrase".toCharArray()) } @Test(expected = AEADBadTagException::class) diff --git a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/LongSlothImplTest.kt b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/LongSlothImplTest.kt index 06d6fb3..2b7b09e 100644 --- a/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/LongSlothImplTest.kt +++ b/android/sloth/src/androidTest/java/com/lambdapioneer/sloth/impl/LongSlothImplTest.kt @@ -6,6 +6,7 @@ import com.lambdapioneer.sloth.crypto.Pbkdf2PwHash import com.lambdapioneer.sloth.storage.OnDiskStorage import com.lambdapioneer.sloth.testing.createSecureElementOrSkip import com.lambdapioneer.sloth.utils.secureRandomBytes +import com.lambdapioneer.sloth.utils.secureRandomChars import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -13,7 +14,7 @@ import org.junit.runner.RunWith import java.io.File private val DEFAULT_HANDLE = "handle".encodeToByteArray() -private const val DEFAULT_PASSWORD = "password" +private val DEFAULT_PASSWORD = "password".toCharArray() private const val DEFAULT_OUTPUT_LENGTH = 32 @RunWith(AndroidJUnit4::class) @@ -51,7 +52,7 @@ class LongSlothImplTest { @Test fun whenKeyGenWithLongPassword_thenYieldSameKey() { val instance = createInstance() - val pw = secureRandomBytes(255).decodeToString() + val pw = secureRandomChars(255) val k1 = instance.keyGen( storage = storage, @@ -82,7 +83,7 @@ class LongSlothImplTest { val k2 = instance.derive( storage = storage, - pw = "not the default password", + pw = "not the default password".toCharArray(), outputLengthBytes = DEFAULT_OUTPUT_LENGTH ) diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/HiddenSloth.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/HiddenSloth.kt index bd95b37..e9e3431 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/HiddenSloth.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/HiddenSloth.kt @@ -75,7 +75,7 @@ class HiddenSloth internal constructor( * [identifier]. The storage must have been previous initialized using [onAppStart]. */ fun encryptToStorage( - pw: String, + pw: CharArray, data: ByteArray, ) { check(isInitialized) @@ -92,7 +92,7 @@ class HiddenSloth internal constructor( * (b) the password is wrong, or (c) the storage has been tampered with. */ fun decryptFromStorage( - pw: String, + pw: CharArray, ): ByteArray { check(isInitialized) val namespacedStorage = storage.getOrCreateNamespace(identifier) @@ -112,7 +112,7 @@ class HiddenSloth internal constructor( * calls to [encryptToStorageWithCachedSecrets] and [decryptFromStorageWithCachedSecrets]. */ fun computeCachedSecrets( - pw: String, + pw: CharArray, authenticateStorage: Boolean = true, ): HiddenSlothCachedSecrets { check(isInitialized) diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/LongSloth.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/LongSloth.kt index 7bdf5ef..a8c0017 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/LongSloth.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/LongSloth.kt @@ -37,7 +37,7 @@ class LongSloth internal constructor( * @param outputLengthBytes The length of the output key in bytes. Defaults to 32 bytes. */ fun createNewKey( - pw: String, + pw: CharArray, outputLengthBytes: Int = 32, ): ByteArray { val namespacedHandle = identifierToHandle(identifier) @@ -58,7 +58,7 @@ class LongSloth internal constructor( * @param outputLengthBytes The length of the output key in bytes. Defaults to 32 bytes. */ fun deriveForExistingKey( - pw: String, + pw: CharArray, outputLengthBytes: Int = 32, ): ByteArray { val namespacedStorage = storage.getOrCreateNamespace(identifier) diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHash.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHash.kt index 3ecebe6..808b1ce 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHash.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/Pbkdf2PwHash.kt @@ -15,12 +15,12 @@ class Pbkdf2PwHash( override fun deriveHash( salt: ByteArray, - password: String, + password: CharArray, outputLengthInBytes: Int, ): ByteArray { val factory = SecretKeyFactory.getInstance(params.algorithm) val keySpec = PBEKeySpec( - /* password = */ password.toCharArray(), + /* password = */ password, /* salt = */ salt, /* iterationCount = */ params.iterationCount, /* keyLength = */ 8 * outputLengthInBytes diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/PwHash.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/PwHash.kt index ad3414b..5df78cd 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/PwHash.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/crypto/PwHash.kt @@ -20,5 +20,5 @@ interface PwHash { * @param password The password to use for the hash function. * @param outputLengthInBytes The length of the output hash in bytes. */ - fun deriveHash(salt: ByteArray, password: String, outputLengthInBytes: Int): ByteArray + fun deriveHash(salt: ByteArray, password: CharArray, outputLengthInBytes: Int): ByteArray } diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothImpl.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothImpl.kt index 5357b58..1945045 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothImpl.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothImpl.kt @@ -16,6 +16,7 @@ import com.lambdapioneer.sloth.utils.NoopTracer import com.lambdapioneer.sloth.utils.Tracer import com.lambdapioneer.sloth.utils.ceilOfIntegerDivision import com.lambdapioneer.sloth.utils.secureRandomBytes +import com.lambdapioneer.sloth.utils.secureRandomChars import java.nio.ByteBuffer import java.util.* import javax.crypto.AEADBadTagException @@ -65,7 +66,7 @@ class HiddenSlothImpl( tracer: Tracer = NoopTracer(), ) { // Lambda is given in bits, but we need bytes for the key length - private val slothKeyLenInBytes = ceilOfIntegerDivision(params.lambda, 8) + private val slothKeyLenInBytes = ceilOfIntegerDivision(params.longSlothParams.lambda, 8) private val longSloth = LongSlothImpl( params = params.longSlothParams, @@ -98,7 +99,8 @@ class HiddenSlothImpl( secureElement.aesCtrGenKey(KeyHandle(hDems)) - val pw = secureRandomBytes(slothKeyLenInBytes).decodeToString() + // if the storage does not exist, we create a new key under a randomly chosen passphrase + val pw = secureRandomChars(entropy = params.longSlothParams.lambda.toDouble()) encrypt(storage, pw, ByteArray(0)) tracer.finish() @@ -129,7 +131,7 @@ class HiddenSlothImpl( */ fun encrypt( storage: WriteableStorage, - pw: String?, + pw: CharArray?, data: ByteArray, tracer: Tracer = NoopTracer(), cachedSecrets: HiddenSlothCachedSecrets? = null, @@ -204,7 +206,7 @@ class HiddenSlothImpl( * Prepares [HiddenSlothCachedSecrets] that can be used with [#decrypt] to speed up repeated access to * the ciphertext. */ - fun computeCachedSecrets(storage: ReadableStorage, pw: String): HiddenSlothCachedSecrets { + fun computeCachedSecrets(storage: ReadableStorage, pw: CharArray): HiddenSlothCachedSecrets { val k = dessDeriveKey(storage, pw) return HiddenSlothCachedSecrets(k = k) } @@ -220,7 +222,7 @@ class HiddenSlothImpl( */ fun decrypt( storage: ReadableStorage, - pw: String?, + pw: CharArray?, tracer: Tracer = NoopTracer(), cachedSecrets: HiddenSlothCachedSecrets? = null, decryptionOffsetAndLength: OffsetAndLength? = null, @@ -335,7 +337,8 @@ class HiddenSlothImpl( * Initializes the DESS scheme. This method must be called before any other DESS method. */ private fun dessInit(storage: WriteableStorage, h: ByteArray) { - val pw = secureRandomBytes(params.lambda).decodeToString() + // if the storage does not exist, we create a new key under a randomly chosen passphrase + val pw = secureRandomChars(entropy = params.longSlothParams.lambda.toDouble()) @Suppress("UNUSED_VARIABLE") val k = longSloth.keyGen(storage, pw, h, slothKeyLenInBytes) @@ -349,7 +352,7 @@ class HiddenSlothImpl( */ private fun dessDeriveKey( storage: ReadableStorage, - pw: String, + pw: CharArray, ) = longSloth.derive(storage = storage, pw = pw, outputLengthBytes = slothKeyLenInBytes) @Suppress("ArrayInDataClass") @@ -361,7 +364,7 @@ class HiddenSlothImpl( */ private fun dessEncrypt( storage: ReadableStorage, - pw: String?, + pw: CharArray?, data: ByteArray, cachedSecrets: HiddenSlothCachedSecrets?, ): DessEncryptionResult { @@ -395,7 +398,7 @@ class HiddenSlothImpl( @Suppress("UsePropertyAccessSyntax") private fun dessDecrypt( storage: ReadableStorage, - pw: String?, + pw: CharArray?, iv: ByteArray, blobAndTag: ByteArray, cachedSecrets: HiddenSlothCachedSecrets? = null, diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothParams.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothParams.kt index ec68939..00e5bac 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothParams.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/HiddenSlothParams.kt @@ -6,9 +6,4 @@ package com.lambdapioneer.sloth.impl data class HiddenSlothParams( internal val payloadMaxLength: Int, internal val longSlothParams: LongSlothParams = LongSlothParams(), - internal val lambda: Int = SECURITY_PARAMETER_LAMBDA_DEFAULT, -) { - companion object { - const val SECURITY_PARAMETER_LAMBDA_DEFAULT = 128 - } -} +) diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothImpl.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothImpl.kt index 1561851..ab8c55f 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothImpl.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothImpl.kt @@ -15,6 +15,7 @@ import com.lambdapioneer.sloth.storage.WriteableStorage import com.lambdapioneer.sloth.utils.NoopTracer import com.lambdapioneer.sloth.utils.Tracer import com.lambdapioneer.sloth.utils.secureRandomBytes +import com.lambdapioneer.sloth.utils.secureRandomChars /** * Keys for the values persisted in storage. @@ -48,7 +49,7 @@ class LongSlothImpl( storage.updateAllLastModifiedTimestamps() } else { // if the storage does not exist, we create a new key under a randomly chosen passphrase - val randomPassphrase = secureRandomBytes(32).decodeToString() + val randomPassphrase = secureRandomChars(entropy = params.lambda.toDouble()) keyGen( storage = storage, pw = randomPassphrase, @@ -60,7 +61,7 @@ class LongSlothImpl( fun keyGen( storage: WriteableStorage, - pw: String, + pw: CharArray, h: ByteArray, outputLengthBytes: Int, ): ByteArray { @@ -72,7 +73,7 @@ class LongSlothImpl( return derive(storage, pw, outputLengthBytes) } - fun derive(storage: ReadableStorage, pw: String, outputLengthBytes: Int): ByteArray { + fun derive(storage: ReadableStorage, pw: CharArray, outputLengthBytes: Int): ByteArray { tracer.addKeyValue("l", params.l.toString()) tracer.addKeyValue("argon", pwHash.toString()) diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothParams.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothParams.kt index f1f6b21..49f8df1 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothParams.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/impl/LongSlothParams.kt @@ -3,14 +3,21 @@ package com.lambdapioneer.sloth.impl import com.lambdapioneer.sloth.utils.ceilToNextKiB /** - * The LongSloth parameters as defined in the paper. The L parameter is rounded up to the next KiB. + * The LongSloth parameters as defined in the paper. * - * See [SlothLib.determineParameter] as a practical way to determine a suitable L parameter. + * The [l] parameter is rounded up to the next KiB. See [SlothLib.determineParameter] as a practical + * way to determine a suitable L parameter. + * + * The [lambda] parameter is the overall security parameter and usually set to 128. */ -class LongSlothParams(l: Int = SECURITY_PARAMETER_L_DEFAULT) { - val l = ceilToNextKiB(l) +class LongSlothParams( + l: Int = SECURITY_PARAMETER_L_DEFAULT, + internal val lambda: Int = SECURITY_PARAMETER_LAMBDA_DEFAULT +) { + internal val l = ceilToNextKiB(l) companion object { const val SECURITY_PARAMETER_L_DEFAULT = 100 * 1024 + const val SECURITY_PARAMETER_LAMBDA_DEFAULT = 128 } } diff --git a/android/sloth/src/main/java/com/lambdapioneer/sloth/utils/ByteArrayExtensions.kt b/android/sloth/src/main/java/com/lambdapioneer/sloth/utils/ByteArrayExtensions.kt index f3ac67c..2dab1fc 100644 --- a/android/sloth/src/main/java/com/lambdapioneer/sloth/utils/ByteArrayExtensions.kt +++ b/android/sloth/src/main/java/com/lambdapioneer/sloth/utils/ByteArrayExtensions.kt @@ -2,6 +2,9 @@ package com.lambdapioneer.sloth.utils import java.nio.ByteBuffer import java.security.SecureRandom +import kotlin.math.ceil +import kotlin.math.log2 +import kotlin.math.roundToInt /** * Creates a new byte array of random bytes sourced from [SecureRandom]. @@ -15,6 +18,49 @@ fun secureRandomBytes(length: Int): ByteArray { return result } +/** + * Creates a new array of random Char elements sourced from [SecureRandom.nextChar] of the + * given [length]. + */ +fun secureRandomChars(length: Int): CharArray { + require(length >= 0) + val secureRandom = SecureRandom() + + return CharArray(length) { secureRandom.nextChar() } +} + +/** + * Creates a new array of random Char elements sourced from [SecureRandom.nextChar] of a given + * total entropy assuming 15.954 bits per character. + */ +fun secureRandomChars(entropy: Double): CharArray { + require(entropy > 0) + + val bitsPerChar = 15.594 // log2(2^16 - 2048) = 15.954 + eps + val length = ceil(entropy / bitsPerChar).toInt() + + return secureRandomChars(length = length) +} + +/** + * Returns a [Char] element chosen uniformly at random from all single code units excluding the + * surrogate range (0xD800..0xDFFF). This ensures that an array of results from this function + * always represents a valid string. + * + * Since we exclude a range of 2048 (0x800) characters, the total entropy per character is just + * log2(2^16 - 2^11) ~= 15.954... + */ +fun SecureRandom.nextChar(): Char { + val surrogateRangeSize = Char.MAX_SURROGATE.toInt() - Char.MIN_SURROGATE.toInt() + 1 + val i = nextInt(Char.MAX_VALUE.toInt() - surrogateRangeSize) + + return if (i >= Char.MIN_SURROGATE.toInt()) { + (i + surrogateRangeSize).toChar() + } else { + i.toChar() + } +} + /** * Decodes the given hex string as a byte array. */