Implemented hashing passwords on iOS
This commit is contained in:
parent
2809a4b149
commit
74e144de59
|
@ -1,19 +1,37 @@
|
||||||
package net.codinux.banking.ui.service
|
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 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 {
|
actual object AuthenticationService {
|
||||||
|
|
||||||
// for iOS see e.g.
|
private const val SaltLength = 16
|
||||||
// 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 SaltAndHashSeparator = '$'
|
||||||
|
|
||||||
actual fun hashPassword(password: String): String {
|
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 {
|
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"))
|
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()
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue