78

For the past 2 hours, I have been trying to debug a weird issue in devise which is not letting me login.

Here's the stuff I'm referring too:

password 
=> 'vinodsobale'

password == 'vinodsobale'
=> true

resource.valid_password?(password)

=> false

resource.valid_password?('vinodsobale')

=> true

Attaching the screenshot as well:

enter image description here Note: I have enabled debugger inside devise so the above code is devise internal code.

To me, it looks like a issue in Devise.secure_compare.

atw
  • 5,428
  • 10
  • 39
  • 63
Viren
  • 5,812
  • 6
  • 45
  • 98
  • @PadmanabanGokula I will already done a comparison `password == 'vinodsobale' `. More importantly even if you solution work how can I add password.to_s to devise internal code. – Viren Dec 07 '15 at 14:47
  • I think he means `resource.valid_password?(password.to_s)`. Still though, it's weird. – dan-klasson Dec 07 '15 at 15:28
  • @Viren what is the response of `resource.valid_password?(password.to_s)` – Paritosh Piplewar Dec 14 '15 at 11:47
  • 2
    Open a GitHub issue? Preferrably with a reproducible test. – deprecated Dec 14 '15 at 15:30
  • @vemv I have already mention this question on Devise Google mailing list. – Viren Dec 14 '15 at 15:41
  • I'm unable to reproduce what you are demonstrating here, which means something else is probably going on. What is the resource object you are using? – Brent Eicher Dec 14 '15 at 18:16
  • @brentEicher if you refer the screenshot the pry is inside the devise code i.e `database_authentication`. I myself has test this 1000 times on console and it work flawlessly the only i difference happening in the above is that when it didn't work the request is oringinate from a ios client build over react native,that it – Viren Dec 15 '15 at 03:59
  • @brentEicher tested with curl work well as well only when the request is from a native client i see the above scenario. – Viren Dec 15 '15 at 04:01
  • 4
    @Viren - I have no clue about ruby, or devise, but I would check the encoding of the given password. – MByD Dec 15 '15 at 18:38
  • 3
    Can you do a `password.encoding` and tell us the result? This is the only thing I can think of going wrong there. You can also play around with `::BCrypt::Engine.hash_secret(password, salt)` and compare that to the actually stored hash. For that comparison `text.bytes` might also come in handy. – Rudolf Dec 15 '15 at 21:35
  • @Rudolf that is first thing i suspected when I got that it definitely a encoding issue but the encoding was `UTF-8` what I can you tell precisely that is that problem occur on `Devise.secure_compare` algorithm. – Viren Dec 16 '15 at 05:46
  • 4
    Can you check `password.bytes` and `'vinodsobale'.bytes` just to make sure? – Rudolf Dec 16 '15 at 07:33
  • Also, I don't think `Devise.secure_compare` is the problem. This is the method that hashes the entered password: [`Devise::Encryptor.compare`](https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/lib/devise/encryptor.rb#L12-L20) The generated hash is in turn compared to the stored hash with `Devise.secure_compare`. My current theory is that the hash gets corrupted for some reason. – Rudolf Dec 16 '15 at 07:38
  • @Rudolf Exactly what I'm saying the that the problem *occur* on `Devise.secure_compare` (is not the a problem) where the byte comparison failed due to irregularity in bytes.My point of asking this question was to find out why their is difference in bytes comparison when the string `==` return true. – Viren Dec 16 '15 at 14:04
  • 1
    Can you do a few things and just send me the output? Then I can do some more research :-) `'vinodsobale'.bytes`, `password.bytes`, `resource.class.pepper` and `resource.encrypted_password`. – Rudolf Dec 16 '15 at 17:15
  • 1
    Wild guess maybe [`NULL` character](https://en.wikipedia.org/wiki/Null_character) at the end of saved one – Lukasz Szozda Dec 16 '15 at 18:52
  • 3
    @lad2025, what do you mean by "saved one"? Is it the `password`? If it is, `NULL character \0` should be encoded to unicode and `password == 'vinodsobale'` shouldn't return `true`. – vutran Dec 17 '15 at 03:50
  • 1
    @Viren I agree with @Rudolf, you should try `password.bytes` so we would see the exact byte sequence of your `password`. – vutran Dec 17 '15 at 03:52
  • @Rudolf the developer who had this issue on his machine told me yesterday he isn't getting the issue anymore. I will keep on eye on on issue and will update you. – Viren Dec 19 '15 at 03:41
  • If its a strange issue then strange solutions might work you should definitely try what is suggested and come back with the response. – Pablo Jomer Dec 21 '15 at 08:01
  • Is it possible that the password was auto-capitalized (often the case on mobile) or that an invisible space was appended at the end? Maybe a lowercase/trim before comparison/validation could help get rid of this sort of issues? – Val Dec 21 '15 at 08:31
  • Just a guess. Could it be an encoding issue? The string literal is encoded as ASCII, but the variable is encoded as UTF-8 or the other way around? I have seen similar issues in Python. – Alon Catz Dec 21 '15 at 08:37
  • The variable is actually just a reference to the object, not the object itself. I think this might pertain to the issue. Also, the code you showed in the screenshot has been updated, since all the valid_password? method did in strategies, was to check if the field was not blank, as opposed to the Model.valid_password? method. I don't know whether this modification happened to induce a qualitative change in results. My other guess is then that the encrypting of the reference differs from the encrypting of the object itself, due to the inclusion of some bytes pertaining to the referencing.. – Uvar Dec 21 '15 at 12:50
  • which ruby version have you? – Малъ Скрылевъ Dec 29 '15 at 20:34
  • Can you check both passwords the following way: `password.unpack("C#{password.bytesize}")` ? – Nickolay Kondratenko Jan 14 '16 at 15:33
  • Just my 2cents, I've had weird issues in the past by accidentally overriding a method that isn't obviously in use in the stack (ex `resource.respond_to? method ? this : that`. Look through the source code for `valid_password?` and compare to your resource model definition. – fbelanger Jul 24 '16 at 19:59
  • So I had a similar issue and what I found was I wasn't entering the password properly. Can you just check the method where you're entering the password or better if you can update the question with the method where you take in the password. – Tequila_Tears Aug 10 '16 at 06:43

4 Answers4

3

This issue is due to a known string-corruption bug in Ruby 2.2.0 that was fixed in 2.2.2.

As described in the bug report, the corruption occured when BCrypt called a specific string-creation API from its C extension, which Devise v3.3.0 triggered by calling ::BCrypt::Engine.hash_secret from the Devise::Models::DatabaseAuthenticatable#valid_password? method. A Devise-specific workaround for this bug was published in v3.5.0.

The solution is to either:

  • Downgrade Ruby to < 2.2.0, or upgrade to >= 2.2.2;
  • Upgrade Devise to >= 3.5.0.
wjordan
  • 19,770
  • 3
  • 85
  • 98
0

How about

resource.valid_password?(password.to_s)

I hope it help You.

  • 3
    Please edit your post and explain why this would work. – Rohit Gupta Dec 21 '15 at 00:30
  • Why do you think this will work i see plenty of problem with this firstly i have already mention i.e `password == 'vinodsobale' ` matches. That been if `vinodsoble` is of type `string` (which we all can see) then `password` is also a `string` so doing a `.to_s` wont yield anything different. Secondly the code you just mention is a part of devise internal code how do you want to implement that like open the devise code and monkey patch it. – Viren Dec 21 '15 at 03:07
  • 1
    It could be that the equal operator automatically calls the to_s method if there is an invalid type on the right hand. But I'm just guessing. Did you try it? – Pablo Jomer Dec 21 '15 at 07:59
  • it's a plausible explanation. – sevenseacat Dec 21 '15 at 09:34
  • 2
    You used `password == 'vinosobale'`. Could you try `password === 'vidodsobale'` matches? Could you read this question -- http://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and ? – Сергій Назаревич Dec 21 '15 at 10:46
  • In other hand. 1. I don't know `what type is variable 'password' in yourcode`. But if `resource.valid_password?(password) => false` AND `resource.valid_password?('vinodsobale') => true` THEN `password !== 'vinodsobale'`. 2. If variable `password` isn't `string` in your code, your first line `password => 'vinodsobale'` was result of `password.to_s` such as any nonstring types for viewing in standart output should be converted to string. – Сергій Назаревич Dec 21 '15 at 10:59
  • So --- I get You solution `What You can do`. You could try it and when it will `resource.valid_password?(password.to_s) => true` You could say `problem with type of variable 'password'`. Any other solutions not possible becouse we cannot see your code. – Сергій Назаревич Dec 21 '15 at 11:00
  • Secondly -- about devise code -- ...as first You could use generators for explaine some code of `devise`. ...as second what are devises modules You use in your code? In some case it could be noncombitable. For example, You could use I16 or anything suitable internationsl support and some one of modules could not work with this.... If You give more informations -- we could say more... – Сергій Назаревич Dec 21 '15 at 11:06
  • In other case You could read very nice article about `buggable strings`: http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings/ – Сергій Назаревич Dec 21 '15 at 11:12
0

Devise DatabaseAuthenticatable#valid_password? is using a method called Encryptor::compare it take 2 objects, the current stored password and the new password you want to compare, I believe there is a side effect for this method that modify the second parameter in the middle so it'll modify the object instead of once it'll be modified twice which leads to a false result, so it may work if you passed a duplicated object of password. can you try to use valid_password? password.dup

Emad Elsaid
  • 323
  • 3
  • 19
  • If that is a case why does the modification not happen when a string is supplied `resource.valid_password?('vinodsobale')` everytime I used to run this it used to work and `return true` – Viren Mar 23 '16 at 15:48
0

It could be a problem with the encoding between the original source and your console. If you run password.codepoints, you should be able to see the actual encoding. Running .codepoints on the raw 'password' string should return [112, 97, 115, 115, 119, 111, 114, 100].

ceph3us
  • 7,326
  • 3
  • 36
  • 43
Torrey Payne
  • 107
  • 5