2012-08-19 18 views
12

Staram się realizować to, co wydaje się bardzo proste podejście uwierzytelniania przy użyciu Sinatra i BCrypt ale wyraźnie czegoś mi brakuje ...Ruby BCrypt porównanie hash

Użytkownicy są wstępnie przypisane tymczasowe hasło, które jest przechowywane w postaci zwykłego tekstu w db.

Uwierzytelniam się przy użyciu hasła tymczasowego, a następnie tworzę sól i hasło_hash i zapisuję je jako ciągi do pliku db (w tym przypadku mongo).

Do uwierzytelnienia Pobieram sól z db i hasła użytkownika w celu porównania.

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 

wartość zwracana przez BCrypt :: Engine.hash_secret (params [: hasło], password_salt) jest inna niż to, co jest zapisane w db (oba są klasy BCrypt :: hasło, ale nie mecz).

Czego mi tu brakuje? Z góry dziękuję za wszelki wgląd!

Marc

Odpowiedz

23

BCrypt::Password jest podklasą String, a overrides the == method aby sprawdzanie haseł łatwiejsze. Kiedy robisz

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

skończyć się wykonując hash dwukrotnie, a więc nie pasuje. Jeśli porównasz bezpośrednio z @user.password_hash zamiast z BCrypt::Password.new powinieneś zobaczyć, że pasują.

Bardziej "poprawnym" sposobem użycia bcrypt-ruby do haseł jest nie używanie klasy Engine w ogóle, tylko klasy Password. Nie trzeba zarządzać soli siebie, bcrypt dba o to i obejmuje ją w ciągu hash hasła:

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

puts password_salt 
puts password_hash 

produkuje coś takiego:

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

Dostaniesz coś nieco inny, jeśli go uruchomisz, ponieważ zostanie wygenerowana inna sól, ale zobaczysz, że hash to hasło zawierające sól.

W twoim przypadku, chcesz coś takiego:

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

Dziękuję bardzo. Właśnie tego mi brakowało, działa idealnie. Mogę przestać wyrywać włosy (do następnej rzeczy). – user1553220

+1

możesz zrobić @ user_hash.is_password? params [: hasło] zamiast == ... Myślę, że jest bardziej przejrzysty i podatny na błędy, ponieważ jeśli nie wiesz, że == został nadpisany i odwrócić kolejność porównywania (parametry [: hasło] == @user_hash), zwróci false ... – rizidoro

Powiązane problemy