すぐに思いつく案としては、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
はやっ、、、
まとめ
- 生年月日や携帯電話の番号などをパスワードにしてはいけない。
- パスワードは共通の認識でなくてはならない。