godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 86 forks source link

Support PBKDF2 key derivation function. (pkcs5) #3293

Open Marzin-bot opened 3 years ago

Marzin-bot commented 3 years ago

Describe the project you are working on

I am currently working on a PostgreSQL client which allows Godot and PostgreSQL to interface with GDScript. This allows SQL queries to be executed on a PostgreSQL backend from GDScript.

Describe the problem or limitation you are having in your project

During the process of connecting to the Postgres database, the server offers several authentication methods to establish the connection between godot and Postgres. Usually the backend will ask the fontend to hash the password in md5 before sending it back. However, this authentication method should no longer be used as it is based on md5 which is not currently recommended anyway.

The new version of postgres upgrades the authentication method and goes through the authentication mechanisms using public standards: SASL (RFC 4422) and SCRAM (RFC 5802 and RFC 7677).

It is a fairly simple authentication mechanism to implement in GDScript but the problem is that this authentication mechanism internally uses the PBKDF2 key derivation function.

The one if is not implemented in Godot which prevents me from properly supporting Postgres 13 and above.

The use of PBKDF2 is of interest in applications other than in the Postgres protocol, in particular in the Mongodb, MySQL, XMPP protocol (I am thinking in particular of this project https://invent.kde.org/woltherav/godot-xmpp-client , He is having the same problem as me because XMPP requires SASL / SCRAM support).

In fact, it is really necessary to support PBKDF2 in order to be able to implement a wide range of protocols in GDScript.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

It's simple, just implement the PBKDF2 key derivation function.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Do something similar to python for example: https://docs.python.org/3/library/hashlib.html#hashlib.pbkdf2_hmac .

In any case it should be at least implemented in the following form: pbkdf2('sha256', password, salt, iteration).

And maybe implement it in the crypto class.

If this enhancement will not be used often, can it be worked around with a few lines of script?

A few lines, certainly not. And anyway, writing this function in pure GDScript will make it way too long to run.

Is there a reason why this should be core and not an add-on in the asset library?

This is essential the same way other cryptographic features are currently implemented.

Faless commented 3 years ago

As far as I understand PBKDF2 only needs presudo-random bytes generation and HMAC/Hash (which we support), then, deriving the key should be a matter of XOR and concatenation (the function is quite simple). I.e. implementing it in GDScript shouldn't be too painful. That said, given it's common enough, we could as well expose the mbedtls version I guess.

Marzin-bot commented 3 years ago

While waiting for your answer I have also studied the algorithm and it turns out that it is indeed very simple. I have successfully implemented PBKDF2 in GDScript but it takes a long time to run with a large number of iterations. Even if the long execution time of PBKDF2 is its strength, it would indeed be preferable to implement it directly in the Godot kernel.

Marzin-bot commented 3 years ago

Here is my implementation in GDscript:

static func pbkdf2(hash_type: int, password: PoolByteArray, salt: PoolByteArray, iterations := 4096, length := 0) -> PoolByteArray:
    var crypto := Crypto.new()
    var hash_length := len(crypto.hmac_digest(hash_type, salt, password))
    if length == 0:
        length = hash_length

    var output := PoolByteArray()
    var block_count := ceil(length / hash_length)

    var buffer := PoolByteArray()
    buffer.resize(4)

    var block := 1
    while block <= block_count:
        buffer[0] = (block >> 24) & 0xFF
        buffer[1] = (block >> 16) & 0xFF
        buffer[2] = (block >> 8) & 0xFF
        buffer[3] = block & 0xFF

        var key_1 := crypto.hmac_digest(hash_type, password, salt + buffer)
        var key_2 := key_1

        for _index in iterations - 1:
            key_1 = crypto.hmac_digest(hash_type, password, key_1)

            for index in key_1.size():
                key_2[index] ^= key_1[index]

        output += key_2

        block += 1

    return output.subarray(0, hash_length - 1)