{"id":162,"date":"2008-04-21T12:56:00","date_gmt":"2008-04-21T20:56:00","guid":{"rendered":"http:\/\/onehub.com\/blog\/posts\/encrypting-your-files-with-rails-part-ii"},"modified":"2018-12-20T07:32:39","modified_gmt":"2018-12-20T15:32:39","slug":"encrypting-your-files-with-rails-part-ii","status":"publish","type":"post","link":"https:\/\/www.onehub.com\/blog\/2008\/04\/21\/encrypting-your-files-with-rails-part-ii\/","title":{"rendered":"Encrypting your files with Rails &#8211; Part II"},"content":{"rendered":"<h4>This is the second post in a two part series (<a href=\"https:\/\/www.onehub.com\/blog\/2008\/04\/14\/encrypting-your-files-with-rails-part-i-2\/\">part 1<\/a> is here) on adding encryption to attachment_fu for Rails applications.<\/h4>\n<p>What about making the file available for download? AVOIDING THE ISSUE OF SCALABILITY FOR A MOMENT (since send<em>file is not the right way to serve files from Rails), we want to use a variant of send<\/em>file to do the decryption and send the file. Here\u2019s a modified version of send_file that uses an extra hash parameter (acme) to decrypt if provided:<\/p>\n<pre><code>module ActionController\n   module Streaming\n\n   def send_file_x(path, options = {}) #:doc:\n      raise MissingFile, \"Cannot read file #{path}\" unless File.file?(path) and File.readable?(path)\n\n      options[:length]   ||= File.size(path)\n      options[:filename] ||= File.basename(path)\n      send_file_headers! options\n\n      @performed_render = false\n      logger.warn(\"Sending file #{path}\")\n      if options[:stream]\n        render :status =&gt; options[:status], :text =&gt; Proc.new { |response, output|\n          logger.info \"Streaming file #{path}\" unless logger.nil?\n          len = options[:buffer_size] || 4096\n          if options[:acme]\n            c = OpenSSL::Cipher::Cipher.new(\"aes-256-cbc\")\n            c.decrypt\n            c.key = key = options[:acme]\n            c.iv = iv = Digest::SHA1.hexdigest(\"OneFishTwoFish\")\n          end\n          File.open(path, 'rb') do |file|\n            while buf = file.read(len)\n              if options[:acme]\n                output.write(c.update(buf))\n              else\n                output.write(buf)\n              end\n            end\n          end\n        }\n      else\n        logger.info \"Sending file #{path}\" unless logger.nil?\n        File.open(path, 'rb') { |file| render :status =&gt; options[:status], :text =&gt; file.read }\n      end\n    end\n  end\nend\n\n<\/code><\/pre>\n<p>The code could be made more efficient by not performing the options[:acme] test each time a buffer is written. Our controller action that downloads a file would call it like so:<\/p>\n<pre><code>send_file_x(@file_item.stored_filename,\n  :filename      =&gt;  @file_item.filename,\n  :type             =&gt;  @file_item.content_type,\n  :disposition  =&gt;  'attachment',\n  :stream    =&gt;  'true',\n  :buffer_size  =&gt;  4096,\n  :acme =&gt; @file_item.acme)\n\n<\/code><\/pre>\n<p>In a production environment, send_file consumes to many server resources \u2013 the rails application, and method used to service it (FastCGI, Mongrel, etc.) are tied up serving the file.<\/p>\n<p>It\u2019s more likely the case that the rails application will be behind a reverse proxy like nginx; in that case, a directive is sent to the server to provide the file (usually through an HTTP header). For nginx, serving a non-encrypted, static file would be done by sending a header with the location of a file:<\/p>\n<pre><code>if defined?(NGINX_FOR_DOWNLOAD) &amp;&amp; NGINX_FOR_DOWNLOAD\n  # code omitted \u2013 set up file name and path\n  response.headers['X-Accel-Redirect'] = NGINX_PATH_FOR_ _DOWNLOAD + path\n  response.headers['Content-Type'] = file_item.content_type\n  render :nothing=&gt;true;\nelse\n  send_file_x(File.join(RAILS_ROOT, FILE_STORAGE_PATH, path_parts, file_item.filename),\n      :type         =&gt; file_item.content_type,\n      :disposition  =&gt; 'attachment',\n      :stream    =&gt; 'true',\n      :buffer_size  =&gt; 4096,\n      :acme      =&gt; nil,\n      :encoding     =&gt; 'utf8',\n      :filename     =&gt; URI.encode(file_item.filename))\nEnd\n\n<\/code><\/pre>\n<p>For more information on nginx and rails, learn more about <a href=\"http:\/\/wiki.codemongers.com\/NginxXSendfile\">NginxXSendfile<\/a>.<\/p>\n<p>To perform a similar feat of decrypting and sending a file for nginx, a new module would need to be written for nginx that takes an additional header variable &#8216;X-Accel-Redirect-Key&#8217; and uses that as the key to send the file, decrypting as it goes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the second post in a two part series (part 1 is here) on adding encryption to attachment_fu for Rails applications. What about making the file available for download? AVOIDING THE ISSUE OF SCALABILITY FOR A MOMENT (since sendfile [&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\/162"}],"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=162"}],"version-history":[{"count":0,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/posts\/162\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/media?parent=162"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/categories?post=162"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.onehub.com\/blog\/wp-json\/wp\/v2\/tags?post=162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}