lambdapioneer / argon2kt

An Android/Kotlin binding for the Argon2 hash
MIT License
79 stars 8 forks source link

Some suggestions about the library API #3

Open Fi5t opened 4 years ago

Fi5t commented 4 years ago

Hello! I'm really glad to see an Android version of the Argon2 library. I wanted to do it myself, but I haven't found enough time for it. Thanks for having done it for me 😉

However, when I started using this library I found out, that some API weren't very convenient to use. For instance:

  1. Passing a mode parameter to the verify() method looks redundant because it can be elicited from a hash.
val hashResult : Argon2KtResult = argon2Kt.hash(
  mode = Argon2Mode.ARGON2_I,
 ....
)

val verificationResult : Boolean = argon2Kt.verify(
  mode = Argon2Mode.ARGON2_I,
  ...
)
  1. The verify() method accepts only the string representation of the hash. It would be better to add an additional version of this method that would accept the byte array representation of the hash. It's very convenient to use it when reading hash from a file.
val hashResult : Argon2KtResult = argon2Kt.hash(
  mode = Argon2Mode.ARGON2_I,
 ....
)

storage.openFileOutput().use {
  it.write(hash.encodedOutputAsByteArray())
}

...

val storedHash = file.openFileInput().use { it.readBytes() }

val verificationResult : Boolean = argon2Kt.verify(
  mode = Argon2Mode.ARGON2_I,
  encoded = storedHash,
  password = passwordByteArray,
)
  1. It would be nice to have an ability to reconstruct Argon2KtResult from the String/ByteArray representation. I have no idea how to do it now without using reflection.

Maybe I'll face some other things later, but these things are the most important ones for me. I understand that you may not have time to make these API changes, but I can send you PR with my own implementation. What do you think?

lambdapioneer commented 4 years ago

Hi @Fi5t , I'm very glad that my library is helpful for you. Thanks so much for trying it out and the suggestions :)

Absolutely happy to review pull-requests for all of these (ideally a separate one for each point). The suggested changes look very sensible!

1. Sounds great. And it seems that this improvement is even applicable to the underlying argon2 library. See encoding.c:

 *  $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>$<bin>$<bin>
[...]
int decode_string(argon2_context *ctx, const char *str, argon2_type type) {

The proper fix would be to update it there and adding the type to the argon2_context. But I am also happy to diverge with Argon2Kt here (either in Kotlin or the C++ code) to save time. Might be worth creating an issue in the main project afterwards though.

2. Yes, more convenience is always good. I'd suggest that we go all the way and have each choice (String, ByteArray, ByteBuffer) for both the encoded and password parameter.

3. I am not sure what you mean with reflection. I'd suggest to add a Java_com_lambdapioneer_argon2kt_Argon2Jni_nativeArgon2decodeString in the Argon2Jni.cpp that calls the decode_string string method of the C library to extract the raw hash. Happy to look into it the next week if it's important for you.

Fi5t commented 4 years ago

I think that the core agron2 library is very "low-level" and it can make sense for such types of libraries API. But libraries intended to the client code need to have a "high-level" API. So, my suggestion about the simplification of the verify() method might make sense only for your library. I've already implemented this feature in my library, which uses your library.

Here is my approach:

private val hashModeRegex = Regex("""argon2(i?d?)""")

....

return argon2instance.verify(
    parseHashMode(hashAsString),
    hashAsString,
    passphraseOrPin
)

....

private fun parseHashMode(encodedHash: String): Argon2Mode {
    val res = hashModeRegex.find(encodedHash)

    return when (res?.value) {
        "argon2i" -> Argon2Mode.ARGON2_I
        "argon2d" -> Argon2Mode.ARGON2_D
        "argon2id" -> Argon2Mode.ARGON2_ID
        else -> throw BadHashException()
    }
}

Decoding a hash to Argon2KtResult is not a mission-critical feature for me. It would be nice to have, no more.

lambdapioneer commented 4 years ago

Sounds good. Just one quick suggestions, by using Regex("""^\${'$'}argon2(i?d?)""") we ensure that we only match it when it's at the beginning of the line -- see the extra ^ for begin of line and then the ugly magic for escaping the $ sign.

I'm also happy with just three String.startsWith("$argo ... calls as the when conditions. I feel simplicity wins here and compared to the main algorithm our computations are basically non-existent.