开发者

Rails saving plain old string to SQlite as BLOB? I'm about to lose my mind!

I have no idea why this would be happening, but Rails is saving a string to SQLite as a BLOB. Before creating a new user in my app I take their plain string password and MD5 it before saving to the database:

class User < ActiveRecord::Base
  before_create :encrypt_password

  def encrypt_password
    self.password = Digest::MD5.hexdigest(self.password)
  end
end

But, the password field goes into SQLite as a freaking BLOB every time! The only way I could even tell is when exporting the table to SQL I can see the true nature of the field:

INSERT INTO "users" VALUES (24, 'john.doe@example.com', X'3639366432396530393430613439353737343866653366633965666432326133');

What the hell?? So now when I try to authenticate a user by looking up their email and MD5 hashed password it will fail every time. REAL strings don't match against BLOBs apparently:

User.find_by_email_and_password('john.doe@example.com', Digest::MD5.hexdigest('password') => nil

I have never used a BLOB in my entire life, let alone as the password field for a user table. My migrations clearly define :string as the datatype. Doing a User.columns clearly shows:

#<ActiveRecord::ConnectionAdapters::SQLiteColumn:0x00000105256ce0 @name="password", @sql_type="varchar(255)"

I've been working on this app for the better part of a month and have never seen this issue until last night when I was writing some tests for the User model. Testing trying to authenticate a user would fail every time, so I started manually building users in the console and come to find out the password w开发者_开发百科ould never match so all user lookups would fail.

The Rails debug info for creating a user looks like:

INSERT INTO "users" ("created_at", "email", "first_name", "last_login_at", "last_name", "login_count", "password", "role_id", "twitter", "updated_at", "uuid") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["created_at", Mon, 04 Jul 2011 18:50:58 UTC +00:00], ["email", "braulio_towne@runolfsson.name"], ["first_name", "Ebba"], ["last_login_at", nil], ["last_name", "Bayer"], ["login_count", nil], ["password", "5f4dcc3b5aa765d61d8327deb882cf99"], ["role_id", 2], ["twitter", nil], ["updated_at", Mon, 04 Jul 2011 18:50:58 UTC +00:00], ["uuid", "7ab57110-889c-012e-e207-482a140835c4"]]

Which, when I convert to regular SQL works just fine, so there's gotta be something else going on somewhere. What is going on?!?!

  • sqlite3 3.6.12
  • rails 3.1.0.rc4
  • sqlite3-ruby 1.3.3

UPDATE

It gets stranger and stranger...if I hardcode some random string in my encrypt_password method then it goes into the database correctly:

def encrypt_password
  self.password = 'foo'
end

I can even hardcode it to the actual MD5 hash of the string 'password' and it works:

def encrypt_password
  self.password = '5f4dcc3b5aa765d61d8327deb882cf99'
end

But if I tell it to Digest::MD5.hexdigest('password') then it goes in as a BLOB.

Now, if I add anything onto the string created by the digest, then it works!

def encrypt_password
  self.password = Digest::MD5.hexdigest(self.password) + ' '
end

What the hell is that?? So for now my workout is to add a newline and then chomp it off:

def encrypt_password
  self.password = (Digest::MD5.hexdigest(self.password) + "\n").chomp
end

I feel like I should open a ticket in Rails somewhere, but this is so amazingly strange that I don't want to get laughed out of the community forever for even suggesting that something like this could be happening!


Turns out this was an encoding issue. See the ticket here: https://github.com/rails/rails/issues/1965

hexdigest returns an ASCII string, but when you go back to query against that same field the query runs as a UTF-8 string. I assume that as soon as I manually added something to the string it was converted to UTF-8 behind the scenes and then was properly saved to the DB as UTF-8. Here's the fix:

def encrypt_password
  self.password = Digest::MD5.hexdigest(self.password).encode('UTF-8')
end


Straight up MD5 hashing isn't great for security. Someone breaks an email/password database elsewhere using this method and they can hack into your site with ease.

Consider using Bcrypt. The following taken from http://bcrypt-ruby.rubyforge.org:

require 'bcrypt'

class User < ActiveRecord::Base
  # users.password_hash in the database is a :string
  include BCrypt

  def password
    @password ||= Password.new(password_hash)
  end

  def password=(new_password)
    @password = Password.create(new_password)
    self.password_hash = @password
  end

end

Add a migration to rename 'password' as 'password_hash'.

This should also have the knock on effect of solving the serialization problem you are having with the result of the hexdigest method.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜