{"id":161,"date":"2008-04-14T11:28:00","date_gmt":"2008-04-14T19:28:00","guid":{"rendered":"http:\/\/onehub.com\/blog\/posts\/encrypting-your-files-with-rails-part-i-2"},"modified":"2018-12-20T07:30:15","modified_gmt":"2018-12-20T15:30:15","slug":"encrypting-your-files-with-rails-part-i-2","status":"publish","type":"post","link":"https:\/\/www.onehub.com\/blog\/2008\/04\/14\/encrypting-your-files-with-rails-part-i-2\/","title":{"rendered":"Encrypting your files with Rails &#8211; Part I"},"content":{"rendered":"<h4>This is the first post in a two part series on adding encryption to attachment_fu for Rails applications.<\/h4>\n<p>Let\u2019s say that you\u2019re building an application that will be used by a number of different people, and it involves storing information in files, and providing that information to the right person(s) at the right time.<\/p>\n<p>There are choices about where to store the \u2018files\u2019 for users \u2013 in a database, in a file system (on disk), or in a specialized store (e.g. Amazon S3). For this example, we\u2019re going to choose storing files \u2018on disk\u2019 in a file system \u2013 locally, across the network, NAS, SAN \u2013 wherever, as long as we can \u2018see\u2019 the information as a file.<\/p>\n<p>One traditional way to enforce permissions on files is to have the file system itself enforce whether someone can or can\u2019t have access to a file. In the old days, on *nix systems, that meant juggling user and group databases, and working within the \u2018user-group-other\u2019 paradigm. More modern attributed file systems make this easier, however that might not work with your web application, because you might not want to synchronize file system attribute information with your web identity information.<\/p>\n<p>You might ultimately decide to control access yourself using some sort of User information DB (homegrown, LDAP, etc.), and explicitly control access by protecting the URLs which download specific files.<\/p>\n<p>As a further measure, you might want to consider encrypting information on a file level. When a file is uploaded, you would generate an encryption key, encrypt the file on disk with that key, store the key separately, and use that key when a file is accessed (as close to the point that the file is downloaded as possible).<\/p>\n<p>For prototype or low-volume applications, let\u2019s look at what it would take to modify attachment_fu and a download controller action to accomplish this, and then extrapolate to what it might take in a higher-performance environment.<\/p>\n<p>Attachment_fu is a nice plug-in to quickly enable file uploads. Plenty of examples are available on how to use it \u2013 we\u2019re going to modify it to generate a an encryption password when a file is uploaded, then encrypt that file as it\u2019s stored into the file system.<br \/>\nHere\u2019s the original code for save<em>to_storage in attachment<\/em>fu\/backends\/file<em>system<\/em>backend.rb.<\/p>\n<pre><code>      #Saves the file to the file system\n      def save_to_storage\n        if save_attachment?\n          # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?\n          FileUtils.mkdir_p(File.dirname(full_filename))\n          File.cp(temp_path, full_filename)\n          File.chmod(attachment_options[:chmod] || 0644, full_filename)\n        end\n        @old_filename = nil\n        true\n      end\n<\/code><\/pre>\n<p>Borrowing some example code from OpenSSL for doing aes encryption (you could use a different method if you like), we\u2019ve modified save_to_storage to accept a key acme, which is used to encrypt the file as it is copied from temporary storage.<\/p>\n<pre><code>      #Saves the file to the file system\n      def save_to_storage(acme=nil)\n        if save_attachment?\n          # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?\n          FileUtils.mkdir_p(File.dirname(full_filename))\n          if acme.nil?\n            File.cp(temp_path, full_filename)\n          else\n            c = OpenSSL::Cipher::Cipher.new(\"aes-256-cbc\")\n            c.encrypt\n            #\n            c.key = key = acme\n            c.iv = iv = Digest::SHA1.hexdigest(\"OneFishTwoFish\")\n            output = File.open(full_filename,'wb')\n            File.open(temp_path, 'rb') do |file|\n              while buf = file.read(4096)\n                output.write(c.update(buf))\n              end\n              file.close\n            end\n            output.write(c.final)\n            output.close\n          end\n          File.chmod(attachment_options[:chmod] || 0644, full_filename)\n        end\n        @old_filename = nil\n        true\n      end\n<\/code><\/pre>\n<p>Where do we get \u2018acme\u2019? file<em>system<\/em>backend is called from attachment<em>fu.rb \u2013 after<\/em>process<em>attachment needs to generate a key, pass it to save<\/em>to<em>storage, and also make the key available in the model attachment<\/em>fu is mixed-in with.<\/p>\n<pre><code> # Cleans up after processing.  Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.\n    def after_process_attachment\n      if @saved_attachment\n        if respond_to?(:process_attachment_with_processing) &amp;&amp; thumbnailable? &amp;&amp; !attachment_options[:thumbnails].blank? &amp;&amp; parent_id.nil?\n          temp_file = temp_path || create_temp_file\n          attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }\n        end\n\n        myacme = nil\n        if attachment_options[:encrypted_storage]\n          myacme = Digest::SHA1.hexdigest(Clock.now.to_i.to_s+rand.to_s)\n        end\n\n        save_to_storage(myacme)\n\n        @temp_paths.clear\n        @saved_attachment = nil\n        write_attribute :acme, myacme\n        callback :after_attachment_saved\n      end\n    end\n\n<\/code><\/pre>\n<p>Note that we\u2019re using Clock.now.to<em>i.to<\/em>s+rand.to_s as the key\u2026 for a real-world example, you would likely additionally seed this with a phrase only known to you.<\/p>\n<p>Your model code should have an attribute named acme, of type string. When your model object is saved, acme will also be updated.<\/p>\n<p>UPDATE: Read <a href=\"https:\/\/www.onehub.com\/blog\/2008\/04\/21\/encrypting-your-files-with-rails-part-ii\/\">Part II<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the first post in a two part series on adding encryption to attachment_fu for Rails applications. Let\u2019s say that you\u2019re building an application that will be used by a number of different people, and it involves storing information [&hellip;]<\/p>\n","protected":false},"author":8,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_themeisle_gutenberg_block_has_review":false},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts\/161"}],"collection":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/comments?post=161"}],"version-history":[{"count":0,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts\/161\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/media?parent=161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/categories?post=161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/tags?post=161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}