LoginSignup
2
0

More than 5 years have passed since last update.

Ruby: 「それっぽい」パスワードを生成する

Last updated at Posted at 2018-03-15

いくつかの制限を設けた、初期値パスワードとして使うランダム文字列をユルく生成する。

要件

  • 一部のまぎらわしい文字を使用しない(今回は o, O, 0)
  • 同じ文字が3つ以上連続しない
  • 記号を1〜2つ含むよう指示するオプションを設ける
  • 最初と最後の文字には記号は使わない

制限

  • 生成できる長さは 32 文字までとする
    • これ以上増やすと、連続しないパターンを生成する計算量が爆発する

使用例

Password.new.generate(16)                  # hk84wnIskvCBDITz
Password.new(symbol: true).generate(16)    # EIf{7fpkexis.vvs

実装

class Password
  require 'securerandom'

  CHAR_LIST = 'abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789'.split('').freeze    # omit o,O,0
  SYMBOL_LIST = '._-=[]{}+#^!?'.split('').freeze
  # SYMBOL_LIST = '"#$%&\'()*+,-./:;<=>?[\]^_`{|}~'.split('').freeze
  SYMBOL_LIST_ESCAPED = SYMBOL_LIST.reduce('') { |r, s| r + '\\' + s }.freeze

  def initialize(symbol: false)
    @symbol = symbol
  end

  def generate(len = 8)
    raise 'too short' if len < 4
    raise 'too long'  if len > 32

    loop do
      result = (0..len - 1).reduce('') { |r, n| r + pick_one(len, n) }
      return result if check(result)
    end
  end

  private

  def pick_one(len, n)
    list = char_list(len, n)

    list[(SecureRandom.random_number(1.0) * list.size).floor]
  end

  def char_list(len, n)
    !@symbol || (n.zero? || n == len - 1) ? CHAR_LIST : CHAR_LIST + SYMBOL_LIST
  end

  def count_symbol(str)
    str.scan(Regexp.new('[%s]' % SYMBOL_LIST_ESCAPED)).size
  end

  def check(str)
    # omit successive chars
    return false if /(.)\1\1/ =~ str

    # confirm the str contains 1..2 symbols
    if @symbol
      return false if count_symbol(str).zero?
      return false if count_symbol(str) > 2
    end

    true
  end
end
2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0