From fe4134a65f77a366e2376087909ecc071f02409b Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Mon, 21 Sep 2020 22:13:36 +0200 Subject: [PATCH] Added java implementation of _pwhash_ --- .../kotlin/crypto/pwhash/PasswordHashTest.kt | 70 ++++++++------ .../kotlin/crypto/pwhash/PasswordHash.kt | 94 +++++++++++++++++++ 2 files changed, 134 insertions(+), 30 deletions(-) create mode 100644 multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt 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 index 7f463c8..942ac78 100644 --- 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 @@ -1,5 +1,6 @@ package com.ionspin.kotlin.crypto.pwhash +import com.ionspin.kotlin.crypto.LibsodiumInitializer import com.ionspin.kotlin.crypto.util.toHexString import kotlin.random.Random import kotlin.random.nextUBytes @@ -14,44 +15,53 @@ import kotlin.test.assertTrue 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()}") + LibsodiumInitializer.initializeWithCallback { + 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()}") + assertTrue { + hashedPassword.toHexString().equals("e762ee529e90e3bbc242c23e8e2f963ab9a17ed9e79f89a00c71261a979207b2213cc" + + "0330c53f410a9c8933c46e8642dc542efc0660c69e255b601c7244ef6b0") + } + } } @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 + LibsodiumInitializer.initializeWithCallback { + 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.strNeedsRehash(hashedPassword, crypto_pwhash_OPSLIMIT_MIN, crypto_pwhash_MEMLIMIT_MIN) == 0 - } + assertTrue { + PasswordHash.strVerify( + hashedPassword, + password + ) + } - assertTrue { - PasswordHash.strNeedsRehash(hashedPassword, crypto_pwhash_OPSLIMIT_MIN, crypto_pwhash_MEMLIMIT_SENSITIVE) == 1 - } + assertTrue { + PasswordHash.strNeedsRehash(hashedPassword, crypto_pwhash_OPSLIMIT_MIN, crypto_pwhash_MEMLIMIT_MIN) == 0 + } - //TODO strNeedsRehash -1 case? + 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/jvmMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt b/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt new file mode 100644 index 0000000..93999ca --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt @@ -0,0 +1,94 @@ +package com.ionspin.kotlin.crypto.pwhash + +import com.ionspin.kotlin.crypto.LibsodiumInitializer.sodium +import com.sun.jna.NativeLong + +actual object PasswordHash { + /** + * The crypto_pwhash() function derives an outlen bytes long key from a password passwd whose length is passwdlen + * and a salt salt whose fixed length is crypto_pwhash_SALTBYTES bytes. passwdlen should be at least crypto_pwhash_ + * PASSWD_MIN and crypto_pwhash_PASSWD_MAX. outlen should be at least crypto_pwhash_BYTES_MIN = 16 (128 bits) and + * at most crypto_pwhash_BYTES_MAX. + * + * See https://libsodium.gitbook.io/doc/password_hashing/default_phf for more details + */ + actual fun pwhash( + outputLength: Int, + password: String, + salt: UByteArray, + opsLimit: ULong, + memLimit: Int, + algorithm: Int + ): UByteArray { + val hashedPassword = UByteArray(outputLength) + + sodium.crypto_pwhash( + hashedPassword.asByteArray(), + outputLength.toLong(), + password.encodeToByteArray(), + password.length.toLong(), + salt.asByteArray(), + opsLimit.toLong(), + NativeLong(memLimit.toLong()), + algorithm + ) + + return hashedPassword + } + + /** + * The crypto_pwhash_str() function puts an ASCII encoded string into out, which includes: + * the result of a memory-hard, CPU-intensive hash function applied to the password passwd of length passwdlen + * the automatically generated salt used for the previous computation + * the other parameters required to verify the password, including the algorithm identifier, its version, opslimit and memlimit. + * out must be large enough to hold crypto_pwhash_STRBYTES bytes, but the actual output string may be shorter. + * The output string is zero-terminated, includes only ASCII characters and can be safely stored into SQL databases + * 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): UByteArray { + val output = ByteArray(crypto_pwhash_STRBYTES) + sodium.crypto_pwhash_str( + output, + password.encodeToByteArray(), + password.length.toLong(), + opslimit.toLong(), + NativeLong(memlimit.toLong()) + ) + return output.asUByteArray() + } + + /** + * Check if a password verification string str matches the parameters opslimit and memlimit, and the current default algorithm. + * The function returns 1 if the string appears to be correct, but doesn't match the given parameters. In that situation, applications may want to compute a new hash using the current parameters the next time the user logs in. + * 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. + */ + actual fun strNeedsRehash( + passwordHash: UByteArray, + opslimit: ULong, + memlimit: Int + ): Int { + return sodium.crypto_pwhash_str_needs_rehash( + passwordHash.asByteArray(), + opslimit.toLong(), + NativeLong(memlimit.toLong()) + ) + } + + /** + * 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 = sodium.crypto_pwhash_str_verify( + passwordHash.asByteArray(), + password.encodeToByteArray(), + password.length.toLong() + ) + + return result == 0 + } + +}