パスワードの強度

奥村先生のパスワード再考というエントリを読んで、パスワードの強度をビット長で表すプログラムをRubyで書いてみました。

# Base 2 logarithm
module Math
  def self.log2(n)
    log(n) / log(2)
  end
end

# Bit length
def password_strength_by_bits(ary, length)
  Math.log2(ary.length ** length)
end

lower = ('a'..'z').to_a
upper = ('A'..'Z').to_a
digit = ('0'..'9').to_a
alpha = lower + upper
alnum = lower + upper + digit

p password_strength_by_bits(lower, 8) #=> 37.6035177451287
p password_strength_by_bits(alpha, 8) #=> 45.6035177451287
p password_strength_by_bits(alnum, 8) #=> 47.633570483095

まずMath.log2を作るとこから始めるのが渋いよね(←自分で言うな)。
底の変換を考えるのが面倒だったので、perldoc -f logを読んだのは内緒です。
追記:
奥村先生のエントリのコメント欄で出ていた問題。
1. 「英文字(52通り)6文字と、数字(10通り)2文字を使うこと」を条件にして作った8文字のパスワードのビット長を計算します。
まず、文字種の順序を考えずに(英文字6文字の後に数字2文字という順序に固定して考えるという意味)、パスワードの場合の数を計算すると、52^{6} \times 10^2通りになる。次に8文字のどこを数字2文字にするかの組み合わせは{}_8C_2 = \frac{8 \times 7}{2 \times 1} = 28通りになる。したがって、

p Math.log2((52 ** 6) * (10 ** 2) * 28) #=> 45.6538494206789

となります。
2. 「英文字(52通り)6文字と、英数字(62通り)2文字を使うこと」を条件にして作った8文字のパスワードのビット長を計算します。
このパスワードは、上記の1に加えて、英文字8文字のパスワードを加えた場合に相当します。英文字8文字のパスワードの場合の数は52^{8}通りなので、

p Math.log2((52 ** 6) * (10 ** 2) * 28 + (52 ** 8)) #=> 46.6289030635486

となります。
まとめると…

英文字8文字                 約45.6ビット
英文字6文字+数字2文字      約45.7ビット
英文字6文字+英数字2文字    約46.6ビット
英数字8文字                 約47.6ビット

追記: