2012-08-19 15 views
12

Sto cercando di attuare quello che sembra un approccio molto semplice autenticazione tramite Sinatra e bcrypt ma chiaramente mi manca qualcosa ...Rubino bcrypt confronto hash

Gli utenti vengono preassegnati una password temporanea che è memorizzato in chiaro nel db.

Autentico contro la password temporanea e quindi creo sia un comando salt che password_hash e li scrivo come stringhe sul db (mongo in questo caso).

Per autenticare, recupero il db e la password utente da confrontare.

post "/password_reset" do 
    user = User.first(:email => params[:email], :temp_password => params[:temp_password]) 
    if dealer != nil then 
    password_salt = BCrypt::Engine.generate_salt 
    password_hash = BCrypt::Engine.hash_secret(params[:password], password_salt) 
    user.set(:password_hash => password_hash) 
    user.set(:password_salt => password_salt) 
    end 
end 

post "/auth" do 
    @user = User.first(:email => params[:email]) 
    @user_hash = BCrypt::Password.new(@user.password_hash) #because the password_hash is stored in the db as a string, I cast it as a BCrypt::Password for comparison 
    if @user_hash == BCrypt::Engine.hash_secret(params[:password], @user.password_salt.to_s) then 
    auth = true 
    else 
    auth = false 
    end 
end 

Il valore restituito da bcrypt :: Engine.hash_secret (params [: password], password_salt) è diverso da ciò che è memorizzato nel db (entrambi sono di classe bcrypt :: Password, ma non lo fanno incontro).

Cosa mi manca qui? Molte grazie in anticipo per qualsiasi intuizione!

Marc

risposta

23

BCrypt::Password è una sottoclasse di String, e overrides the == method per rendere le password di controllo più facile. Quando si esegue

if @user_hash == BCrypt::Engine.hash_secret(params[:password], @user.password_salt.to_s) 

si finisce per eseguire l'hash due volte, e così essi non corrispondono. Se si confronta direttamente con @user.password_hash anziché utilizzare BCrypt::Password.new, si dovrebbe vedere che corrispondono.

Il modo più "corretto" di utilizzare bcrypt-ruby per le password consiste nel non utilizzare affatto la classe Engine, solo la classe Password. Non è necessario per gestire il sale da soli, bcrypt si occupa di questo e lo include nella stringa di hash della password:

password_salt = BCrypt::Engine.generate_salt 
password_hash = BCrypt::Engine.hash_secret("s3kr1t!", password_salt) 

puts password_salt 
puts password_hash 

produce qualcosa di simile:

$2a$10$4H0VpZjyQO9SoAGdfEB5j. 
$2a$10$4H0VpZjyQO9SoAGdfEB5j.oanIOc4zp3jsdTra02SkdmhAVpGK8Z6 

Si otterrà un po 'qualcosa diverso se lo si esegue, poiché verrà generato un sale diverso, ma è possibile vedere che l'hash della password include il sale.

Nel tuo caso, si desidera qualcosa di simile:

post "/password_reset" do 
    user = User.first(:email => params[:email], :temp_password => params[:temp_password]) 
    if dealer != nil then 
    password_hash = BCrypt::Password.create(params[:password]) 
    user.set(:password_hash => password_hash) # no need to store the salt separately in the database 
    end 
end 

post "/auth" do 
    @user = User.first(:email => params[:email]) 
    @user_hash = BCrypt::Password.new(@user.password_hash) 
    if @user_hash == params[:password] then # overridden == method performs hashing for us 
    auth = true 
    else 
    auth = false 
    end 
end 
+1

grazie mille. Era esattamente quello che mi mancava, funziona perfettamente. Posso smettere di tirare i miei capelli (fino alla prossima cosa). – user1553220

+1

si potrebbe fare @ user_hash.is_password? params [: password] invece di == ... Penso che sia più chiaro e soggetto a errori, perché se non sai che == è stato sovrascritto e invertito l'ordine di confronto (params [: password] == @user_hash), restituirà falso ... – rizidoro