LoginSignup
26
20

More than 5 years have passed since last update.

zxcvbn をつかって Rails のパスワードバリデーションを強化してみる

Last updated at Posted at 2012-12-26

脆弱なパスワードを放置すると、ユーザーのアカウントは常に危険にさらされます。
アカウントの乗っ取りが起こった場合にはウェブサービスの信頼性を疑われることにもなりかねません。

これらの問題に対処するためのライブラリ zxcvbn をつかって、信頼性低下を予防しましょう。

なにができるの?

脆弱なパスワードが設定された場合、以下の画像のように、「このパスワードは数分で破られます」といった具体的なメッセージを表示することができます。
強力なパスワードを使用する理由がユーザーに伝わりやすくなり、モチベーションをあまり低下させずに再入力を促すことができるようになります。

使用例

zxcvbn について、より詳しい情報は以下を閲覧してください。
http://tech.dropbox.com/?p=165
https://github.com/lowe/zxcvbn

Rails アプリケーションに組み込んでみる

では、実際に Rails アプリケーションに組み込んでみましょう。
まず zxcvbn-ruby という素晴らしい gem をインストールします。

Gemfile
gem "zxcvbn-ruby", :require => 'zxcvbn'
shell
bundle

モデルから簡単に使えるよう、バリデータつくってみましたんでコピペしてください。

app/validators/zxcvbn_validator.rb
require 'zxcvbn'

class ZxcvbnValidator < ActiveModel::EachValidator
  attr_reader :record, :attribute, :value

  SCORE_RANGE = (1..4).freeze

  def initialize(options)
    unless SCORE_RANGE.include? options[:score]
      raise ArgumentError, ":range must be a #{SCORE_RANGE.to_s.gsub('..', ' to ')}"
    end

    super
  end

  def validate_each(record, attribute, value)
    @record, @attribute, @value = record, attribute, value

    @score = options[:score]
    add_error unless valid?
  end

  private

  def valid?
    @messages_options = {}
    week_keywords = []

    for key, val in @record.attributes
      next if %w[password password_confirmation encrypted_password].include?(key)
      next unless val.kind_of?(String)

      week_keywords.push val if val

      for splited_val in val.split(/[@._]/)
        week_keywords.push splited_val
      end
    end

    result = Zxcvbn.test(value, week_keywords)
    @messages_options[:crack_time] = display_time result.crack_time
    result.score >= @score
  end

  def add_error
    if message = options[:message]
      record.errors[attribute] << message
    else
      record.errors.add attribute, :week_password, @messages_options
    end
  end

  def display_time(seconds)
    minute  = 60
    hour    = minute * 60
    day     = hour * 24
    month   = day * 31
    year    = month * 12
    century = year * 100

    case
    when seconds < minute
      unit = 'x_seconds'
      count = 1 + seconds.ceil
    when seconds < hour
      unit = 'x_minutes'
      count = 1 + (seconds / minute).ceil
    when seconds < day
      unit = 'about_x_hours'
      count = 1 + (seconds / hour).ceil
    when seconds < month
      unit = 'x_days'
      count = 1 + (seconds / day).ceil
    when seconds < year
      unit = 'x_months'
      count = 1 + (seconds / month).ceil
    when seconds < century
      unit = 'x_years'
      count = 1 + (seconds / year).ceil
    end

    I18n.t "#{unit}.other", scope: [:datetime, :distance_in_words], count: count
  end

end

パスワードの強度を単純に評価するだけでなく、
モデルに紐付く情報、たとえばユーザ名やメールアドレスなどに含まれる単語から
推測されるパスワードも強度が低いと判定されるようになっています。

メッセージも忘れずにどうぞ。

config/locales/ja.yml
ja:
  errors:
    messages:
      week_password: より複雑な%{attribute}を入力してください。あなたの%{attribute}は%{crack_time}で破られます。

ユーザモデルに追加する場合。強度は1から4まで指定できます。

app/models/user.rb
  validates :password, zxcvbn: {score: 3}
26
20
0

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
26
20