Help us understand the problem. What is going on with this article?

知人からその知人の生年月日がパスワードのパスワード付きzipファイルを渡されたけど、その人の生年月日なんて知らない場合の対処法

More than 3 years have passed since last update.

すぐに思いつく案としては、2つあります。

  1. 知人に生年月日を聞く
  2. 頑張ってその人の産まれていそうな日を入力する

案1は、知人は「私の生年月日がパスワードです。」って言って渡してきているので、当然自分が生年月日を知っていると思っているはずなので、その人に対して「すいません、生年月日を存じ上げません。」などと言うのは日本の文化としてはなかなか難しいことに感じます。
そういう場合は止むを得ず案2でしょうか。

パスワード付きzipファイルの展開方法(Ruby)

decryptできるgemを探したら、ZipRubyというのがあり、使いやすそうだったのでこれを使おうとしたのですが、展開を試みる時に一時ファイルを作ってしまうようなので、rubyzipを使うことにします。
名前が似ているのでどっちがどっちなのかわかりにくいですね、、、

使い方はこういう感じらしいです。

Zip::InputStream.open("my.zip") do |io|
  while (entry = io.get_next_entry)
    puts "Contents of #{entry.name}: '#{io.read}'"
  end
end

上はZip::InputStreamのopenにファイル名のみを渡していますが、第3引数にデクリプタが指定できるようです。

.open(filename_or_io, offset = 0, decrypter = nil) ⇒ Object

Decrypterを調べると、Zip::TraditionalDecrypterというのがあるようなので、これを使えば良さそうです。

生年月日のパスワード(文字列)を生成する方法

日付は普通にTimeとかDateを使えば良いのですが、現在わかっているのが知人の推定年齢だけなので、年齢を曖昧に指定できる必要があります。
というわけで、こんな感じにします。

# 推定年齢の範囲
RangeOfAge = (25..30)

# 年齢の範囲から誕生日の範囲に変換する。
def to_birthday_range(r)
  (Time.new(Time.new.year - r.last, 1, 1).to_date .. Time.new(Time.new.year - r.first, 12, 31).to_date)
end

# 日付からパスフレーズを生成する。
def to_passphrase(d)
  d.strftime("%Y%m%d")
end

# 使用例
to_birthday_range(RangeOfAge).each { |d| p to_passphrase(d) }

並列処理

並列で処理したほうが速いかもしれないので、簡単に分散処理してみます。
年単位でブロックにして、いくつかのスレッドでブロックを処理させるようにします。
また、根本的な話で、正解にたどり着けばそこで目的達成なので、馬鹿正直に全部試す必要はありません。推定年齢の範囲を指定するときに、おそらく正解であろう年齢を範囲の真ん中あたりに持ってきそうなので、真ん中から解析するように並び替えておくと効率がいいかもしれません。

実装

require 'time'
require 'thread'
require 'zip'

# 対象のファイル
FileName = ARGV[0]

# 推定年齢の範囲
RangeOfAge = (ARGV[1].to_i..ARGV[2].to_i)

# 年齢の範囲から誕生日の範囲に変換する。
def to_birthday_range(r)
  (Time.new(Time.new.year - r.last, 1, 1).to_date .. Time.new(Time.new.year - r.first, 12, 31).to_date)
end

# 日付からパスフレーズを生成する。
def to_passphrase(d)
  d.strftime("%Y%m%d")
end


# セクションクラス
class Sections
  def initialize(f, r)
    # 範囲の真ん中から解析するように並び替える。
    a = r.to_a
    b = []
    until a.empty?
      b << a.slice!(a.length / 2)
    end
    @sections = b.map { |i| { file: f, range: to_birthday_range(i..i), assign: false } }
    @semaphore = Mutex.new
    @result = nil
  end

  # 未処理のブロックを取得する。なければnilが返される。
  def next
    @semaphore.synchronize {
      return nil unless @result.nil?

      s = @sections.find { |i| !i[:assign] }
      return nil if s.nil?
      s[:assign] = true
      puts "assign #{s[:range]}"
      s
    }
  end

  # 完了を通知する。
  def finish(s, result)
    puts "#{s[:range]}: #{result}"
    unless result.nil?
      @result = result
    end
  end

  # 結果を取得する。
  def result
    @result
  end
end

# 解析クラス
class PasswordAnalyzer
  def initialize(s)
    @sections = s
    @thread = nil
  end

  def start
    @thread = Thread.new {
      # ブロックを取得する。
      while s = @sections.next
        result = nil
        s[:range].each { |d|
          # 完了していたら停止する。
          break unless @sections.result.nil?

          # 復号を試みる。
          pass = to_passphrase(d)
          dec = Zip::TraditionalDecrypter.new(pass)
          begin
            Zip::InputStream.open(s[:file], 0, dec) { |z|
              first_entry = z.get_next_entry
              next if first_entry.nil?
              r = z.read
              if r.size == first_entry.size
                result = pass
                break
              end
            }
          rescue => e
          end
        }

        # ブロックの処理の完了を通知する。
        @sections.finish(s, result)
      end
    }
  end

  # 処理の完了を待つ。
  def join
    unless @thread.nil?
      @thread.join
      @thread = nil
    end
  end
end

# 実行
sections = Sections.new(FileName, RangeOfAge)

analyzers = []
4.times { analyzers << PasswordAnalyzer.new(sections).start }
analyzers.each { |d| d.join }

puts "result: #{sections.result}"

結果

実際にもらったファイルじゃないけど一応結果を載せておいた方がいいと思ったので。

$ time bundle exec ruby unzip.rb sample.rb.zip 25 30
assign 1987-01-01..1987-12-31
assign 1988-01-01..1988-12-31
assign 1986-01-01..1986-12-31
assign 1989-01-01..1989-12-31
finish: 1987-01-01..1987-12-31:
assign 1985-01-01..1985-12-31
finish: 1989-01-01..1989-12-31:
assign 1990-01-01..1990-12-31
finish: 1986-01-01..1986-12-31: 19860601
finish: 1990-01-01..1990-12-31:
finish: 1988-01-01..1988-12-31:
finish: 1985-01-01..1985-12-31:
result: 19860601

real    0m0.898s
user    0m0.805s
sys 0m0.131s

はやっ、、、

まとめ

  • 生年月日や携帯電話の番号などをパスワードにしてはいけない。
  • パスワードは共通の認識でなくてはならない。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away