Skip to content

Commit

Permalink
Android: prefer CharArray over String
Browse files Browse the repository at this point in the history
  • Loading branch information
lambdapioneer committed Feb 23, 2024
1 parent 73bc771 commit 4552b92
Show file tree
Hide file tree
Showing 17 changed files with 133 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Expand All @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
}
Expand All @@ -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
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,58 +15,58 @@ 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)
}

@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()
}

@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()
}

@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()
}

@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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ 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
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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit 4552b92

Please sign in to comment.