attr-encrypted / attr_encrypted Goto Github PK
View Code? Open in Web Editor NEWGenerates attr_accessors that encrypt and decrypt attributes
License: MIT License
Generates attr_accessors that encrypt and decrypt attributes
License: MIT License
Some clients following ISO 27001 are asking me to encrypt 'sensitive' data on DB, fields like email address, phone number e etc. attr_encrypted works really great, but I have several queries in pure SQL and Sphinx Search to deal with.
I tried these two ciphers with attr_encrypted, but AES_DECRYPT(encrypted_field,'key') returned NULL on MySQL console:
Am I missing something here, or the aes implementation from MySQL is different from openssl?
Best regards,
Fernando
When used in nested models using accepts_nested_attributes_for :model the key can not be dynamic.
e.g
class Parent < ActiveRecord::Base has_many :children, :dependent => :destroy accepts_nested_attributes_for :children, :allow_destroy => true end class Child < ActiveRecord::Base belongs_to :parent attr_encrypted :some_column, :key => proc { |child| child.encryption_key } end
This does not work, the key can only be a static value.
I'm retracting this issue shown below.
This gem attr_encrypted does actually work with ActiveRecord 3.2.9 and possibly some previous versions where the issue was raised about the encrypted data not being stored in the database.
I misunderstood how this feature is to be used with the database. The following excerpt got me thinking that I might be wrong on how I was implementing this feature:
"Specifying the encrypted attribute name
By default, the encrypted attribute name is encrypted_#{attribute} (e.g. attr_encrypted :email would create an attribute named encrypted_email). So, if you’re storing the encrypted attribute in the database, you need to make sure the encrypted_#{attribute} field exists in your table. You have a couple of options if you want to name your attribute something else."
I began doing some tests and got the gem to work flawlessly, but it does kind of work backwards than the way I was thinking it should work.
Here's how to implement attr_encrypted as I understand it.
MySQL Table EncyptTest structure:
id: Integer, Autoincrement, Primary Key
encrypted_data: Long Text or BLOB
created_at: DateTime
Model definition:
class EncryptTest < ActiveRecord::Base
attr_encypted :data, :key => CONFIG[:encryption_key]
end
This code will work as expected. What I wasn't getting was that the "unencrypted" attribute name (:data in this case) is not the field name in the database table, it is actually just used for accessing/storing the data via the model. The actual field name in the database needs to be the "encrypted" name (encrypted_data in this case) created by the attr_encrypted feature. And since I'm using ActiveRecord, the :encode flag is set to true by default.
Hope this helps some other newbie that is trying to use this.
!!!!!!!!!!!!!!!!!!!!!!!!!!!I S S U E___B E L O W___R E T R A C T E D!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
I've been unable to get attr_encrypted to work with ActiveRecord 3.2.9 and it appears to be the same issue as reported before with the encrypted fields not actually getting written to the database.
However, I've a simple alternative approach that seems to work as seamlessly as this plugin.
You just need the encryptor 1.1.3 gem created by shuber.
Here is some sample code to show how to do the same thing using ActiveRecord 3.2.9 and encryptor 1.1.3 gem until the attr_encrypted gem is fixed.
Note: I put my encryption key into a configuration constant using the figaro gem.
class DatabaseModelName < ActiveRecord::Base
attr_accessible :data
before_save :encrypt_data
after_save :decrypt_data
after_find :decrypt_data
protected
def encrypt_data
self.data = Encryptor.encrypt(:value => self.data, :key => CONFIG[:encryption_key]) unless self.data.blank?
end
def decrypt_data
self.data = Encryptor.decrypt(:value => self.data, :key => CONFIG[:encryption_key) unless self.data.blank?
end
end
Hi Sean,
I'm wondering if you have plans to put attr_encrypted up on Gemcutter (or even RubyForge) in the future. Github dropping gem building has a lot of people standing on their heads! Thanks,
Steve
It works without bundler on a different machine, but my setup only works with bundler + RVM setups.
What's Happening?: Whenever I try to save an encrypted attribute via the Web UI, it doesn't save to the database. (All params are successfully sent to the controller)
I do "ap params" (ap is awesome_print) before and after the save (and everything I entered in the form is there), and store the result of the method update_attributes to a variable -- whose value is true... so rails thinks the save was successful... but when I look at the database all the attr_encrypted fields are still blank. No errors or anything, on the web side or the server side. Rails thinks everything is working as intended.
I'm using:
ruby 1.8.7
rails 2.3.8
bundler 1.0.21
This is how attr_encrypted is in the gemfile:
gem "attr_encrypted"
Hi,
I've installed attr_encrypted gem, created new AR model Cc with attr_encrypted :pan method. I've also created db table, containing encrypted_pan field. When I try to assign value to pan, I'm encountering error below:
TypeError: can't convert nil into String
from /bla/bla/bla/gems/encryptor-1.1.2/lib/encryptor.rb:57:in `pkcs5_keyivgen'
from /bla/bla/bla/gems/encryptor-1.1.2/lib/encryptor.rb:57:in `crypt'
from /bla/bla/bla/gems/encryptor-1.1.2/lib/encryptor.rb:31:in `encrypt'
from /bla/bla/bla/gems/attr_encrypted-1.2.0/lib/attr_encrypted.rb:193:in `encrypt'
from /bla/bla/bla/gems/attr_encrypted-1.2.0/lib/attr_encrypted.rb:269:in `encrypt'
from /bla/bla/bla/gems/attr_encrypted-1.2.0/lib/attr_encrypted.rb:128:in `block (2 levels) in attr_encrypted'
from (irb):3
from /bla/bla/bla/gems/ruby-1.9.2-p180@app0/gems/railties-3.0.6/lib/rails/commands/console.rb:44:in `start'
from /bla/bla/bla/gems/ruby-1.9.2-p180@app0/gems/railties-3.0.6/lib/rails/commands/console.rb:8:in `start'
from /bla/bla/bla/gems/ruby-1.9.2-p180@app0/gems/railties-3.0.6/lib/rails/commands.rb:23:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
I use Ruby 1.9.2-p180 with Rails 3.0.6
Calling reload
on an instance of an Active Record model class (i.e. a class derived from ActiveRecord::Base
) is intended to restore the state of the object by requerying its contents from the database. Attributes declared with attr_encrypted
do not exhibit this behaviour.
Here's the test scenario:
Create model source file User.rb
and corresponding database migration:
class User < ActiveRecord::Base
attr_encrypted :name, :marshal => true, :key => 'SECRET_KEY'
end
Run the migration to create the table and then perform the following sequence of steps in the Rails console:
$ u = User.create(:name => 'Winston Churchill')
$ u.name = 'Neville Chamberlain'
$ u.reload
$ u.name
> 'Neville Chamberlain'
In this example, we create an instance of the model with a given name
value and then change it to another value without saving it to the database. Calling reload
should restore the value of this attribute to the value in the database. In this example, the last line should yield 'Winston Churchill'
instead of 'Neville Chamberlain'
. This is definitely not peace for our time.
In fact, it turns out that reload
does, in fact, requery the database and reload all of the class's real attributes (in this case encrypted_name
etc.). However, attr_encrypted
does not correctly invalidate attr_encrypted
's virtual attributes such as name
.
The fix should be fairly straightforward: we need to overload the ActiveRecord::Base.reload
method to clear out the instance variables used to track the values of the virtual attributes exposed via attr_encrypted
. Here's the current workaround I'm using for this issue:
class ActiveRecord::Base
def reload(*args)
self.class.encrypted_attributes.keys.each do |attribute_name|
instance_variable_set("@#{attribute_name}", nil)
end
super
end
end
This behaviour would just need to be mixed into ActiveRecord::Base
in the usual way.
Is TEXT the best to do? Or some binary BLOB?
Giving some advice on that topic definitely saves some time during setup of attr_encrypted as highly appreciated! :)
Perhaps I'm setting up my class incorrectly, but I can't get my encrypted data to save to MongoDB. I'm using attr_encrypted (1.2.1) and mongoid (3.0.14).
My class looks like
class Participant include Mongoid::Document field :first_name, type: String field :last_name, type: String field :email, type: String attr_encrypted :password, :key => 'mykey', :encode => true field :password, type: String field :encrypted_password, type: String attr_accessible :first_name, :last_name, :email, :password auto_increment :user_id validates_presence_of :encrypted_password validates_length_of :password, minimum: 5 validates :last_name, :presence => true, :if => "first_name.nil?" validates_uniqueness_of :email validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :allow_nil => true end
My failing test is
describe 'saving' do let!(:participant) { FactoryGirl.build(:participant)} it 'should save the encrypted password in the database' do participant.password = 'averyuniquepassword' participant.save puts participant.password puts participant.encrypted_password retrieved_participant = Participant.where(encrypted_password: participant.encrypted_password).first retrieved_participant.should_not be_nil end end
and I can retrieve the values of encrypted_password and password from the instance in memory, but I can see they're not saved in MongoDB.
Thanks for your help!
Hi, as of Ruby 2.0 we can no longer pass a protected method to generate our key
:key => :protect_method_to_generate_key
protected
def protect_method_to_generate_key
# some clever method to generate and return a key
end
This generates a TypeError (no implicit conversion of Symbol into String)
Can you suggest another way to pass in a protected or private method so that the key can be generated on the fly without it being accessible from outside the class?
class MyModel < ActiveRecord::Base
attr_encrypted :password, :key => 'a secret key'
end
current_user.MyModel(:user_id => current_user.id,
:site => params[:site],
:username => param[:username],
:password => params[:password]
)
current_user.save!
Pushes NULL value into Mysql db.
Rails version 2.3.4
Ruby version 1.8.7
Am I missing something??
Thanks
We are using using attr_encrypted for emails in our database with the following config:
{:dump_method=>"dump", :marshal=>false,
:load_method=>"load", :default_encoding=>"m",
:attribute=>:encrypted_email,
:key=>"<key>",
:encryptor=>Encryptor, :unless=>false, :if=>true, :suffix=>"",
:prefix=>"encrypted_",
:encrypt_method=>"encrypt", :encode=>"m",
:decrypt_method=>"decrypt", :marshaler=>Marshal}
Now we have a part of the team that need to work with database directly without ruby.
So they can not use attr_encrypted.
Is it possible to decrypt an attribute at database level, so that any client can recognize it?
We are using mysql.
When assigning an integer to an attr_encrypted attribute the following error is raised:
TypeError: can't convert Fixnum into String
This is because the integer value is passed straight through to Encryptor which passes it to the OpenSSL::Cipher::Cipher instance. The error only occurs when marshaling is not used; I assume the marshaling procedure always outputs a string acceptable to the cipher.
As a solution I suggest type casting the assigned value. This can be achieved by updating line 192 of attr_encrypted.rb from...
value = options[:marshaler].send(options[:dump_method], value) if options[:marshal]
...to...
value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
...or...
if options[:marshal]
value = options[:marshaler].send(options[:dump_method], value)
else
value = value.to_s
end
...depending on whether you prefer clarity or brevity.
Alternately:
I think it's reasonable to assume the user intends all assigned values to be cast to string when marshaling is not used, which is why I propose doing it here in the AttrEncrypted module.
Hi,
I would like to implement a model of the structure:
class IngredientItem < ActiveRecord::Base
attr_encrypted :ingredient_id, :key => :attr_encryption_key, :marshal => true
def attr_encryption_key
return "password"
end
end
The problem is that with this setup I can not use dynamic find_by's, e.g.:
IngredientItem.find_by_ingredient_id(id)
fails saying:
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/encryptor-1.1.3/lib/encryptor.rb:58:in `pkcs5_keyivgen'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/encryptor-1.1.3/lib/encryptor.rb:58:in `crypt'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/encryptor-1.1.3/lib/encryptor.rb:31:in `encrypt'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted.rb:193:in `encrypt'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted.rb:227:in `method_missing'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted/adapters/active_record.rb:50:in `method_missing_with_attr_encrypted'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted/adapters/active_record.rb:44:in `block in method_missing_with_attr_encrypted'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted/adapters/active_record.rb:42:in `each'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted/adapters/active_record.rb:42:in `each_with_index'
/home/funsi/.rvm/gems/ruby-1.9.2-p136/gems/attr_encrypted-1.2.0/lib/attr_encrypted/adapters/active_record.rb:42:in `method_missing_with_attr_encrypted'
The README says that this should work if all records are encrypted with the same encryption key (per attribute), which is clearly the case in my simple example above.
Thanks,
Simon
Just observed the following behavior:
attr_encrypted
serialize :attribute
below itAfter that, my serialized fields weren't being de-serialized. Accessing the attribute yielded the YAML string instead of the deserialized object (Array in my case).
Moving the attr_encrypted
call to after the serialize
call seems to have fixed that specific problem. Makes me worry about more hidden issues though.
This is on Rails 2.3.2 and Ruby 1.8.7.
You are using cbc mode with the same :iv over an over.
see this discussion:
danpal/encryptor@c1b7e39
This gem is quite downloaded(36,520 total downloads). It should be fixed, and users should at least be warned.
I'm using attr_encrypted
in combination with validates_timeliness
(https://github.com/adzap/validates_timeliness) and it turns out that the order with which I declare my attributes with the appropriate methods is significant. In the case of the following example, the call to attr_encrypted
breaks validation of the birthdate
attribute:
class User < ActiveRecord::Base
attr_encrypted :name, :key => 'SECRET_KEY'
validates :birthdate, :timeliness => { :type => :date }
end
If, however, I declare them the other way round, everything works fine:
class User < ActiveRecord::Base
validates :birthdate, :timeliness => { :type => :date }
attr_encrypted :name, :key => 'SECRET_KEY'
end
The culprit is this line: https://github.com/shuber/attr_encrypted/blob/master/lib/attr_encrypted/adapters/active_record.rb#L17. Basically, the call to attr_encrypted
in my class is invoking define_attribute_methods
which can be empty at this point since the validators have not yet been encountered in the source file.
I'm not sure how to fix this, but I do, at least, have a reasonable workaround for the problem.
Could be related to #46.
Gosh - I can't win - the column is encrypted in the DB (yay) but now I'm getting this when trying to decrypt:
ruby-1.9.2-p290 :009 > u.dob
OpenSSL::Cipher::CipherError: wrong final block length
from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/encryptor-1.1.3/lib/encryptor.rb:62:in final' from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/encryptor-1.1.3/lib/encryptor.rb:62:in
crypt'
from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/encryptor-1.1.3/lib/encryptor.rb:44:in decrypt' from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:179:in
decrypt'
from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:262:in decrypt' from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:126:in
block (2 levels) in attr_encrypted'
from (irb):9
from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.2.8/lib/rails/commands/console.rb:47:in start' from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.2.8/lib/rails/commands/console.rb:8:in
start'
from /Users/northband/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.2.8/lib/rails/commands.rb:41:in <top (required)>' from script/rails:6:in
require'
from script/rails:6:in `
Any ideas?
I think an accidental require might have snuck its way in:
http://github.com/shuber/attr_encrypted/blob/master/lib/attr_encrypted.rb#L3
The debugger doesn't appear to be used and isn't listed in the gemspec.
Other team members can't run the migrations for a new model that uses attr_encrypted. Is the call to define_attribute_methods necessary?
Is it possible to generate a salt attribute to be used for each record where attributes are being encrypted? I've attempted to create the salt by override the attribute setter - but the instance method to generate the key+salt used in the encryption key seems to be ignored.
I've verified that with ActiveRecord 3.2.9 that simply replacing the key word attr_encrypted with attr_encryptor prevents data from being stored to database. I'm not sure why.
The validates_numericality_of method in ActiveRecord specifically sends (activerecord/lib/active_record/validations.rb:1019, in rails-2.3.5):
record.send("#{attr_name}_before_type_cast")
While this method exists for any native-columns in ActiveRecord, it doesn't with the virtual non-encrypted fields.
Example:
class AchPayment < ActiveRecord::Base
attr_encrypted :account_number, :key => $encryption_key
attr_encrypted :routing_number, :key => $encryption_key
validates_length_of :account_number, :in => 2..15
validates_numericality_of :account_number, :only_integer => true
validates_length_of :routing_number, :is => 9
validates_numericality_of :account_number, :only_integer => true
end
Produces:
a = Factory.build(:ach_payment)
=> #<AchPayment id: nil, order_id: 321975001, name: "John Doe", encrypted_account_number: "nslmxtNRHAmYmWsXq7dnfA==\n", encrypted_routing_number: "NuL13ZfR8CpuNvdQpa3CPg==\n", account_type: "checking", payment_date: "2009-12-12 22:29:53", amount_mode: nil, amount: 500, created_at: nil, updated_at: nil>
a.save
NoMethodError: undefined methodaccount_number_before_type_cast' for #<AchPayment:0x102e380c0> from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:255:in
method_missing'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1019:insend' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1019:in
validates_numericality_of'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:468:invalidates_each' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:465:in
each'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:465:invalidates_each' from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:182:in
call'
from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:182:inevaluate_method' from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:166:in
call'
from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:90:inrun' from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:90:in
each'
from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:90:insend' from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:90:in
run'
from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:276:inrun_callbacks' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1098:in
valid_without_callbacks?'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/callbacks.rb:315:invalid?' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1077:in
save_without_dirty'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/dirty.rb:79:insave_without_transactions' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:229:in
send'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:229:inwith_transaction_returning_status' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in
transaction'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:182:intransaction' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:228:in
with_transaction_returning_status'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:196:insave' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:208:in
rollback_active_record_state!'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:196:in `save'
would it make sense to add a _before_type_cast match to Huberry::AttrEncrypted::Adapters::ActiveRecord::method_missing_with_attr_encrypted ?
I think the documentation should at least mention that you should have a column prefixed with the word encrypted_ (by default) for it to be saved correctly
at some point, an update to datamapper renamed the file to be required from "datamapper" to "data_mapper"
I have a rails 3.1.8 app with a model that uses both serialize and attr_encrypted. These are used on separate attributes btw. I updated it to rails 3.2.8 and started getting an error. The serialized attribute was no longer being deserialized.
In order to fix this issue I had to move the serialize line above the attr_encrypted line.
by default active record will give you last_name? if you have a column named last_name. this would be a good method to add for those using attr_encrypted
def last_name?
encrypted_last_name?
end
I've got an issue here http://stackoverflow.com/questions/5130589/attr-encrypted-with-rails but haven't found anyone who knows what's happening. I feel like I'm missing something.
thanks
I'm using attr_encrypted on a Rails 2.3 app. I've been noticing a SHOW FIELDS FROM query anytime I open the Rails console in development and tracked it down to the attr_encrypted call on the model (commented it out and log no longer does the query). I'm using Resque as a background job processor and it seems that anytime it's running, the SHOW FIELDS FROM query keeps getting spammed a lot. Is there a way to modify the gem initialization so that it doesn't load the ActiveRecord model? I'm using several other plugins/gems that extend AR and none seem to do this.
I use mongoid, but I think it does not matter.
User model:
class User
include Mongoid::Document
attr_encrypted :name, :key => 'password', :encode => true
field :encrypted_name
end
I tried to play such code:
irb(main):005:0> User.create! :name => "тест"
=> #<User _id: 4d2a32fe5e6f990866000003, encrypted_name: "FDBMiOZQCXfSf9Mcyq2CFA==\n">
irb(main):006:0> User.last.name
=> "\xD1\x82\xD0\xB5\xD1\x81\xD1\x82"
And as you can see it can't decode string properly. But when I switched to ruby 1.8.7 it is ok:
irb(main):005:0> User.create! :name => "тест"
=> #<User _id: 4d2a32fe5e6f990866000003, encrypted_name: "FDBMiOZQCXfSf9Mcyq2CFA==\n">
irb(main):006:0> User.last.name
=> "тест"
P.S. Ruby 1.9.2. Patches for ruby 1.9 from spectator: https://github.com/spectator/attr_encrypted
Gosh - I must be doing something wrong as I can't get this thing to work. Every time I save it simply comes up NULL.
Anyone care to take a look to see what I'm doing wrong:
as the title says, as soon as i add the validates_uniqueness_of validation for an encrypted model attribute, upon create/update ruby throws
undefined method `text?' for nil:NilClass
example:
class User < ActiveRecord::Base
attr_encrypted :name, :key => 'a very secret key'
validates_uniqueness_of :name
end
other validations work fine for me.
i'm on ruby 1.9.2-p180, rails 3.0.9, attr_encrypted 1.2.0
I don't know if this is related to issue #2 or not, but this is what I have found.
These 3 lines (really the last 2) are the culprits (lib/attr_encrypted.rb lines 121-123)
instance_methods_as_symbols = instance_methods.collect { |method| method.to_sym }
attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
From what I have been able to figure out, when attr_encrypted runs Rails has not generated the accessor methods for the database columns, so attr_encrypted writes its own using the above code. Then, when Rails comes around to generating the accessors, they already exist so it doesn't do anything.
The problem is, the getters and setters generated in the code above do not use the read_attribute and write_attribute methods (or self[:attribute_name] and self[:attribute_name]=). As a result, changes to "encrypted_attribute_name" don't end up in the changes hash and thus don't get saved to the DB. Also, because the getters don't call read_attribute, the persisted "encrypted_attribute_name" isn't pulled from the database. In other word, Ruby friendly accessors are generated, not Rails friendly ones.
Commenting out those lines does seem to fix some issues, but unfortunately, not all of them. I am too new at ruby/rails to really dig in much deeper and figure this out. It looks like I am going to be manually encrypting columns...
Hey again,
I serialize my object to json, I just noticed that the attr_encrypted fields are serialized in encrypted form, instead of plaintext. Is there a best practise to fix that?
This issue seems to be related, but the :marshal => true option seems not to work for me
#11
My code:
attr_encrypted :some_attribute, :other_attribute, :key => 'some_key', :marshal => true
In a previous (early) version of attr_encrypted under rails 2.3.x, I had always done:
attr_encrypted :cc_number, :cc_month, :prefix => 'crypted_', :key => 'somekey'
Recently I upgraded my application to rails 3.0.9, with attr_encrypted 1.2.0 (my Gemfile contains "gem 'attr_encrypted'").
Suddenly the cc_month accessor method (i.e., not the first attribute) returns the value of cc_number (i.e., the first attribute).
I've fixed this by temporarily doing.
attr_encrypted :cc_number, :prefix => 'crypted_', :key => 'somekey'
attr_encrypted :cc_month, :prefix => 'crypted_', :key => 'somekey'
FWIW I actually have more than two encrypted attributes in the model, above is just an example.
I noticed this commit that attempts to fix the issue:
9194755
Although I haven't had time to dig further and debug myself, it does seem that this issue is not fixed, or I am doing something incredibly wrong.
Thanks.
Just trying to get the basic example working:
class SillyEncryptor
def self.silly_encrypt(options)
(options[:value] + options[:secret_key]).reverse
end
def self.silly_decrypt(options)
options[:value].reverse.gsub(/#{options[:secret_key]}$/, '')
end
end
class User
include DataMapper::Resource
property :id, Serial
property :encrypted_email, String
attr_encrypted :email, :secret_key => 'secret', :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt
end
I get:
$ rails console
Loading development environment (Rails 3.0.6)
>> u=User.create(:email => '[email protected]')
=> #<User @id=1 @encrypted_email="dGVyY2VzbW9jLmV0YXZpcnBAbmlyb2M=\n">
>>
Ran into an issue where our MySQL db column was smaller than our length validation limit, and data was truncated and therefore corrupted.
This naturally raises the question: how much longer can the encrypted value be than the original value?
Example:
zip code, should validate max length 9. How long must the db column be?
Is there a minimum encrypted length, and above that, the original and encrypted lengths are the same? Or is there a fixed length that's always added, or some percentage?
It'd be great if someone knows the answer so I don't have to spend time figuring it out. It should also be added to the readme, assuming it's something people should know about.
I recently upgraded my application that has been successfully using attr_encrypted since its inception to rails 3.2.2 + ruby 1.9.3. I am now experiencing a problem where an encrypted attribute that contains UTF-8 characters (i.e., a ruby 1.9 String encoded as UTF-8) upon encryption is not being encoded correctly upon decryption. When accessing the decrypted attribute, the String encoding is ASCII-8BIT.
This is probably best demonstrated by the below example:
# my class using attr_encrypted
class PaymentMethod < ActiveRecord::Base
attr_encrypted :cc_number, :prefix => 'crypted_', :key => Cloak.cloak
attr_encrypted :cc_expiration_month, :prefix => 'crypted_', :key => Cloak.cloak
attr_encrypted :cc_expiration_year, :prefix => 'crypted_', :key => Cloak.cloak
attr_encrypted :first_name, :prefix => 'crypted_', :key => Cloak.cloak
attr_encrypted :middle_name, :prefix => 'crypted_', :key => Cloak.cloak
attr_encrypted :last_name, :prefix => 'crypted_', :key => Cloak.cloak
end
1.9.3-p125 :006 > p = PaymentMethod.last
PaymentMethod Load (0.3ms) SELECT "payment_methods".* FROM "payment_methods" ORDER BY "payment_methods"."id" DESC LIMIT 1
=> #<PaymentMethod id: 1, user_id: 1, active: true, crypted_cc_number: "UdOXr61FOI709h52Radtiw==\n", crypted_cc_expiration_month: "Y7uSwMsu5dlc74J5GALv8g==\n", crypted_cc_expiration_year: "alcZ1dlyzq7WToQYCwuA9g==\n", crypted_first_name: "9wS8fEwmZEcr0OEPNpY4Ag==\n", crypted_middle_name: "", crypted_last_name: "xrKHtQgjisVriueH6mMNgw==\n", company: nil, address1: "240 cant", address2: nil, city: "bury", state: "fl", zip: "33428", country: "US", phone: "3829382938", note: nil, created_at: "2012-03-21 15:16:29", updated_at: "2012-03-21 15:16:29", created_by: "ncr", updated_by: nil>
1.9.3-p125 :007 > p.last_name.inspect => "\"Gagn\\xC3\\xA9\""
1.9.3-p125 :008 > p.last_name.encoding => #<Encoding:ASCII-8BIT>
1.9.3-p125 :009 > p.last_name => "Gagn\xC3\xA9"
1.9.3-p125 :010 > p.last_name.encode('UTF-8')
Encoding::UndefinedConversionError: "\xC3" from ASCII-8BIT to UTF-8
from (irb):10:in `encode'
from (irb):10
from /Users/nick/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.2/lib/rails/commands/console.rb:47:in `start'
from /Users/nick/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.2/lib/rails/commands/console.rb:8:in `start'
from /Users/nick/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.2.2/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
1.9.3-p125 :011 > p.last_name.unpack('U*') => [71, 97, 103, 110, 233]
1.9.3-p125 :012 > p.last_name.unpack('U*').pack('U*') => "Gagné" # this is what it should be
As you can see, the decrypted String does indeed contain the correct UTF-8 data, but it is not escaped in a way that ruby 1.9's String understands. An explicit unpack followed by a pack corrects the encoding.
I am not sure how to fix this correctly and am hoping someone can shed some light. Is this an attr_encrypted problem, an shuber/encryptor problem, or something that I should be handling myself?
At the moment I have this monkey patch in place in an initializer, which seems to alleviate the problem for my deployments.
AttrEncrypted::InstanceMethods.module_eval do
alias_method :original_decrypt, :decrypt
def decrypt(attribute, encrypted_value)
value = original_decrypt(attribute, encrypted_value)
value.is_a?(String) ? value.unpack('U*').pack('U*') : value
end
end
New to GitHub. I found this gem very useful, but just to add a few comments:
It is annoying to specify the :key all the time for all of the encryptable attributes. Would be extremely nice to have a simple configuration file, where you could specify the default key to be used everywhere, where :key is not specified. So it would be clean, flexible and very nice.
If no key is specified in both places (config and :key), gem could use "YourApp::Application.config.secret_token" as an encryption key. It is pretty unique and secure!
Not related to config, but, please, add an additional validation to the encrypted attribute. If it is equal to the original - throw an error. Consider example: attr_encrypted :snn, :prefix => "", :suffix => "". The IRB would throw one of the following errors: 1) Stack too deep. 2) Memory Error. Shouldn't be like that.
Hey guys
Im attempting an upgrade to Ruby 2.0.0-p247 how ever Iv discovered an issue with attr_encrypted.
When setting the value of an encrypted attribute I get the error:
TypeError: no implicit conversion of Symbol into String
Quick example:
attr_encrypted :content, :key => :note_key, :unless => :locked
user = User.new
user.content = "Some encrypted content"
It's worth mentioning that this code has been working fine using Ruby 1.9.3 and Rails 3.2 for many months.
Environment:
Rails 3.2
Ruby 2.0.0-p247
Strong Parameters
Trace
TypeError: no implicit conversion of Symbol into String
from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/encryptor-1.1.3/lib/encryptor.rb:58:in pkcs5_keyivgen' from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/encryptor-1.1.3/lib/encryptor.rb:58:in
crypt'
from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/encryptor-1.1.3/lib/encryptor.rb:31:in encrypt' from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:205:in
encrypt'
from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:281:in encrypt' from (irb):2 from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/railties-4.0.0/lib/rails/commands/console.rb:90:in
start'
from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/railties-4.0.0/lib/rails/commands/console.rb:9:in start' from /usr/local/rvm/gems/ruby-2.0.0-p247@sampleapp/gems/railties-4.0.0/lib/rails/commands.rb:64:in
<top (required)>'
from script/rails:6:in require' from script/rails:6:in
Hey there again,
I hope I don't make you mad with my posts here .. :-)
I tried to migrate my users with first- and lastname fields to encrypted ones.
So my migration looks like this:
class AddEncryptedFirstnameAndLastname < ActiveRecord::Migration
def up
add_column :users, :encrypted_firstname, :string
add_column :users, :encrypted_lastname, :string
encrypt_user_fullname
end
def encrypt_user_fullname
User.all.each do |user|
user.firstname = user.read_attribute('firstname')
user.lastname = user.read_attribute('lastname')
user.save!
end
end
end
In user.rb I have added encryption:
attr_encrypted :firstname, :lastname, :key => 'the_magic'
However when i run the migration, it creates the database fields encrypted_firstname and encrypted_lastname, but it does not store them (nil everywhere). Reading the values out works, so user.read_attribute('firstname') gives indeed the firstname.
Is this problem known, or maybe is it just some mistake on my side?
Edit:
Also trying to reset the User.reset_column_information() after the add_column commands did not help. (thanks for hint to rvanlieshout)
For the beginner, it would be handy to provide an example of how to generate a strong key, e.g. SecureRandom.base64(96)
, and some guidance on where to store it. A simplistic approach for a Rails app:
# file: config/initializers/custom.rb
MY_ENCRYPTION_KEY = "1xiGKEkkXG0PXOVJL+S8Ux2QkcYlekqhWCFVX1FWgpUf5hv8j1Kgcl47stgBGrpso5rfWwfCdgXZv3PRpBGhKY8ekInvmhVagLipMB2ZQjJyUwag1HnuqVTPMrlOKRMf"
A slightly fancier version would store MY_ENCRYPTION_KEY as an environment variable so the key doesn't appear in the source code. This might be handy for people wishing to deploy on Heroku, for example. But I don't consider myself enough of a security expert to know if that's a sensible approach.
Trying to get attr_encrypted working, but none of the other existing "wrong block length" solutions seem to work for me. Just trying to figure out where things are going wrong, but am stumped.
In other project that uses Encryptor directly I had to use "m0" to encode and "m" to decode to get it to work in ruby 1.9. But not even that seems to work here.
Setup:
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_encrypted :first_name, :last_name, :key => 'Pvk1Uv447S2vI8Ex71qqFCHL1vmV4EXg', :default_encoding => 'm0'
attr_accessible :email, :first_name, :last_name, :password, :password_confirmation
end
1.9.2p320 > u = User.new :email => '[email protected]', :password => 'password', :password_confirmation => 'password'
u.save
1.9.2p320 > u.first_name = "test"
1.9.2p320 > u.save
1.9.2p320 :006 > u.save
(0.6ms) BEGIN
User Exists (1.4ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" = '[email protected]' AND "users"."id" != 2 AND "users"."portal_id" = 1) LIMIT 1
(0.7ms) UPDATE "users" SET "encrypted_first_name" = '$2a$10$VkCljwchbn1UEINCASUmlOMthSZ2/q3mULDFF1Ow90Wr4ZUqaxMAO', "updated_at" = '2012-11-16 21:37:13.147546' WHERE "users"."id" = 2
(0.7ms) COMMIT
=> true
1.9.2p320 > User.first.first_name
User Load (0.7ms) SELECT "users".* FROM "users" LIMIT 1
OpenSSL::Cipher::CipherError: wrong final block length
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/encryptor-1.1.3/lib/encryptor.rb:62:in `final'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/encryptor-1.1.3/lib/encryptor.rb:62:in `crypt'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/encryptor-1.1.3/lib/encryptor.rb:44:in `decrypt'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:179:in `decrypt'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:262:in `decrypt'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/attr_encrypted-1.2.1/lib/attr_encrypted.rb:126:in `block (2 levels) in attr_encrypted'
from (irb):7
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/railties-3.2.8/lib/rails/commands/console.rb:47:in `start'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/railties-3.2.8/lib/rails/commands/console.rb:8:in `start'
from /Users/jkamenik/.rvm/gems/ruby-1.9.2-p320@waterfallportal/gems/railties-3.2.8/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
I could not get attr_encrypted to just save the encrypted values without keeping the unencrypted values - when I tried to force this by setting :attribute or :prefix I got an exception raised within attr_encrypted options.merge! call (option seems to be set to nil). Now I roll my own implementation:
def attr_accessor_encrypted(*args) options = args.extract_options! options[:key] ||= DEFAULT_ATTR_ENCRYPTION_KEY args.each do |attribute| attr_accessor attribute define_method(:"#{attribute}") do value = instance_variable_get(:"@#{attribute}") value.present? ? value.decrypt(:key => Digest::SHA256.hexdigest(options[:key])) : value end define_method(:"#{attribute}=") do |value| value = value.present? ? value.encrypt(:key => Digest::SHA256.hexdigest(options[:key])) : value instance_variable_set :"@#{attribute}", value end end end
...but don't really get why this was not possible/simple to do with this gem.
This is a question more than an answer. Do you want encrypted fields to work with update_attributes? I'm leaning toward no because these fields should have attr_protected. Curious on your feelings.
I'm hoping I can get some help with migrating from the sentry plugin to attr_encrypted. I have a bunch of rails deployments with encrypted data using sentry. I was hoping, since attr_encrypted is so flexible, that I could just drop-in-replace sentry with attr_encrypted without having to do a more elaborate un-encrypt and re-encrypt migration effort.
Heres sort of what I had for sentry in one of my models...
Sentry::SymmetricSentry.default_key = CLOAK
include ActiveRecord::Sentry
symmetrically_encrypts :cc_number
symmetrically_encrypts :cc_expiration_month
symmetrically_encrypts :cc_expiration_year
symmetrically_encrypts :first_name
symmetrically_encrypts :middle_name
symmetrically_encrypts :last_name
equivalent config for attr_encrypted that I am trying...
attr_encrypted :cc_number, :cc_expiration_month, :cc_expiration_year,
:first_name, :middle_name, :last_name, :prefix => 'crypted_',
:key => CLOAK, :algorithm => 'des-ede3-cbc'
Using the previous I get "encryptor.rb:48:in `final': bad decrypt" exceptions when trying to load old records. I'm pretty sure that the default symmetric algorithm used by sentry is des-ede3-cbc. There must be something else different in the decryption process that I do not understand.
Should this work or am I out of luck? Everything works great for new records. Appreciate any help. Thanks.
Passing a hash to Mode.new doesn't encrypt values:
m = Model.new(some_attribute: "some_value")
m.encrypted_some_attribute
>> "some_value"
Whereas using the setter method everything works fine:
m = Model.new
m.some_attribute = "some_value"
m.encrypted_some_attribute
>> "N80ZHASLND..."
I believe that the attributes should be encrypted in both situations. Do you agree?
Hi there,
I'm desperatly trying to encrypt the devise user token.
The "authentication_token" is an attribute of the user class.
So what I tried to do is to add the line
attr_encrypted :authentication_token, :key => 'a secret key', :attribute => 'authentication_token'
to the user class and hope it would transparently encrypt it.
However I run into some problem, namely, when I login (note that i previously deleted any user token from the DB), I get the following error message:
Completed 500 Internal Server Error in 364ms
SystemStackError - stack level too deep:
(gem) actionpack-3.2.13/lib/action_dispatch/middleware/reloader.rb:70:in `'
I assume that an endless recursion is occuring, but I don't understand where this originates from or how to fix it. Maybe someone can give me a hint?
Here also a link to my stackoverflow question:
http://stackoverflow.com/questions/15529846/encrypt-the-devise-token
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.