GithubHelp home page GithubHelp logo

attr_encrypted's Introduction

attr_encrypted

workflow Gem Version

Generates attr_accessors that transparently encrypt and decrypt attributes.

It works with ANY class, however, you get a few extra features when you're using it with ActiveRecord or Sequel.

Installation

Add attr_encrypted to your gemfile:

  gem "attr_encrypted"

Then install the gem:

  bundle install

Usage

If you're using an ORM like ActiveRecord or Sequel, using attr_encrypted is easy:

  class User
    attr_encrypted :ssn, key: 'This is a key that is 256 bits!!'
  end

If you're using a PORO, you have to do a little bit more work by extending the class:

  class User
    extend AttrEncrypted
    attr_accessor :name
    attr_encrypted :ssn, key: 'This is a key that is 256 bits!!'

    def load
      # loads the stored data
    end

    def save
      # saves the :name and :encrypted_ssn attributes somewhere (e.g. filesystem, database, etc)
    end
  end

  user = User.new
  user.ssn = '123-45-6789'
  user.ssn # returns the unencrypted object ie. '123-45-6789'
  user.encrypted_ssn # returns the encrypted version of :ssn
  user.save

  user = User.load
  user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'

Encrypt/decrypt attribute class methods

Two class methods are available for each attribute: User.encrypt_email and User.decrypt_email. They accept as arguments the same options that the attr_encrypted class method accepts. For example:

  key = SecureRandom.random_bytes(32)
  iv = SecureRandom.random_bytes(12)
  encrypted_email = User.encrypt_email('[email protected]', iv: iv, key: key)
  email = User.decrypt_email(encrypted_email, iv: iv, key: key)

The attr_encrypted class method is also aliased as attr_encryptor to conform to Ruby's attr_ naming conventions. I should have called this project attr_encryptor but it was too late when I realized it ='(.

attr_encrypted with database persistence

By default, attr_encrypted uses the :per_attribute_iv encryption mode. This mode requires a column to store your cipher text and a column to store your IV (initialization vector).

Create or modify the table that your model uses to add a column with the encrypted_ prefix (which can be modified, see below), e.g. encrypted_ssn via a migration like the following:

  create_table :users do |t|
    t.string :name
    t.string :encrypted_ssn
    t.string :encrypted_ssn_iv
    t.timestamps
  end

You can use a string or binary column type. (See the encode option section below for more info)

If you use the same key for each record, add a unique index on the IV. Repeated IVs with AES-GCM (the default algorithm) allow an attacker to recover the key.

  add_index :users, :encrypted_ssn_iv, unique: true

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 or db column something else, see below for more details.

attr_encrypted options

Options are evaluated

All options will be evaluated at the instance level. If you pass in a symbol it will be passed as a message to the instance of your class. If you pass a proc or any object that responds to :call it will be called. You can pass in the instance of your class as an argument to the proc. Anything else will be returned. For example:

Symbols representing instance methods

Here is an example class that uses an instance method to determines the encryption key to use.

  class User
    attr_encrypted :email, key: :encryption_key

    def encryption_key
      # does some fancy logic and returns an encryption key
    end
  end
Procs

Here is an example of passing a proc/lambda object as the :key option as well:

  class User
    attr_encrypted :email, key: proc { |user| user.key }
  end

Default options

The following are the default options used by attr_encrypted:

  prefix:            'encrypted_',
  suffix:            '',
  if:                true,
  unless:            false,
  encode:            false,
  encode_iv:         true,
  encode_salt:       true,
  default_encoding:  'm',
  marshal:           false,
  marshaler:         Marshal,
  dump_method:       'dump',
  load_method:       'load',
  encryptor:         Encryptor,
  encrypt_method:    'encrypt',
  decrypt_method:    'decrypt',
  mode:              :per_attribute_iv,
  algorithm:         'aes-256-gcm',
  allow_empty_value: false

All of the aforementioned options are explained in depth below.

Additionally, you can specify default options for all encrypted attributes in your class. Instead of having to define your class like this:

  class User
    attr_encrypted :email, key: 'This is a key that is 256 bits!!', prefix: '', suffix: '_crypted'
    attr_encrypted :ssn, key: 'a different secret key', prefix: '', suffix: '_crypted'
    attr_encrypted :credit_card, key: 'another secret key', prefix: '', suffix: '_crypted'
  end

You can simply define some default options like so:

  class User
    attr_encrypted_options.merge!(prefix: '', :suffix => '_crypted')
    attr_encrypted :email, key: 'This is a key that is 256 bits!!'
    attr_encrypted :ssn, key: 'a different secret key'
    attr_encrypted :credit_card, key: 'another secret key'
  end

This should help keep your classes clean and DRY.

The :attribute option

You can simply pass the name of the encrypted attribute as the :attribute option:

  class User
    attr_encrypted :email, key: 'This is a key that is 256 bits!!', attribute: 'email_encrypted'
  end

This would generate an attribute named email_encrypted

The :prefix and :suffix options

If you don't like the encrypted_#{attribute} naming convention then you can specify your own:

  class User
    attr_encrypted :email, key: 'This is a key that is 256 bits!!', prefix: 'secret_', suffix: '_crypted'
  end

This would generate the following attribute: secret_email_crypted.

The :key option

The :key option is used to pass in a data encryption key to be used with whatever encryption class you use. If you're using Encryptor, the key must meet minimum length requirements respective to the algorithm that you use; aes-256 requires a 256 bit key, etc. The :key option is not required (see custom encryptor below).

Unique keys for each attribute

You can specify unique keys for each attribute if you'd like:

  class User
    attr_encrypted :email, key: 'This is a key that is 256 bits!!'
    attr_encrypted :ssn, key: 'a different secret key'
  end

It is recommended to use a symbol or a proc for the key and to store information regarding what key was used to encrypt your data. (See below for more details.)

The :if and :unless options

There may be times that you want to only encrypt when certain conditions are met. For example maybe you're using rails and you don't want to encrypt attributes when you're in development mode. You can specify conditions like this:

  class User < ActiveRecord::Base
    attr_encrypted :email, key: 'This is a key that is 256 bits!!', unless: Rails.env.development?
    attr_encrypted :ssn, key: 'This is a key that is 256 bits!!', if: Rails.env.development?
  end

You can specify both :if and :unless options.

The :encryptor, :encrypt_method, and :decrypt_method options

The Encryptor class is used by default. You may use your own custom encryptor by specifying the :encryptor, :encrypt_method, and :decrypt_method options.

Lets suppose you'd like to use this custom encryptor class:

  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

Simply set up your class like so:

  class User
    attr_encrypted :email, secret_key: 'This is a key that is 256 bits!!', encryptor: SillyEncryptor, encrypt_method: :silly_encrypt, decrypt_method: :silly_decrypt
  end

Any options that you pass to attr_encrypted will be passed to the encryptor class along with the :value option which contains the string to encrypt/decrypt. Notice that the above example uses :secret_key instead of :key. See encryptor for more info regarding the default encryptor class.

The :mode option

The mode options allows you to specify in what mode your data will be encrypted. There are currently three modes: :per_attribute_iv, :per_attribute_iv_and_salt, and :single_iv_and_salt.

NOTE: :per_attribute_iv_and_salt and :single_iv_and_salt modes are deprecated and will be removed in the next major release.

The :algorithm option

The default Encryptor class uses the standard ruby OpenSSL library. Its default algorithm is aes-256-gcm. You can modify this by passing the :algorithm option to the attr_encrypted call like so:

  class User
    attr_encrypted :email, key: 'This is a key that is 256 bits!!', algorithm: 'aes-256-cbc'
  end

To view a list of all cipher algorithms that are supported on your platform, run the following code in your favorite Ruby REPL:

  require 'openssl'
  puts OpenSSL::Cipher.ciphers

See Encryptor for more information.

The :encode, :encode_iv, :encode_salt, and :default_encoding options

You're probably going to be storing your encrypted attributes somehow (e.g. filesystem, database, etc). You can simply pass the :encode option to automatically encode/decode when encrypting/decrypting. The default behavior assumes that you're using a string column type and will base64 encode your cipher text. If you choose to use the binary column type then encoding is not required, but be sure to pass in false with the :encode option.

  class User
    attr_encrypted :email, key: 'some secret key', encode: true, encode_iv: true, encode_salt: true
  end

The default encoding is m (base64). You can change this by setting encode: 'some encoding'. See Array#pack for more encoding options.

The :marshal, :dump_method, and :load_method options

You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). If this is the case, simply pass the :marshal option to automatically marshal when encrypting/decrypting.

  class User
    attr_encrypted :credentials, key: 'some secret key', marshal: true
  end

You may also optionally specify :marshaler, :dump_method, and :load_method if you want to use something other than the default Marshal object.

The :allow_empty_value option

You may want to encrypt empty strings or nil so as to not reveal which records are populated and which records are not.

  class User
    attr_encrypted :credentials, key: 'some secret key', marshal: true, allow_empty_value: true
  end

ORMs

ActiveRecord

If you're using this gem with ActiveRecord, you get a few extra features:

Default options

The :encode option is set to true by default.

Dynamic find_by_ and scoped_by_ methods

Let's say you'd like to encrypt your user's email addresses, but you also need a way for them to login. Simply set up your class like so:

  class User < ActiveRecord::Base
    attr_encrypted :email, key: 'This is a key that is 256 bits!!'
    attr_encrypted :password, key: 'some other secret key'
  end

You can now lookup and login users like so:

  User.find_by_email_and_password('[email protected]', 'testing')

The call to find_by_email_and_password is intercepted and modified to find_by_encrypted_email_and_encrypted_password('ENCRYPTED EMAIL', 'ENCRYPTED PASSWORD'). The dynamic scope methods like scoped_by_email_and_password work the same way.

NOTE: This only works if all records are encrypted with the same encryption key (per attribute).

NOTE: This feature is deprecated and will be removed in the next major release.

Sequel

Default options

The :encode option is set to true by default.

Deprecations

attr_encrypted v2.0.0 now depends on encryptor v2.0.0. As part of both major releases many insecure defaults and behaviors have been deprecated. The new default behavior is as follows:

  • Default :mode is now :per_attribute_iv, the default :mode in attr_encrypted v1.x was :single_iv_and_salt.
  • Default :algorithm is now 'aes-256-gcm', the default :algorithm in attr_encrypted v1.x was 'aes-256-cbc'.
  • The encryption key provided must be of appropriate length respective to the algorithm used. Previously, encryptor did not verify minimum key length.
  • The dynamic finders available in ActiveRecord will only work with :single_iv_and_salt mode. It is strongly advised that you do not use this mode. If you can search the encrypted data, it wasn't encrypted securely. This functionality will be deprecated in the next major release.
  • :per_attribute_iv_and_salt and :single_iv_and_salt modes are deprecated and will be removed in the next major release.

Backwards compatibility is supported by providing a special option that is passed to encryptor, namely, :insecure_mode:

  class User
    attr_encrypted :email, key: 'a secret key', algorithm: 'aes-256-cbc', mode: :single_iv_and_salt, insecure_mode: true
  end

The :insecure_mode option will allow encryptor to ignore the new security requirements. It is strongly advised that if you use this older insecure behavior that you migrate to the newer more secure behavior.

Upgrading from attr_encrypted v1.x to v3.x

Modify your gemfile to include the new version of attr_encrypted:

  gem attr_encrypted, "~> 3.0.0"

The update attr_encrypted:

  bundle update attr_encrypted

Then modify your models using attr_encrypted to account for the changes in default options. Specifically, pass in the :mode and :algorithm options that you were using if you had not previously done so. If your key is insufficient length relative to the algorithm that you use, you should also pass in insecure_mode: true; this will prevent Encryptor from raising an exception regarding insufficient key length. Please see the Deprecations sections for more details including an example of how to specify your model with default options from attr_encrypted v1.x.

Upgrading from attr_encrypted v2.x to v3.x

A bug was discovered in Encryptor v2.0.0 that incorrectly set the IV when using an AES-*-GCM algorithm. Unfornately fixing this major security issue results in the inability to decrypt records encrypted using an AES-*-GCM algorithm from Encryptor v2.0.0. Please see Upgrading to Encryptor v3.0.0 for more info.

It is strongly advised that you re-encrypt your data encrypted with Encryptor v2.0.0. However, you'll have to take special care to re-encrypt. To decrypt data encrypted with Encryptor v2.0.0 using an AES-*-GCM algorithm you can use the :v2_gcm_iv option.

It is recommended that you implement a strategy to insure that you do not mix the encryption implementations of Encryptor. One way to do this is to re-encrypt everything while your application is offline.Another way is to add a column that keeps track of what implementation was used. The path that you choose will depend on your situtation. Below is an example of how you might go about re-encrypting your data.

  class User
    attr_encrypted :ssn, key: :encryption_key, v2_gcm_iv: is_decrypting?(:ssn)

    def is_decrypting?(attribute)
      attr_encrypted_encrypted_attributes[attribute][:operation] == :decrypting
    end
  end

  User.all.each do |user|
    old_ssn = user.ssn
    user.ssn= old_ssn
    user.save
  end

Things to consider before using attr_encrypted

Searching, joining, etc

While choosing to encrypt at the attribute level is the most secure solution, it is not without drawbacks. Namely, you cannot search the encrypted data, and because you can't search it, you can't index it either. You also can't use joins on the encrypted data. Data that is securely encrypted is effectively noise. So any operations that rely on the data not being noise will not work. If you need to do any of the aforementioned operations, please consider using database and file system encryption along with transport encryption as it moves through your stack.

Data leaks

Please also consider where your data leaks. If you're using attr_encrypted with Rails, it's highly likely that this data will enter your app as a request parameter. You'll want to be sure that you're filtering your request params from your logs or else your data is sitting in the clear in your logs. Parameter Filtering in Rails Please also consider other possible leak points.

Storage requirements

When storing your encrypted data, please consider the length requirements of the db column that you're storing the cipher text in. Older versions of Mysql attempt to 'help' you by truncating strings that are too large for the column. When this happens, you will not be able to decrypt your data. MySQL Strict Trans

Metadata regarding your crypto implementation

It is advisable to also store metadata regarding the circumstances of your encrypted data. Namely, you should store information about the key used to encrypt your data, as well as the algorithm. Having this metadata with every record will make key rotation and migrating to a new algorithm signficantly easier. It will allow you to continue to decrypt old data using the information provided in the metadata and new data can be encrypted using your new key and algorithm of choice.

Enforcing the IV as a nonce

On a related note, most algorithms require that your IV be unique for every record and key combination. You can enforce this using composite unique indexes on your IV and encryption key name/id column. RFC 5084

Unique key per record

Lastly, while the :per_attribute_iv_and_salt mode is more secure than :per_attribute_iv mode because it uses a unique key per record, it uses a PBKDF function which introduces a huge performance hit (175x slower by my benchmarks). There are other ways of deriving a unique key per record that would be much faster.

Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with rakefile, version, changelog, or history.
  • Send me a pull request. Bonus points for topic branches.

attr_encrypted's People

Contributors

ankane avatar arthurnn avatar austintaylor avatar bfreese avatar bitsapien avatar byroot avatar cheynewallace avatar cyrusstoller avatar dillonwelch avatar douwem avatar eugeneius avatar f3ndot avatar grosser avatar igor-drozdov avatar jish avatar jmazzi avatar joshbranham avatar miltzi avatar mlarraz avatar mvastola avatar mwean avatar nagachika avatar pmarreck avatar rcook avatar saghaulor avatar sbfaulkner avatar shuber avatar tamird avatar vimalvnair avatar willnet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

attr_encrypted's Issues

Using attr_encryption in a migration file, values not stored to db?

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)

Ruby 2 Issue: TypeError: no implicit conversion of Symbol into String

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:incrypt'
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:inencrypt'
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:instart'
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

'

Gemcutter time?

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

update_attributes

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.

Generating salt to use in encryption key

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.

stack level too deep: devise token encryption

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

length validation and column size

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.

validates_uniqueness_of results in undefined method `text?' for nil:NilClass

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

Nested models

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.

attr_encrypted seems to break serialized attributes

Just observed the following behavior:

  1. declared a attr_encrypted
  2. declared a serialize :attribute below it

After 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.

Reloading Active Record model instance does not refresh attr_encrypted's virtual attributes

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.

Ruby 1.9.3 and UTF-8

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

OpenSSL::Cipher::CipherError: wrong final block length

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:incrypt'
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:indecrypt'
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:inblock (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:instart'
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:inrequire'
from script/rails:6:in `

'

Any ideas?

Cannot really use with serialized objects in DB

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.

validates_numericality_of fails to run on attr_encrypted fields

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 method account_number_before_type_cast' for #<AchPayment:0x102e380c0> from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/attribute_methods.rb:255:inmethod_missing'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1019:in send' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1019:invalidates_numericality_of'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:468:in validates_each' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:465:ineach'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:465:in validates_each' from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:182:incall'
from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:182:in evaluate_method' from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:166:incall'
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:90:ineach'
from /Users/user/Desktop/project/vendor/rails/activesupport/lib/active_support/callbacks.rb:90:in send' 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:276:in run_callbacks' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1098:invalid_without_callbacks?'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/callbacks.rb:315:in valid?' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/validations.rb:1077:insave_without_dirty'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/dirty.rb:79:in save_without_transactions' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:229:insend'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:229:in with_transaction_returning_status' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:136:intransaction'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:182:in transaction' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:228:inwith_transaction_returning_status'
from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:196:in save' from /Users/user/Desktop/project/vendor/rails/activerecord/lib/active_record/transactions.rb:208:inrollback_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 ?

Assigning integer to attr_encrypted attribute raises TypeError

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:

  • The type casting could be performed in the Encryptor.
  • The user could hack a marshaller into doing the type casting.
  • The user could wrap the assignment in a type casting block using alias_method_chain.

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.

Configuration

New to GitHub. I found this gem very useful, but just to add a few comments:

  1. 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.

  2. 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!

  3. 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.

Pass key from private or protected method

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?

Broken in Rails 3.0.8 Ruby 1.9.2

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...

documentation request: provide simple example of how to generate a key (and where to store it)

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.

attr_encryptor alias breaks functionality.

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.

  • Christopher Mullins

Ruby 1.9: troubles with non-English chars

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

I need to encrypt the data such that mysql can decrypt it.

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:

  • aes-128-cbc
  • aes-128-ecb

Am I missing something here, or the aes implementation from MySQL is different from openssl?

Best regards,
Fernando

Currently not compatible with ActiveRecord 3.2.9 (Retracted)

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

Custom Encryptor Not Working

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">
>>

In Rails 3.2.8+ if you have attr_encrypted before a serialize beware

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.

Dynamic find_by and a non String key

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

attribute? not working

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

specifying multiple attributes on single line is broken

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.

Broken in Rails 2.3.8 + Bundler

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"

Active Record adapter ordering issue with validators

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.

TypeError: can't convert nil into String

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

Encrypted password doesn't save in MongoDB

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!

migrating from "sentry"

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.

Gem initialization with ActiveRecord spams SHOW FIELDS FROM query

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.

Passing a hash to Model.create / Model.new doesn't encrypt the attributes

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?

Decrypt with database only

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.

Not encrypting the password..

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

OpenSSL::Cipher::CipherError: wrong final block length (Again)

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:

  • Ruby 1.9.2p320
  • Rails 3.2
  • attr_encrypted 1.2.1
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>'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.