Implemented hashing passwords on iOS

This commit is contained in:
dankito 2024-09-24 05:16:45 +02:00
parent 2809a4b149
commit 74e144de59
1 changed files with 86 additions and 5 deletions

View File

@ -1,19 +1,37 @@
package net.codinux.banking.ui.service
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.refTo
import net.codinux.banking.ui.model.AuthenticationResult
import platform.CoreCrypto.CCCalibratePBKDF
import platform.CoreCrypto.CCKeyDerivationPBKDF
import platform.CoreCrypto.CC_SHA512
import platform.CoreCrypto.CC_SHA512_DIGEST_LENGTH
import platform.CoreCrypto.kCCPBKDF2
import platform.CoreCrypto.kCCPRFHmacAlgSHA256
import platform.Security.SecRandomCopyBytes
import platform.Security.kSecRandomDefault
@OptIn(ExperimentalForeignApi::class, ExperimentalStdlibApi::class)
actual object AuthenticationService {
// for iOS see e.g.
// https://medium.com/@mohamed.ma872/strengthening-mobile-app-security-pbkdf2-bcrypt-and-scrypt-for-android-and-ios-8089b0edbf76
// https://github.com/felipeflorencio/BCryptSwift
private const val SaltLength = 16
private const val SaltAndHashSeparator = '$'
actual fun hashPassword(password: String): String {
return password // TODO
val salt = generateRandomSalt(SaltLength)
val derivedKey = hashPassword(password, salt)
return toString(salt) + SaltAndHashSeparator + derivedKey
}
actual fun checkPassword(password: String, hashedPassword: String): Boolean {
return password == hashedPassword // TODO
val (salt, hash) = hashedPassword.split(SaltAndHashSeparator)
return hashPassword(password, salt.hexToUByteArray()) == hash
}
@ -23,4 +41,67 @@ actual object AuthenticationService {
authenticationResult(AuthenticationResult(false, "Biometrics is not implemented yet"))
}
private fun generateRandomSalt(length: Int = 16): UByteArray {
val salt = UByteArray(length)
memScoped {
val status = SecRandomCopyBytes(kSecRandomDefault, length.toULong(), salt.refTo(0))
if (status != 0) {
throw IllegalStateException("Failed to generate random salt, status: $status")
}
}
return salt
}
private fun hashPassword(password: String, salt: UByteArray): String {
val derivedKey = deriveKeyPBKDF2(password, salt)
return toString(derivedKey)
}
private fun deriveKeyPBKDF2(password: String, salt: UByteArray, iterations: Int = 500_000, keyLength: Int = 32): UByteArray {
val derivedKey = UByteArray(keyLength)
memScoped {
val status = CCKeyDerivationPBKDF(
kCCPBKDF2,
password,
password.length.toULong(),
salt.refTo(0),
salt.size.toULong(),
kCCPRFHmacAlgSHA256,
iterations.toUInt(),
derivedKey.refTo(0),
keyLength.toULong()
)
// Check if the derivation succeeded
if (status != 0) {
throw IllegalStateException("Key derivation failed with status $status")
}
}
return derivedKey
}
private fun generateSha512(password: String): UByteArray {
val data = password.encodeToByteArray()
val hash = UByteArray(CC_SHA512_DIGEST_LENGTH)
memScoped {
val result = CC_SHA512(
data.refTo(0),
data.size.toUInt(),
hash.refTo(0)
)
checkNotNull(result) { "Hash computation failed" }
}
return hash
}
private fun toString(bytes: UByteArray): String = bytes.toHexString()
}