読者です 読者をやめる 読者になる 読者になる

ディレクトリごとファイルをパスワードで暗号化する [Ruby]

ファイルをパスワードで暗号化するには openssl が便利だ。
ruby の標準ライブラリには openssl が入っているのでそのまま使える。
参考 : openssl の暗号化と同等のrubyでの処理 - それマグで!, class OpenSSL::Cipher (Ruby 2.2.0)
クラウドや外付けストレージにたくさんのファイルを保存する場合、あるいは一台の PC を共有している場合はディレクトリごと暗号化したいと思うことがある。
ディレクトリを zip 化してパスワードをかける方法もあるが、今回は直接すべてのファイルを暗号化してみた。

環境 : OSX 10.9.5 Ruby 2.0.0

# encrypt.rb
require 'openssl'
require 'optparse'

def encrypt_file(data, password)
  salt = OpenSSL::Random.random_bytes(8)
  enc = OpenSSL::Cipher.new("AES-256-CBC")
  enc.encrypt
  key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 2000,
                                           enc.key_len + enc.iv_len)
  enc.key = key_iv[0, enc.key_len]
  enc.iv = key_iv[enc.key_len, enc.iv_len]
  encrypted_data = ""
  encrypted_data << enc.update(data)
  encrypted_data << enc.final
  "Salted__"+ salt + encrypted_data
end

def decrypt_file(data, password)
  dec = OpenSSL::Cipher.new("AES-256-CBC")
  dec.decrypt
  data = data.force_encoding("ASCII-8BIT")
  salt = data[8, 8]
  data = data[16, data.size]
  key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 2000,
                                           dec.key_len + dec.iv_len)
  dec.key = key_iv[0, dec.key_len]
  dec.iv = key_iv[dec.key_len, dec.iv_len]
  decrypted_data = ""
  decrypted_data << dec.update(data)
  decrypted_data << dec.final
end

def rec_dir(dir, sym)
  files = Dir.entries(dir).delete_if {|i| i == "." or i == ".."}
  Dir.chdir(dir)
  wd = Dir.pwd
  files.each do |file|
    Dir.chdir(wd)
    if File.directory?(file)
      rec_dir(file, sym)
    elsif File.file?(file)
      puts "#{sym}rypted #{file}"
      data = open(file, "r").read
      if data.size != 0
        if sym == :enc
          open("#{file}.enc", "w"){|f| f.write encrypt_file(data, PASSWORD)}
          File.delete(file)
        elsif sym == :dec and file =~ /.enc$/
          open(file.gsub(/.enc$/,""), "w"){|f| f.write decrypt_file(data, PASSWORD)}
          File.delete(file)
        end
      end
    end
  end
end

params = ARGV.getopts("e:d:p:")

if params["e"] and !params["d"] and params["p"]
  PASSWORD = params["p"]
  rec_dir(params["e"],:enc)
elsif params["d"] and !params["e"] and params["p"]
  PASSWORD = params["p"]
  rec_dir(params["d"],:dec)
else
  puts "illegal args!"
end
$ ruby encrypt.rb -e dirpath -p password

で暗号化

$ ruby encrypt.rb -d dirpath -p password

で復号化する。

ディレクトリ内の暗号化されたファイルには .enc の拡張子が付き、復号化すると外れる。
ディレクトリを再帰的に探索するので、内部のすべてのファイルが暗号化される。
openssl では salt はオプションだが、すべてのファイルを同じパスワードで暗号化するのでここでは必須だ。
合計サイズが GB 単位でも数分で終わるので、まあ許容範囲だろう。

欠点として無関係の .enc 拡張子がすでにあると止まってしまうということがある。
また元ファイルは削除されるのでパスワードを無くしてしまうと一巻の終わりだ。
zip でもそうだが、ファイル名は暗号化されないので注意が必要だ。