diff --git a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/pwhash/PasswordHash.kt b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/pwhash/PasswordHash.kt index 8fe6e9d..75f5ab8 100644 --- a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/pwhash/PasswordHash.kt +++ b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/pwhash/PasswordHash.kt @@ -7,25 +7,25 @@ package com.ionspin.kotlin.crypto.pwhash const val crypto_pwhash_BYTES_MIN = 16U const val crypto_pwhash_MEMLIMIT_INTERACTIVE = 67108864U -const val crypto_pwhash_MEMLIMIT_MIN = 8192U -const val crypto_pwhash_MEMLIMIT_MODERATE = 268435456U -const val crypto_pwhash_MEMLIMIT_SENSITIVE = 1073741824U -const val crypto_pwhash_OPSLIMIT_INTERACTIVE = 2U -const val crypto_pwhash_OPSLIMIT_MAX = 4294967295U -const val crypto_pwhash_OPSLIMIT_MIN = 1U -const val crypto_pwhash_OPSLIMIT_MODERATE = 3U -const val crypto_pwhash_OPSLIMIT_SENSITIVE = 4U -const val crypto_pwhash_PASSWD_MAX = 4294967295U -const val crypto_pwhash_PASSWD_MIN = 0U -const val crypto_pwhash_SALTBYTES = 16U -const val crypto_pwhash_STRBYTES = 128U +const val crypto_pwhash_MEMLIMIT_MIN = 8192 +const val crypto_pwhash_MEMLIMIT_MODERATE = 268435456 +const val crypto_pwhash_MEMLIMIT_SENSITIVE = 1073741824 +const val crypto_pwhash_OPSLIMIT_INTERACTIVE = 2 +const val crypto_pwhash_OPSLIMIT_MAX = 4294967295UL +const val crypto_pwhash_OPSLIMIT_MIN = 1UL +const val crypto_pwhash_OPSLIMIT_MODERATE = 3UL +const val crypto_pwhash_OPSLIMIT_SENSITIVE = 4UL +const val crypto_pwhash_PASSWD_MAX = 4294967295 +const val crypto_pwhash_PASSWD_MIN = 0 +const val crypto_pwhash_SALTBYTES = 16 +const val crypto_pwhash_STRBYTES = 128 const val crypto_pwhash_STRPREFIX = "\$argon2id$" val crypto_pwhash_argon2id_ALG_ARGON2ID13 = 2 val crypto_pwhash_argon2i_ALG_ARGON2I13 = 1 val crypto_pwhash_ALG_DEFAULT = crypto_pwhash_argon2id_ALG_ARGON2ID13 -class PasswordHashFailed() : RuntimeException("Password hashing failed") +class PasswordHashingFailed() : RuntimeException("Password hashing failed") expect object PasswordHash { /** @@ -55,7 +55,7 @@ expect object PasswordHash { * and other data stores. No extra information has to be stored in order to verify the password. * The function returns 0 on success and -1 if it didn't complete successfully. */ - fun str(password: String, opslimit: ULong, memlimit: Int): String + fun str(password: String, opslimit: ULong, memlimit: Int): UByteArray /** * Check if a password verification string str matches the parameters opslimit and memlimit, and the current default algorithm. @@ -63,6 +63,13 @@ expect object PasswordHash { * The function returns 0 if the parameters already match the given ones. * It returns -1 on error. If it happens, applications may want to compute a correct hash the next time the user logs in. */ - fun strNeedsRehash(password: String, opslimit: ULong, memlimit: Int): Boolean - fun strVerify(passwordHash: String, password: UByteArray): Boolean -} \ No newline at end of file + fun strNeedsRehash(passwordHash: UByteArray, opslimit: ULong, memlimit: Int): Int + + /** + * his function verifies that str is a valid password verification string (as generated by crypto_pwhash_str()) for passwd whose length is passwdlen. + * str has to be zero-terminated. + * It returns 0 if the verification succeeds, and -1 on error. + */ + fun strVerify(passwordHash: UByteArray, password: String): Boolean + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHashTest.kt b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHashTest.kt new file mode 100644 index 0000000..7f463c8 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHashTest.kt @@ -0,0 +1,57 @@ +package com.ionspin.kotlin.crypto.pwhash + +import com.ionspin.kotlin.crypto.util.toHexString +import kotlin.random.Random +import kotlin.random.nextUBytes +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2020 + */ +class PasswordHashTest { + @Test + fun testPasswordHash() { + val randomBytes = Random(0).nextUBytes(crypto_pwhash_SALTBYTES) + val password = "correct horse battery staple" + val hashedPassword = PasswordHash.pwhash( + 64, + password, + randomBytes, + crypto_pwhash_OPSLIMIT_MIN, + crypto_pwhash_MEMLIMIT_MIN, + crypto_pwhash_ALG_DEFAULT + ) + println("Hashed password: ${hashedPassword.toHexString()}") + } + + @Test + fun testPasswordHashForStorage() { + val password = "correct horse battery staple" + val hashedPassword = PasswordHash.str( + password, + crypto_pwhash_OPSLIMIT_MIN, + crypto_pwhash_MEMLIMIT_MIN + ) + println("Hashed password for storage: ${hashedPassword.toHexString()}") + + assertTrue { + PasswordHash.strVerify( + hashedPassword, + password + ) + } + + assertTrue { + PasswordHash.strNeedsRehash(hashedPassword, crypto_pwhash_OPSLIMIT_MIN, crypto_pwhash_MEMLIMIT_MIN) == 0 + } + + assertTrue { + PasswordHash.strNeedsRehash(hashedPassword, crypto_pwhash_OPSLIMIT_MIN, crypto_pwhash_MEMLIMIT_SENSITIVE) == 1 + } + + //TODO strNeedsRehash -1 case? + } +} diff --git a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt index 1e5bf37..20a4381 100644 --- a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt +++ b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt @@ -4,9 +4,11 @@ import com.ionspin.kotlin.crypto.util.toPtr import kotlinx.cinterop.addressOf import kotlinx.cinterop.convert import kotlinx.cinterop.pin +import kotlinx.cinterop.toCValues import libsodium.crypto_pwhash import libsodium.crypto_pwhash_str import libsodium.crypto_pwhash_str_needs_rehash +import libsodium.crypto_pwhash_str_verify actual object PasswordHash { /** @@ -30,7 +32,7 @@ actual object PasswordHash { val hashedPasswordPinned = hashedPassword.pin() val saltPinned = salt.pin() - crypto_pwhash( + val hashingResult = crypto_pwhash( hashedPasswordPinned.toPtr(), outputLength.convert(), password, @@ -42,6 +44,9 @@ actual object PasswordHash { ) saltPinned.unpin() hashedPasswordPinned.unpin() + if (hashingResult != 0) { + throw PasswordHashingFailed() + } return hashedPassword } @@ -56,8 +61,8 @@ actual object PasswordHash { * and other data stores. No extra information has to be stored in order to verify the password. * The function returns 0 on success and -1 if it didn't complete successfully. */ - actual fun str(password: String, opslimit: ULong, memlimit: Int): String { - val output = ByteArray(crypto_pwhash_STRBYTES.toInt()) + actual fun str(password: String, opslimit: ULong, memlimit: Int): UByteArray { + val output = ByteArray(crypto_pwhash_STRBYTES) val outputPinned = output.pin() crypto_pwhash_str( outputPinned.addressOf(0), @@ -68,7 +73,7 @@ actual object PasswordHash { ) outputPinned.unpin() - return output.decodeToString() + return output.asUByteArray() } /** @@ -78,20 +83,32 @@ actual object PasswordHash { * It returns -1 on error. If it happens, applications may want to compute a correct hash the next time the user logs in. */ actual fun strNeedsRehash( - password: String, + passwordHash: UByteArray, opslimit: ULong, memlimit: Int - ): Boolean { - val password = password.encodeToByteArray() - crypto_pwhash_str_needs_rehash( - , + ): Int { + val result = crypto_pwhash_str_needs_rehash( + passwordHash.asByteArray().toCValues(), opslimit, memlimit.convert() ) + return result + + } - actual fun strVerify(passwordHash: String, password: UByteArray): Boolean { - TODO("Not yet implemented") + /** + * his function verifies that str is a valid password verification string (as generated by crypto_pwhash_str()) for passwd whose length is passwdlen. + * str has to be zero-terminated. + * It returns 0 if the verification succeeds, and -1 on error. + */ + actual fun strVerify(passwordHash: UByteArray, password: String): Boolean { + val result = crypto_pwhash_str_verify( + passwordHash.asByteArray().toCValues(), + password, + password.length.convert() + ) + return result == 0 } -} \ No newline at end of file +} diff --git a/test b/test new file mode 100644 index 0000000..e69de29