technion / ruby-argon2

A Ruby gem offering bindings for Argon2 password hashing
MIT License
229 stars 30 forks source link

Memory cost definition #60

Closed estebanz01 closed 1 year ago

estebanz01 commented 1 year ago

Hola!

please, feel free to close this if my question is not in the right place.

So right now I'm trying to move some services from python to ruby and this service hashes all passwords with the Argon2 algorithm. The problem I'm having right now is trying to match the memory cost that is specified by the python app, which uses django defaults: https://github.com/django/django/blob/3b79dab19a2300a4884a3d81baa6c7c1f2dee059/django/contrib/auth/hashers.py#L369-L371

Basically, I need the app to replicate the cost of 100 MiB or m=102400 when hashing. Is this possible? I tried a number between 10 and 16 for m_cost and I couldn't get it.

Any ideas would be great, thanks!

EDIT: Basically I want this number :sweat_smile: as m_cost

3.2.2 :003 > Math.log2(102400)
 => 16.643856189774723
technion commented 1 year ago

I think the difficulty here is that I tried to follow the path of the Argon2 reference CLI application where m_cost is supplied as 1<<m. This is consistent by the way with for example, how Django does bcrypt, where the BCRYPT_ROUNDS are in fact 1<<ROUNDS.

I don't believe there's an easy way to fix this for you without completely breaking the API, but you may be able to completely ignore the advice to not use the C wrappers directly, and reimplement this function:

https://github.com/technion/ruby-argon2/blob/master/lib/argon2/ffi_engine.rb#L81

This appears to work, but consider the warnings about minimally tested crypto:

      secret = nil
      password = "my cool password"
      salt = Argon2::Engine.saltgen
      p_cost = 1
      t_cost = 2
      custom_m_cost = 102400

      result = ''
      secretlen = secret.nil? ? 0 : secret.bytesize
      passwordlen = password.nil? ? 0 : password.bytesize
      raise ArgonHashFail, "Invalid salt size" if salt.length != Argon2::Constants::SALT_LEN

      FFI::MemoryPointer.new(:char, Argon2::Constants::ENCODE_LEN) do |buffer|
        ret = Argon2::Ext.argon2_wrap(buffer, password, passwordlen,
                              salt, salt.length, t_cost, custom_m_cost,
                              p_cost, secret, secretlen)
        raise ArgonHashFail, ERRORS[ret.abs] unless ret.zero?

        result = buffer.read_string(Argon2::Constants::ENCODE_LEN)
      end
      result.delete "\0"

=> "$argon2id$v=19$m=102400,t=2,p=1$kd0+axUtgSPPBZouNXrWFQ$Y24FpyOfA9AoAc8q1g1VcmyjIyI4JZfN2/4tj8w1mX0"

estebanz01 commented 1 year ago

Thanks for the feedback. I'll stick with the current wrapper then.