Implemented hashing passwords on iOS
This commit is contained in:
parent
2809a4b149
commit
74e144de59
|
@ -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()
|
||||
|
||||
}
|
Loading…
Reference in New Issue