jmazzi / crypt_keeper Goto Github PK
View Code? Open in Web Editor NEWTransparent ActiveRecord encryption
Home Page: http://jmazzi.github.com/crypt_keeper/
License: MIT License
Transparent ActiveRecord encryption
Home Page: http://jmazzi.github.com/crypt_keeper/
License: MIT License
Here is what I discovered when using crypt_keeper version off github (which uses "serialized" attributes approach instead of callbacks as it does in the released gem).
Saving and existing record which uses crypt_keeper will update all encrypted attributes with new re-encrypted values even if nothing changed on the model. Here is failing test (fails on rails 3.2):
require 'spec_helper'
describe User do
let(:user) { FactoryGirl.create(:user) }
subject { user }
class RawUser < ActiveRecord::Base
self.table_name = User.table_name
end
# This particular scenario was failing for crypto_keeper version on github.
# To use 'serialized' attributes approach the newer crypto_keeper is using,
# active_record needs to be of later versions then we are using right now.
# Need to use crypt_keeper 0.13.1 (released)
it 'should not re-encrypt and save unchanged encrypted attributes on save' do
encrypted_email_before = RawUser.find(user.id).email
subject.save # this was re-encrypting and saving serialized attributes... :(
expect(RawUser.find(user.id).email).to eq encrypted_email_before
end
end
Apparently active record always updates serialized attributes which causes unnecessary performance hit.
https://github.com/rails/rails/blob/9aa1a3d85327fa0a3055b5b757a0be092ce582f7/activerecord/lib/active_record/attribute_methods/dirty.rb#L70
For now I am reverting to the releases 0.13.1 version.
Hi,
Im having an extrange issue when trying to edit an encrypted field through a form.
Everything works just fine, the data is saved ok and retreived without problems (if I print object.inspect, that field shows un-encrypted), but when I try to show the input with:
<%= f.text_area :protected_field, class: "input-xxlarge text-field-box" %>
The textarea contains the data encrypted.
Everything works fine in Rails 4.1.4. I'm having this issue only with Rails 4.2.1
Note: This update is a breaking change which will require re-encrypting all data for anyone using the AES and MySQL AES providers.
salt
param in addition to the key
. The salt
it used when hashing the passphrase to ensure a unique key is used when encrypting data. The Armor gem is used to accomplish this.Models can be migrated by temporarly using the migrate encryptors and calling save
on every record. Example: :migrate_aes
or :migrate_mysql_aes
. This works by attempting to read the data using the old encryption method and falling back to the new method. Encryption always uses the new method
@fabiokr @itspriddle what are your thoughts on the below? It's pretty rough but seems to work in my testing.
require 'digest/sha2'
require 'openssl'
require 'base64'
module CryptKeeper
module Provider
class OldMysqlAes < CryptKeeper::Provider::MysqlAes
def initialize(options = {})
@key = options.fetch(:key)
end
end
class MigrateMysqlAes
def initialize(options = {})
@new_enc = CryptKeeper::Provider::MysqlAes.new(options)
@old_enc = OldMysqlAes.new(options)
end
def decrypt(value)
plain_text = @old_enc.decrypt(value)
if plain_text.blank?
@new_enc.decrypt(value)
else
plain_text
end
end
def encrypt(value)
@new_enc.encrypt(value)
end
end
class OldAes
SEPARATOR = ":crypt_keeper:"
attr_accessor :key
attr_accessor :aes
def initialize(options = {})
@aes = ::OpenSSL::Cipher::Cipher.new("AES-256-CBC")
@aes.padding = 1
key = options.fetch(:key) do
raise ArgumentError, "Missing :key"
end
@key = Digest::SHA256.digest(key)
end
def encrypt(value)
aes.encrypt
aes.key = key
Base64::encode64("#{aes.random_iv}#{SEPARATOR}#{aes.update(value.to_s) + aes.final}")
end
def decrypt(value)
iv, value = Base64::decode64(value.to_s).split(SEPARATOR)
aes.decrypt
aes.key = key
aes.iv = iv
aes.update(value) + aes.final
end
def search(records, field, criteria)
records.select { |record| record[field] == criteria }
end
end
class MigrateAes
def initialize(options = {})
@new_enc = CryptKeeper::Provider::Aes.new(options)
@old_enc = OldAes.new(options)
end
def decrypt(value)
plain_text = @old_enc.decrypt(value)
if plain_text.blank?
@new_enc.decrypt(value)
else
plain_text
end
end
def encrypt(value)
@new_enc.encrypt(value)
end
end
end
end
Hi,
I get the following error when trying to save a record with an integer as value:
"TypeError: no implicit conversion of Fixnum into String".
Model contains:
crypt_keeper :my_field, encryptor: :aes_new, key: 'secret', salt: 'salt'
In Rails console:
m = MyModel.new
m.my_field = 123
m.save
TypeError: no implicit conversion of Fixnum into String
does appraisal need to be included? Isn''t it just for testing the gem?
Key derivation by single Hash function is not a good idea. Consider using pkcs5_keyivgen if the specified key here is a master secret.
References 4325b69#commitcomment-2352679
When saving a record where an encrypted field has a value of "" (empty string), as is common when a web form field is left blank, crypt_keeper raises an exception.
I've worked around it by setting the field to nil if it == "" in the controller but this is inelegant for sure.
Is there a cleaner workaround I'm not aware of?
Thanks,
-Dan
I am encrypting some of the columns of table in my rails app. In model i specify the fields of table to be encryptes. All the encryption and decryption is working fine but in case where i am fetching only specific non encrypted fields like id and name. its throwing an exception like ActiveModel::MissingAttributeError - missing attribute.
stacktrace is as follows:
activemodel (4.0.9) lib/active_model/attribute_methods.rb:481:in `missing_attribute'
activerecord (4.0.9) lib/active_record/attribute_methods/read.rb:59:in `block in __temp__26c6f6f646f57627f65707'
activerecord (4.0.9) lib/active_record/attribute_methods/read.rb:95:in `block (2 levels) in read_attribute'
activerecord (4.0.9) lib/active_record/attribute_methods/read.rb:94:in `block in read_attribute'
activerecord (4.0.9) lib/active_record/attribute_methods/read.rb:84:in `read_attribute'
activerecord (4.0.9) lib/active_record/attribute_methods/read.rb:59:in `__temp__26c6f6f646f57627f65707'
crypt_keeper (0.18.2) lib/crypt_keeper/model.rb:30:in `block in force_encodings_on_fields'
crypt_keeper (0.18.2) lib/crypt_keeper/model.rb:29:in `force_encodings_on_fields'
activesupport (4.0.9) lib/active_support/callbacks.rb:375:in `_run__1730928489337275095__find__callbacks'
activesupport (4.0.9) lib/active_support/callbacks.rb:80:in `run_callbacks'
activerecord (4.0.9) lib/active_record/core.rb:210:in `init_with'
activerecord (4.0.9) lib/active_record/persistence.rb:52:in `instantiate'
activerecord (4.0.9) lib/active_record/querying.rb:45:in `block in find_by_sql'
activerecord (4.0.9) lib/active_record/result.rb:21:in `block in each'
activerecord (4.0.9) lib/active_record/result.rb:21:in `each'
activerecord (4.0.9) lib/active_record/querying.rb:45:in `find_by_sql'
activerecord (4.0.9) lib/active_record/relation.rb:587:in `exec_queries'
activerecord (4.0.9) lib/active_record/relation.rb:471:in `load'
activerecord (4.0.9) lib/active_record/relation.rb:220:in `to_a'
activerecord (4.0.9) lib/active_record/relation/delegation.rb:12:in `each'
Thanks in advance.
Komal
Hey there. I'm storing some serialized JSON in my encrypted column. To accomplish this within crypt_keeper, I overrode your encrypt/decrypt callbacks to load/dump the JSON as appropriate, but perhaps there's a better way? I could create a custom encryptor, but that seemed like overkill to me.
e.g.
def encrypt_callback
self.selections = self.class.encrypt(JSON.dump(read_attribute(:selections)))
end
def decrypt_callback
self.selections = JSON.load(self.class.decrypt(read_attribute(:selections)))
end
(I've lost the ability to encrypt multiple columns, but that doesn't apply to this situation anyhow)
Also, just wanted so say thanks for making crypt_keeper. Works out of the box without issue and is just much nicer than other solutions. ✨
Hi,
I am using crypt keeper with devise on a user model, and I'm encrypting the users email address. The issue is, that even though I don't update the email field (only some other field), the email address field is updated every time. This is causing devise to send out confirmation emails every time I update the user. Is there a way to stop updating these field when they are not changed?
I've found a couple issues related to both the plaintext accessor methods and the encrypted database field names being the same, and being converted with callbacks:
An example of where this may cause an issue: if I'm trying to use an auditing library like paper_trail, it's recording the attributes as part of a callback. Because crypt_keeper users a before_save
callback to encrypt the fields, it's possible that some libraries will grab the plain-text attribute values.
I find attr_encrypted's approach much less problematic for this case. The database fields are named encrypted_foo
, and then it creates accessors foo
and foo=
. The added benefit is that the attributes are lazy-loaded, which gives a persistent performance benefit. You also don't need to worry about any other callbacks or Observers. Additionally, direct manipulation of an encrypted field, e.g. User.update_column(:encrypted_ssn, '"\\xc30d040..")
is more explicitly communicative.
This may be too sweeping of a suggested change for a library that's emphasizing not being too complicated. These aspects may be better as a documentation change, and I could try adapting your pgcrypto() code into a custom attr_encrypted-compatible Encryptor object.
I am using PostgreSQL 9.3.5, Rails 4.1 and trying to achieve :postgres_pgp_public_key
. I am getting PG::ExternalRoutineInvocationException: ERROR: Corrupt ascii-armor
. I can't seem to figure out what's wrong. Please help.
Service.encrypt_table!
is fine, but decrypt_table!
is undefined.
class Service
(...)
crypt_keeper :options,
encryptor: :aes_new,
key: 'XXXXXXXXXXXXXXXXXXX',
salt: 'XXXXXXXXXXXXXXXXXXX',
encoding: 'UTF-8'
(...)
end
[4] pry(main)> Service.crypt_keeper_fields?
=> true
[5] pry(main)> Service.dec
Service.decorate_columns Service.decrement_counter Service.decrypt
[5] pry(main)> Service.encrypt
Service.encrypt Service.encrypt_table!
[5] pry(main)> Service.encrypt
Service.encrypt Service.encrypt_table!
[5] pry(main)> Service.decrypt
Service.decrypt
[5] pry(main)> Service.decrypt_table!
NoMethodError: undefined method `decrypt_table!' for Service (call 'Service.connection' to establish a connection):Class
from /media/mkalita/storage/efiduty_local/vendor/bundle/ruby/2.2.0/gems/activerecord-4.1.6/lib/active_record/dynamic_matchers.rb:26:in `method_missing'
I would love to see an example of how to implement the key and salt securely.
How long do they need to be? Should they be the same for each column or generate a random one each time?
When attempting to db:migrate from a clean state (i.e. after db:drop/db:create), I'm getting the following error:
PG::Error: ERROR: relation "people" does not exist
LINE 4: WHERE a.attrelid = '"people"'::regclass
^
: SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '"people"'::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
If I comment-out the crypt_keeper call in my model, the migration will complete, as-expected.
Intended behavior: defer relations checks until database has been migrated.
PG::SyntaxError: ERROR: syntax error at or near "AS"
LINE 1: ...."last_four" = $1 AND ((pgp_sym_decrypt(cast(full AS bytea),...
Wouldn't this require a Create Cast statement to work?
Hey there. Let's say I'm using crypt_keeper to encrypt a text attribute on 'Model':
Example:
crypt_keeper :text, encryptor: :postgres_pgp, key: key
Works great. The encrypt and decrypt work as expected when getting records. However, my question is... when doing a sql call—the actual call is more complex with joins, etc—but for example's sake:
Model.where("text ILIKE ?", "%#{term}%")
text is not being decrypted, and hence there is no match. Is there a means by which to decrypt text so that i can make a call like that?
Thanks!
This innocent looking support matrix in .travis.yml is very deceptive
rvm:
- 1.9.3
- 2.0.0
- 2.1.1
gemfile:
- gemfiles/activerecord_3_1.gemfile
- gemfiles/activerecord_3_2.gemfile
- gemfiles/activerecord_4_0.gemfile
- gemfiles/activerecord_4_1.gemfile
- gemfiles/activerecord_4_2.gemfile
matrix
include:
- rvm: 2.2.0
gemfile: gemfiles/activerecord_4_2.gemfile
this runs 3x5+1 = 16 jobs and takes 21 minutes before you get feedback after commit.
We also need to add newer ruby versions: 2.3.0 at least, and the upcoming rails 5 version.
Any suggestions as to which jobs should be weeded out to make space for newer versions?
I need to run the fix that allows empty strings that's in 0.9.0.pre - just wandering when the stable version is likely to drop?
Cheers!
Getting this error on my show page after creating a new record:
Could not log "sql.active_record" event.
ArgumentError: invalid byte sequence in UTF-8 ["/Users/me/.rvm/gems/ruby-2.0.0-p451@App/gems/crypt_keeper-0.18.1/lib/crypt_keeper/log_subscriber/mysql_aes.rb:17:in `gsub'",
"/Users/me/.rvm/gems/ruby-2.0.0-p451@App/gems/crypt_keeper-0.18.1/lib/crypt_keeper/log_subscriber/mysql_aes.rb:17:in `sql_with_mysql_aes'"
I'm using crypt_keeper :name, :encryptor => :mysql_aes_new, :key => ENV['TOKEN_KEY'], salt: ENV['SALT'], :encoding => 'UTF-8'
I am trying to use the PostgreSQL PGP Public Key encryptor. However, mention somewhere in the docs that:
The private key is optional. If the private key is not present the ciphertext value is returned instead of the plaintext
and...
Encryption is possible with only a public key. Any server that needs access to the plaintext will need the private key.
Do you have any idea how I would do decryption (example code etc.) on the app server so I can keep the private keys completely off my database server?
Thanks for the awesome library!
I have the snippet below in my User
model:
crypt_keeper :secret_field, encryptor: :aes_new, key: ENV['SECRET_KEY_BASE'], salt: 'secret salt'
It's encrypting the data using the specified encryptor just fine, however, when calling the save
method on the model it resets the :secret_field
on the model causing the updated_at
to also be reset.
For example, on another model, the snippet below would do nothing:
client = Client.find(1)
client.save
However, on a model with crypt keeper it updates the encrypted field and then sets the updated_at. How can this be prevented?
Perhaps this is expected behavior and I'm missing something. Having issues with certain validations failing once the crypt_keeper
method is applied to the attribute. For example I have a model which defines encryption for the title
attribute:
crypt_keeper :title, encryptor: :aes_new, key: 'super_good_password', salt: 'salt'
Once I add the title
attribute to crypt_keeper the uniqueness validation will fail. Below is the validation:
validates_uniqueness_of :title, scope: :assessment_component
Rspec test syntax:
it { should validate_uniqueness_of(:title).scoped_to(:assessment_component_id) }
Test output when actually run with rspec:
1) AssessmentFinding should require case sensitive unique value for title scoped to assessment_component_id
Failure/Error: it { should validate_uniqueness_of(:title).scoped_to(:assessment_component_id) }
Expected errors to include "has already been taken" when title is set to "a",
got no errors
# ./spec/models/assessment_finding_spec.rb:33:in `block (2 levels) in <top (required)>'
Any insight of how I can properly test the uniqueness of this attribute and still provide encryption?
I am attempting to call crypt_keeper more than once in my model:
class Person < ActiveRecord::Base
crypt_keeper :field1, :field2, encryptor: :postgres_pgp_pub_key, public_key: KEY_DATA
crypt_keeper :field3, :field4, encryptor: :postgres_pgp_pub_key, public_key: KEY_DATA, private_key: OTHER_KEY_DATA
end
The first crypt_keeper call gets wiped-out by the second crypt_keeper call.
Desired effect: per-field encryption/decryption according to crypt_keeper settings for that field.
Given an activerecord table with a text column created with array: true
t.text "my_column", default: [], array: true
And
class MyModel < ActiveRecord::Base
crypt_keeper :my_column, encryptor: :aes_new, key: 'CRYPT_KEEPER_KEY', salt: 'CRYPT_KEEPER_SALT'
end
MyModel.encrypt_table!
fails:
TypeError: no implicit conversion of Array into String
from /Users/fritz/devprojects/smartpropertysystems/.gems/gems/aes-0.5.0/lib/aes/aes.rb:119:in `update'
from /Users/fritz/devprojects/smartpropertysystems/.gems/gems/aes-0.5.0/lib/aes/aes.rb:119:in `_encrypt'
from /Users/fritz/devprojects/smartpropertysystems/.gems/gems/aes-0.5.0/lib/aes/aes.rb:58:in `encrypt'
from /Users/fritz/devprojects/smartpropertysystems/.gems/gems/aes-0.5.0/lib/aes/aes.rb:5:in `encrypt'
It would be good if CryptKeeper could encrypt each value in the text[] column.
See #53
Originally mentioned in #87
Right now the README mentions the new salt option only once, in the sample code.
I have to admit I'm confused about the meaning and usage of this new option.
Traditionally salts are pseudo random characters that are not shared across the whole application but instead are stored at the entity level (think password
column and password_salt
column in a DB) to make unique hashes even if the input data is the same.
Having that in mind, as it is in the code sample the salt is going to be the same for every record, isn't it? If so, what's the point in the first place?
More details than the ones stated in this commit message would be welcome.
Perhaps I'm getting it totally wrong, but is it possible to store the salt as column in the database on a per row level?
I think about other enryption solutions, that store one salt per encrypted record
Just to clarify, why is salt the same for all record of a model?
Thanks in advance!
Best Regards,
Roberto
The code responsible for ensuring a column type is text should be moved to saving, rather than initiation. It can cause issues when things try to access your model before a connection is to the database is made.
https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/model.rb#L97-101
cc @itspriddle
I gave 0.9.0.pre a test run yesterday, and found a change in behavior from 0.8.0 in that it throws an exception when attempting to save an AR object where the crypt_keeper field is nil.
I'd previously noticed a problem with AR objects and empty strings.
Researching AES just a little bit, it seems AES cannot encrypt an empty string. I'm not sure how other projects/libraries handle it (mysql AES encryption for example works on an empty string).
I suggest we modify the AES encryptor to return nils and empty-strings as-is, like so:
# Public: Decrypt a string
#
# Returns a string
def decrypt(value)
return nil if value.nil?
return '' if value == ''
...
end
# Public: Encrypt a string
#
# Returns a string
def encrypt(value)
return nil if value.nil?
return '' if value == ''
...
end
I'm happy to submit a pull request for this if you agree this should happen.
I get this when running crypt_keeper to upgrade to aes_new. The columns in the table I'm updating have no nil values in them. I haven't looked for where the nil is coming from though.
/Users/Cedric/.rvm/gems/ruby-2.1.1@spaceman/gems/crypt_keeper-0.17.0/lib/crypt_keeper/provider/aes.rb:53:in `iv=': no implicit conversion of nil into String (TypeError)
from /Users/Cedric/.rvm/gems/ruby-2.1.1@spaceman/gems/crypt_keeper-0.17.0/lib/crypt_keeper/provider/aes.rb:53:in `decrypt'
from /Users/Cedric/.rvm/gems/ruby-2.1.1@spaceman/gems/crypt_keeper-0.17.0/bin/crypt_keeper:82:in `block (3 levels) in reencrypt_all'
So I couldn't use your upgrade scripts because of whatever the reason was so needed to write my own upgrade scripts. Problem is it is taking hours for a mere 10 000 attribute values. How can I speed it up?
$ $(which crypt_keeper) --old-key 'KEY' --new-key 'NEW_KEY' --salt 'SALT' --table-name 'users' --columns 'ssn' --old-encryptor aes
`rescue in find_sti_class': The single-table inheritance mechanism failed to locate the subclass: 'Admin'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite MigrateData.inheritance_column to use another column for that information. (ActiveRecord::SubclassNotFound)
Hi
Minor problem. Thank you for a usefull gem :-)
I am using crypt_keeper (0.13.1) with encryptor aes in an linux, rails 4, ruby 2, sqllite3 project Work fine except when I enter UTF-8 characters in an html from.
Gets ActionView::Template::Error (incompatible character encodings: ASCII-8BIT and UTF-8) when the saved data is displayed after submit.
Works if I remove the attribute from crypt_keeper column list. Verifies that there are no other utf8 encoding problems in my project).
Works if I adds .force_encoding("UTF-8") where the data is displayed in view.
Ok to add .force_encoding("UTF-8") in views, but maybe there is a better solution?!
Kind Regards
Jan
I'm giving this gem a quick try, I applied this into my model:
crypt_keeper :token, :secret, encryptor: :aes, key: 'super_good_password', salt: 'salt'
2.0.0p353 :001 > AuthToken.first
TypeError: no implicit conversion of nil into String
gems/aes-0.5.0/lib/aes/aes.rb:76:in `update'
gems/aes-0.5.0/lib/aes/aes.rb:76:in `decrypt'
gems/aes-0.5.0/lib/aes/aes.rb:9:in `decrypt'
bundler/gems/crypt_keeper-1c9050c4c9db/lib/crypt_keeper/provider/aes.rb:34:in `decrypt'
bundler/gems/crypt_keeper-1c9050c4c9db/lib/crypt_keeper/helper.rb:34:in `load'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods/serialization.rb:89:in `unserialize'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods/serialization.rb:80:in `unserialized_value'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods/serialization.rb:67:in `type_cast'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods/read.rb:101:in `block in read_attribute'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods/read.rb:84:in `fetch'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods/read.rb:84:in `read_attribute'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/attribute_methods.rb:272:in `attribute_for_inspect'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/core.rb:354:in `block in inspect'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/core.rb:352:in `collect'
bundler/gems/rails-b23d3ffcd157/activerecord/lib/active_record/core.rb:352:in `inspect'
bundler/gems/rails-b23d3ffcd157/railties/lib/rails/commands/console.rb:90:in `start'
In the fetched row secret
is nil, however it was my understanding that this should just return the original value instead of raising: https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes.rb#L28
I like crypt_keeper
. I dislike writing getters/setters for attributes to encrypt/decrypt them appropriately. crypt_keeper
abstracts that away for me.
I'm getting this exception whenever I try to call any instance method on the model:
OpenSSL::Cipher::CipherError: iv length too short
This is the line where I invoke crypt_keeper:
crypt_keeper :secret, :encryptor => :aes, :key => ENV['SECRET']
Note that secret
is attr_accessible
.
I know I'm doing something wrong here, but I'm uncertain of what.
What is the minimum iv length? What is iv? What am I doing wrong here?
Here's the full stack trace:
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/crypt_keeper-0.13.1/lib/crypt_keeper/provider/aes.rb:52:in `iv='
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/crypt_keeper-0.13.1/lib/crypt_keeper/provider/aes.rb:52:in `decrypt'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/crypt_keeper-0.13.1/lib/crypt_keeper/model.rb:111:in `decrypt'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/crypt_keeper-0.13.1/lib/crypt_keeper/model.rb:54:in `block in decrypt_callback'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/crypt_keeper-0.13.1/lib/crypt_keeper/model.rb:51:in `each'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/crypt_keeper-0.13.1/lib/crypt_keeper/model.rb:51:in `decrypt_callback'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:405:in `_run__2196557130005149586__find__2474344336412501888__callbacks'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:405:in `__run_callback'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:385:in `_run_find_callbacks'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:81:in `run_callbacks'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/base.rb:523:in `init_with'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/inheritance.rb:68:in `instantiate'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/querying.rb:38:in `block (2 levels) in find_by_sql'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/querying.rb:38:in `collect!'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/querying.rb:38:in `block in find_by_sql'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/explain.rb:41:in `logging_query_plan'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/querying.rb:37:in `find_by_sql'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/newrelic_rpm-3.6.5.130/lib/new_relic/agent/method_tracer.rb:518:in `block in find_by_sql_with_trace_ActiveRecord_self_name_find_by_sql'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/newrelic_rpm-3.6.5.130/lib/new_relic/agent/method_tracer.rb:268:in `trace_execution_scoped'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/newrelic_rpm-3.6.5.130/lib/new_relic/agent/method_tracer.rb:513:in `find_by_sql_with_trace_ActiveRecord_self_name_find_by_sql'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation.rb:171:in `exec_queries'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation.rb:160:in `block in to_a'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/explain.rb:34:in `logging_query_plan'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation.rb:159:in `to_a'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation/finder_methods.rb:380:in `find_first'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation/finder_methods.rb:122:in `first'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation/finder_methods.rb:338:in `find_one'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation/finder_methods.rb:314:in `find_with_ids'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/relation/finder_methods.rb:107:in `find'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/activerecord-3.2.13/lib/active_record/querying.rb:5:in `find'
/Users/jarred/Code/G/lib/updator.rb:19:in `perform'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/processor.rb:48:in `block (3 levels) in process'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:109:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:109:in `block in invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/newrelic_rpm-3.6.5.130/lib/new_relic/agent/instrumentation/sidekiq.rb:25:in `block in call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/newrelic_rpm-3.6.5.130/lib/new_relic/agent/instrumentation/controller_instrumentation.rb:318:in `perform_action_with_newrelic_trace'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/newrelic_rpm-3.6.5.130/lib/new_relic/agent/instrumentation/sidekiq.rb:21:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:111:in `block in invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/bugsnag-1.4.0/lib/bugsnag/sidekiq.rb:5:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:111:in `block in invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/server/active_record.rb:6:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:111:in `block in invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/server/retry_jobs.rb:49:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:111:in `block in invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/server/logging.rb:11:in `block in call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/logging.rb:22:in `with_context'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/server/logging.rb:7:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:111:in `block in invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:114:in `call'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/middleware/chain.rb:114:in `invoke'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/processor.rb:47:in `block (2 levels) in process'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/processor.rb:102:in `stats'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/processor.rb:46:in `block in process'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/processor.rb:83:in `do_defer'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/sidekiq-2.13.0/lib/sidekiq/processor.rb:37:in `process'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/celluloid-0.14.1/lib/celluloid/calls.rb:25:in `public_send'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/celluloid-0.14.1/lib/celluloid/calls.rb:25:in `dispatch'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/celluloid-0.14.1/lib/celluloid/calls.rb:125:in `dispatch'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/celluloid-0.14.1/lib/celluloid/actor.rb:326:in `block in handle_message'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/celluloid-0.14.1/lib/celluloid/tasks.rb:42:in `block in initialize'
/Users/jarred/.rvm/gems/ruby-2.0.0-p0/gems/celluloid-0.14.1/lib/celluloid/tasks/task_fiber.rb:11:in `block in create'
I'm using crypt_keeper
0.13.1 and this is a Rails 3.2.13 app. ENV['SECRET']
is not nil.
Meantime while this gem is really cool, it's not fully transparent for models. I can mention at least two things it affects:
attribute_before_type_cast
method. So, if I have date encrypted field, and I've figured out typecasting from string to DateTime object, I can't get string value for date. attribute
will return DateTime, while attribute_before_type_cast
will return encrypted value for me.So, I need, the best way is to encrypt/decrypt somewhere before attribute_before_type_cast
method + provide cool solution for rails casting to integer, date or datetime types. This way the gem will be really transparent, and even more rocks!
Thank you!
According to the documentation provider/aes_new.rb, the aes_new returns empty string when encounters empty string, instead it raises exception
2.3.0 :036 > @aes_new_encryptor.encrypt("")
ArgumentError: data must not be empty
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/aes-0.5.0/lib/aes/aes.rb:119:in `update'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/aes-0.5.0/lib/aes/aes.rb:119:in `_encrypt'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/aes-0.5.0/lib/aes/aes.rb:58:in `encrypt'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/aes-0.5.0/lib/aes/aes.rb:5:in `encrypt'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/crypt_keeper-0.20.0/lib/crypt_keeper/provider/aes_new.rb:27:in `encrypt'
from (irb):36
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/railties-4.1.7/lib/rails/commands/console.rb:90:in `start'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/railties-4.1.7/lib/rails/commands/console.rb:9:in `start'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/railties-4.1.7/lib/rails/commands/commands_tasks.rb:69:in `console'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/railties-4.1.7/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
from /Users/hanjie/.rvm/gems/ruby-2.3.0/gems/railties-4.1.7/lib/rails/commands.rb:17:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
Any suggestions to how I might go about making sure that an encrypted columns value is unique across all rows?
alias_method_chain
is deprecated as of Rails 5.
A potential patch, but looking through logs I noticed that RSpec is complaining about stubbing, so testing that avoids that would be preferable. I'll dig more into it when I have time, for now Rails 5 users will see deprecation notices when the log scrubbers are loaded.
Patch:
commit 194d2a311bdfa9b1b592cfb80b22a156b1917bf5
Author: Joshua Priddle <[email protected]>
AuthorDate: Thu Sep 22 14:31:31 2016 -0400
Commit: Joshua Priddle <[email protected]>
CommitDate: Thu Sep 22 14:31:31 2016 -0400
Prefer Module#prepend over alias_method_chain
diff --git a/lib/crypt_keeper/log_subscriber/mysql_aes.rb b/lib/crypt_keeper/log_subscriber/mysql_aes.rb
index 72af0e1..9dd98cd 100644
--- a/lib/crypt_keeper/log_subscriber/mysql_aes.rb
+++ b/lib/crypt_keeper/log_subscriber/mysql_aes.rb
@@ -4,14 +4,12 @@ require 'active_support/lazy_load_hooks'
module CryptKeeper
module LogSubscriber
module MysqlAes
- extend ActiveSupport::Concern
-
- included do
- alias_method_chain :sql, :mysql_aes
- end
-
# Public: Prevents sensitive data from being logged
- def sql_with_mysql_aes(event)
+ #
+ # event - An ActiveSupport::Notifications::Event
+ #
+ # Returns a boolean.
+ def sql(event)
filter = /(aes_(encrypt|decrypt))\(.*\)/i
payload = event.payload[:sql]
.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
@@ -22,12 +20,12 @@ module CryptKeeper
"#{$1}([FILTERED])"
end
- sql_without_mysql_aes(event)
+ super(event)
end
end
end
end
ActiveSupport.on_load :crypt_keeper_mysql_aes_log do
- ActiveRecord::LogSubscriber.send :include, CryptKeeper::LogSubscriber::MysqlAes
+ ActiveRecord::LogSubscriber.prepend CryptKeeper::LogSubscriber::MysqlAes
end
diff --git a/lib/crypt_keeper/log_subscriber/postgres_pgp.rb b/lib/crypt_keeper/log_subscriber/postgres_pgp.rb
index b021972..7109fcb 100644
--- a/lib/crypt_keeper/log_subscriber/postgres_pgp.rb
+++ b/lib/crypt_keeper/log_subscriber/postgres_pgp.rb
@@ -4,14 +4,12 @@ require 'active_support/lazy_load_hooks'
module CryptKeeper
module LogSubscriber
module PostgresPgp
- extend ActiveSupport::Concern
-
- included do
- alias_method_chain :sql, :postgres_pgp
- end
-
# Public: Prevents sensitive data from being logged
- def sql_with_postgres_pgp(event)
+ #
+ # event - An ActiveSupport::Notifications::Event
+ #
+ # Returns a boolean.
+ def sql(event)
filter = /(\(*)pgp_(sym|pub)_(?<operation>decrypt|encrypt)(\(+.*\)+)/im
payload = event.payload[:sql]
.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
@@ -22,12 +20,12 @@ module CryptKeeper
"#{$~[:operation]}([FILTERED])"
end
- sql_without_postgres_pgp(event)
+ super(event)
end
end
end
end
ActiveSupport.on_load :crypt_keeper_postgres_pgp_log do
- ActiveRecord::LogSubscriber.send :include, CryptKeeper::LogSubscriber::PostgresPgp
+ ActiveRecord::LogSubscriber.prepend CryptKeeper::LogSubscriber::PostgresPgp
end
diff --git a/spec/log_subscriber/mysql_aes_spec.rb b/spec/log_subscriber/mysql_aes_spec.rb
index 67434b9..c681a22 100644
--- a/spec/log_subscriber/mysql_aes_spec.rb
+++ b/spec/log_subscriber/mysql_aes_spec.rb
@@ -16,6 +16,14 @@ module CryptKeeper::LogSubscriber
subject { ::ActiveRecord::LogSubscriber.new }
+ let(:logger) do
+ subject.send(:logger)
+ end
+
+ def verify_log(query)
+ logger.should_receive(:debug).with(/#{Regexp.escape(query)}/)
+ end
+
let(:input_query) do
"SELECT aes_encrypt('encrypt_value', 'encrypt_key'), aes_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
end
@@ -33,25 +41,19 @@ module CryptKeeper::LogSubscriber
end
it "filters aes functions" do
- subject.should_receive(:sql_without_mysql_aes) do |event|
- event.payload[:sql].should == output_query
- end
+ verify_log(output_query)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
end
it "filters aes functions in lowercase" do
- subject.should_receive(:sql_without_mysql_aes) do |event|
- event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
- end
+ verify_log(output_query.downcase.gsub(/filtered/, 'FILTERED'))
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
end
it "filters aes functions when searching" do
- subject.should_receive(:sql_without_mysql_aes) do |event|
- event.payload[:sql].should == output_search_query
- end
+ verify_log(output_search_query)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_search_query }))
end
@@ -64,7 +66,7 @@ module CryptKeeper::LogSubscriber
it "skips logging if CryptKeeper.silence_logs is set" do
CryptKeeper.silence_logs = true
- subject.should_not_receive(:sql_without_mysql_aes)
+ logger.should_not_receive(:debug)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
end
diff --git a/spec/log_subscriber/postgres_pgp_spec.rb b/spec/log_subscriber/postgres_pgp_spec.rb
index 6f55c3c..524b834 100644
--- a/spec/log_subscriber/postgres_pgp_spec.rb
+++ b/spec/log_subscriber/postgres_pgp_spec.rb
@@ -6,6 +6,14 @@ module CryptKeeper::LogSubscriber
CryptKeeper.silence_logs = false
end
+ let(:logger) do
+ subject.send(:logger)
+ end
+
+ def verify_log(query)
+ logger.should_receive(:debug).with(/#{Regexp.escape(query)}/)
+ end
+
use_postgres
context "Symmetric encryption" do
@@ -33,25 +41,19 @@ module CryptKeeper::LogSubscriber
end
it "filters pgp functions" do
- subject.should_receive(:sql_without_postgres_pgp) do |event|
- event.payload[:sql].should == output_query
- end
+ verify_log(output_query)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
end
it "filters pgp functions in lowercase" do
- subject.should_receive(:sql_without_postgres_pgp) do |event|
- event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
- end
+ verify_log(output_query.downcase.gsub(/filtered/, 'FILTERED'))
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
end
it "filters pgp functions when searching" do
- subject.should_receive(:sql_without_postgres_pgp) do |event|
- event.payload[:sql].should == output_search_query
- end
+ verify_log(output_search_query)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_search_query }))
end
@@ -64,7 +66,7 @@ module CryptKeeper::LogSubscriber
it "skips logging if CryptKeeper.silence_logs is set" do
CryptKeeper.silence_logs = true
- subject.should_not_receive(:sql_without_postgres_pgp)
+ logger.should_not_receive(:debug)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
end
@@ -96,17 +98,13 @@ module CryptKeeper::LogSubscriber
end
it "filters pgp functions" do
- subject.should_receive(:sql_without_postgres_pgp) do |event|
- event.payload[:sql].should == output_query
- end
+ verify_log(output_query)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
end
it "filters pgp functions in lowercase" do
- subject.should_receive(:sql_without_postgres_pgp) do |event|
- event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
- end
+ verify_log(output_query.downcase.gsub(/filtered/, 'FILTERED'))
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
end
@@ -114,7 +112,7 @@ module CryptKeeper::LogSubscriber
it "skips logging if CryptKeeper.silence_logs is set" do
CryptKeeper.silence_logs = true
- subject.should_not_receive(:sql_without_postgres_pgp)
+ logger.should_not_receive(:debug)
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
end
I've installed the gem and am using MySQL AES and the encryptor seen in this repository.
I added the line below to the model for which I'm encrypting some columns:
crypt_keeper :field1, :field2, :encryptor => :mysql_aes_new, :key => ENV['SECRET_KEY_BASE'], :salt => 'random string'
After running MyModel.encrypt_table!
the data had been encrypted and when trying User.last.field1
it decrypted on-the-fly (which is cryptkeeper working). However, a few hours later when doing the same thing the data was scrambled and seems unrecoverable. I have backups so it's not a problem, but I want to be encrypting this data and this is causing a problem.
Why would this be happening?
I'm not sure if this is technically possible yet. I'll have to find out when you're required to hook into logs to see if loading it later is even an option.
Playing around with the beta of Rails 5.0, and hit this gem compatibility issue:
Bundler could not find compatible versions for gem "activerecord":
In Gemfile:
crypt_keeper (~> 0.20) was resolved to 0.20.0, which depends on
activerecord (< 4.3, >= 3.1)
rails (~> 5.0.0.beta1) was resolved to 5.0.0.beta1, which depends on
activerecord (= 5.0.0.beta1)
Happy to investigate how the compatibility of the gem with Rails 5.0 and submit a PR updating the dependencies, if nobody else is working on it already.
Hi, i use crypt_keeper and it's really cool, but notice a big amount of
(0.2ms) SELECT AES_DECRYPT([FILTERED])
(0.4ms) SELECT AES_DECRYPT([FILTERED])
(0.2ms) SELECT AES_DECRYPT([FILTERED])
(0.2ms) SELECT AES_DECRYPT([FILTERED])
Requests, as i understand it's for decryption of loaded records, is there any possibility to make keeper more lazy, for example to do request only when i try to access to encrypted data, because usually i do not need that secret stuff
It seems like Crypt Keeper depends on activesupport (< 4.2, >= 3.1)
which precludes Rails 4.2. Could this be changed without breaking anything?
I would expect the below spec to be true but unfortunately I get something completely different back.
- Address validations utf-8 encoded addreses should decrypt properly
Failure/Error: expect(newly_saved.city).to eq 'Tromsø'expected: "Tromsø" got: "Troms\xC3\xB8"
describe "utf-8 encoded addreses" do
let(:address) { build :address, player_id: 1, city: 'Tromsø' }
it "should decrypt properly" do
address.save!
newly_saved = Address.find(address.id)
expect(newly_saved.city).to eq 'Tromsø'
end
end
Any suggestions @jmazzi ?
Is it possible to have different key per record?
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.