Class: PasswordTests
- Inherits:
-
Object
- Object
- PasswordTests
- Defined in:
- lib/password_tests.rb
Overview
PasswordTests Class
PasswordTests provides functionality for testing strength of passwords
Usage
-
Pass the constructor either a Password object or a String (or anything that can be converted to a String using
to_s
). -
Pass the constructor a Hash of tests and their corresponding parameters. The tests are method names of the PasswordTests object.
-
Execute the
run
method to execute the tests. -
Inspect the returned
Hash
to verify pass/fail for each test.
Examples
Test the length of the password
tests = PasswordTests.new( Password.new('foobar'), :test_length => 8)
results = tests.run => {:test_length => false}
Test the length of the password and for at least 2 upper case alpha characters.
tests = PasswordTests.new( 'FooBar', :test_length => 8,
:test_minimum_alphas => 2 )
results = tests.run => {:test_length => false,
:test_minimum_alphas => false}
Tests
These are the currently implemented tests; however, feel free to mix-in your own or extend the PasswordTests class.
- test_length
-
minimum length
- test_minimum_alphas
-
minimum number of alpha characters (upper and lower case)
- test_maximum_alphas
-
maximum number of alpha characters (upper and lower case)
- test_minimum_upper_alphas
-
minimum number of uppercase alpha characters
- test_maximum_upper_alphas
-
maximum number of uppercase alpha characters
- test_minimum_lower_alphas
-
minimum number of lowercase alpha characters
- test_maximum_lower_alphas
-
maximum number of lowercase alpha characters
- test_minimum_numerals
-
minimum number of numeric characters
- test_maximum_numerals
-
maximum number of numeric characters
- test_minimum_non_alphanumeric
-
minimum number of non-alphanumeric characters (symbols)
- test_maximum_non_alphanumeric
-
maximum number of non-alphanumeric characters (symbols)
Calculating the qualitative strength of a password
PasswordTests.qualitative_strength( '123456') => 0
PasswordTests.qualitative_strength( 'foo' ) => 0
PasswordTests.qualitative_strength( 'fooo' ) => 2
PasswordTests.qualitative_strength( 'foobar' ) => 12
PasswordTests.qualitative_strength( '1fo0^*bar9' ) => 95
Calculating the entropy of a password
Password.entropy('1') => 3.32192809488736
Password.entropy('123456') => 19.9315685693242
Password.entropy('foobar') => 28.2026383088466
Password.entropy('1fo0^*bar9') => 61.0852445677817
Class Method Summary collapse
-
.entropy(password) ⇒ Object
Calculates the entropy of the given password (in bits).
-
.qualitative_strength(password) ⇒ Object
Tests the strength of a given password qualitativly and returns a numerical strength.
-
.repetitions(password, splits = [], reps = {}) ⇒ Object
Finds all character sequences that are repeated in the password.
Instance Method Summary collapse
-
#default_test_params(level = :medium_low) ⇒ Object
Get a default test parameters hash of the given level of security.
-
#initialize(password, test_params = {}) ⇒ PasswordTests
constructor
Initialize the password tests (in
test_params
) with the givenpassword
. -
#run ⇒ Object
Run the tests.
-
#test_length(*params) ⇒ Object
Tests the length of the password.
-
#test_maximum_alphas(*params) ⇒ Object
Tests for a maximum number of alpha characters.
-
#test_maximum_lower_alphas(*params) ⇒ Object
Tests for a maximum number of lower case alpha characters.
-
#test_maximum_non_alpha(*params) ⇒ Object
Tests for a maximum number of non-alpha (i.e. not letters) characters.
-
#test_maximum_non_alphanumeric(*params) ⇒ Object
Tests for a maximum number of non-alphanumeric characters.
-
#test_maximum_numerals(*params) ⇒ Object
Tests for a maximum number of numeric characters.
-
#test_maximum_upper_alphas(*params) ⇒ Object
Tests for a maximum number of upper case alpha characters.
-
#test_minimum_alphas(*params) ⇒ Object
Tests for a minimum number of alpha characters.
-
#test_minimum_lower_alphas(*params) ⇒ Object
Tests for a minimum number of lower case alpha characters.
-
#test_minimum_non_alpha(*params) ⇒ Object
Tests for a minimum number of non-alpha (i.e. not letters) characters.
-
#test_minimum_non_alphanumeric(*params) ⇒ Object
Tests for a minimum number of non-alphanumeric characters.
-
#test_minimum_numerals(*params) ⇒ Object
Tests for a minimum number of numeric characters.
-
#test_minimum_upper_alphas(*params) ⇒ Object
Tests for a minimum number of upper case alpha characters.
Constructor Details
#initialize(password, test_params = {}) ⇒ PasswordTests
Initialize the password tests (in test_params
) with the given password
. If test_params
is left out or is an empty Hash, the medium-low default will be used from PasswordTests.default_test_params
Example: The following would run the test_length and test_minimum_alphas
tests (and both would fail on the given password)
tests = PasswordTests.new( 'FooBar', :test_length => 8,
:test_minimum_alphas => 2 )
Example: The following would run the high default level tests (and it would
fail on the given password)
tests = PasswordTests.new( 'FooBar', :high )
106 107 108 109 110 111 112 113 114 115 |
# File 'lib/password_tests.rb', line 106 def initialize( password, test_params = {} ) @password = password.to_s if test_params.is_a?(Symbol) @test_params = default_test_params(test_params) elsif test_params.empty? @test_params = default_test_params else @test_params = test_params end end |
Class Method Details
.entropy(password) ⇒ Object
Calculates the entropy of the given password (in bits).
Note: Technically, when using a deterministic (pseudo) random number generator, the entropy of ANY password will be the size of the set of integers the random function is picking from (eg. if you use rand(255) the
entropy would be 8, but if you use rand(), the entropy would be 32).
This is a simplified implementation of the formula for entropy on page 4 of RFC 4086 (www.ietf.org/rfc/rfc4086.txt).
This wikipedia has an explanation of password entropy: en.wikipedia.org/wiki/Random_password_generator
Examples:
Password.entropy('1') => 3.32192809488736
Password.entropy('123456') => 19.9315685693242
Password.entropy('foobar') => 28.2026383088466
Password.entropy('1fo0^*bar9') => 61.0852445677817
375 376 377 378 379 380 381 382 383 |
# File 'lib/password_tests.rb', line 375 def self.entropy( password ) n = 0 n += 26 if password.scan( /[a-z]/ ).size > 0 n += 26 if password.scan( /[A-Z]/ ).size > 0 n += 10 if password.scan( /[0-9]/ ).size > 0 n += 33 if password.scan( /[^A-Za-z0-9]/ ).size > 0 return password.length * (Math.log10(n) / Math.log10(2)) end |
.qualitative_strength(password) ⇒ Object
Tests the strength of a given password qualitativly and returns a numerical strength.
The algorithm is modified from phiras.wordpress.com/2007/04/08/password-strength-meter-a-jquery-plugin/
Examples:
PasswordTests.qualitative_strength( '123456') => 0
PasswordTests.qualitative_strength( 'foo' ) => 0
PasswordTests.qualitative_strength( 'fooo' ) => 2
PasswordTests.qualitative_strength( 'foobar' ) => 12
PasswordTests.qualitative_strength( '1fo0^*bar9' ) => 95
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/password_tests.rb', line 296 def self.qualitative_strength( password ) return 0 if password.length < 4 score = password.length * 4 # repetitions reps = PasswordTests.repetitions( password ) reps.each { |rep,times| score -= rep.size * 2 * times } # counts nums = password.scan( /\d/ ).size symbols = password.scan( /[^A-Za-z0-9]/ ).size lower_alphas = password.scan( /[a-z]/ ).size upper_alphas = password.scan( /[A-Z]/ ).size alphas = lower_alphas + upper_alphas score += 5 if nums >= 3 score += 5 if symbols >= 2 score += 15 if nums > 0 && symbols > 0 score -= 10 if nums == 0 && symbols == 0 score += 10 if lower_alphas > 0 && upper_alphas > 0 score += 15 if nums > 0 && alphas > 0 score += 15 if symbols > 0 && alphas > 0 score -= 30 if symbols == 0 && alphas == 0 return score if score > 0 return 0 end |
.repetitions(password, splits = [], reps = {}) ⇒ Object
Finds all character sequences that are repeated in the password
Example:
PasswordTests.repetitions( 'foofoo') => {"foo" => 1, "f" => 1, "oo" => 1, "o" => 3}
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/password_tests.rb', line 333 def self.repetitions( password, splits = [], reps = {} ) splits = [password[0...(password.length/2)], password[(password.length/2)...password.length]] if splits.empty? next_splits = [] splits.each do |partial| r = password.scan(partial) reps[r[0]] = r.size-1 if r.size > 1 && (not reps.include?(r[0])) if partial.length > 1 next_splits << partial[0...(partial.length/2)] next_splits << partial[(partial.length/2)...partial.length] end end repetitions( password, next_splits, reps ) unless next_splits.empty? return reps end |
Instance Method Details
#default_test_params(level = :medium_low) ⇒ Object
Get a default test parameters hash of the given level of security. The default level is :medium_low
.
Levels:
:low
: minimum 6 characters, at least 1 letter :medium_low
: minimum 8 characters, at least 1 letter and 1
non-alpha character (ie. number or symbol) <b>default</b>
:medium_high
: minimum 8 characters, at least 1 upper-case letter,
1 lower-case letter, and 1 non-alpha character (ie.
number or symbol)
:high
: minimum 8 characters, at least 1 upper-case letter,
1 lower-case letter, 1 numeral character, and 1
symbol
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/password_tests.rb', line 256 def default_test_params( level = :medium_low ) case level when :low { :test_length => 6, :test_minimum_alphas => 1 } when :medium_low { :test_length => 8, :test_minimum_alphas => 1, :test_minimum_non_alpha => 1} when :medium_high { :test_length => 8, :test_minimum_upper_alphas => 1, :test_minimum_lower_alphas => 1, :test_minimum_non_alpha => 1 } when :high { :test_length => 8, :test_minimum_upper_alphas => 1, :test_minimum_lower_alphas => 1, :test_minimum_numerals => 1, :test_minimum_non_alphanumeric => 1} else raise "#{level.to_s} is not a valid level" end end |
#run ⇒ Object
Run the tests.
The results are returned in a Hash with the key as the test, and the value as whether or not the test passed
Example:
tests = PasswordTests.new( 'FooBar', :test_length => 8,
:test_minimum_alphas => 2 )
results = tests.run => {:test_length => false,
:test_minimum_alphas => false}
130 131 132 133 134 |
# File 'lib/password_tests.rb', line 130 def run results = Hash.new { |h,k| h[k] = false } @test_params.each { |test,param| results[test] = send(test, param) } return results end |
#test_length(*params) ⇒ Object
Tests the length of the password
139 140 141 142 |
# File 'lib/password_tests.rb', line 139 def test_length( *params ) params[0] ||= @test_params[current_method_name] @password.length >= params[0] end |
#test_maximum_alphas(*params) ⇒ Object
Tests for a maximum number of alpha characters
155 156 157 158 |
# File 'lib/password_tests.rb', line 155 def test_maximum_alphas( *params ) params[0] ||= @test_params[current_method_name] maximum_test( /[A-Za-z]/, params[0] ) end |
#test_maximum_lower_alphas(*params) ⇒ Object
Tests for a maximum number of lower case alpha characters
187 188 189 190 |
# File 'lib/password_tests.rb', line 187 def test_maximum_lower_alphas( *params ) params[0] ||= @test_params[current_method_name] maximum_test( /[a-z]/, params[0] ) end |
#test_maximum_non_alpha(*params) ⇒ Object
Tests for a maximum number of non-alpha (i.e. not letters) characters
219 220 221 222 |
# File 'lib/password_tests.rb', line 219 def test_maximum_non_alpha( *params ) params[0] ||= @test_params[current_method_name] maximum_test( /[^A-Za-z]/, params[0] ) end |
#test_maximum_non_alphanumeric(*params) ⇒ Object
Tests for a maximum number of non-alphanumeric characters
235 236 237 238 |
# File 'lib/password_tests.rb', line 235 def test_maximum_non_alphanumeric( *params ) params[0] ||= @test_params[current_method_name] maximum_test( /[^A-Za-z0-9]/, params[0] ) end |
#test_maximum_numerals(*params) ⇒ Object
Tests for a maximum number of numeric characters
203 204 205 206 |
# File 'lib/password_tests.rb', line 203 def test_maximum_numerals( *params ) params[0] ||= @test_params[current_method_name] maximum_test( /[0-9]/, params[0] ) end |
#test_maximum_upper_alphas(*params) ⇒ Object
Tests for a maximum number of upper case alpha characters
171 172 173 174 |
# File 'lib/password_tests.rb', line 171 def test_maximum_upper_alphas( *params ) params[0] ||= @test_params[current_method_name] maximum_test( /[A-Z]/, params[0] ) end |
#test_minimum_alphas(*params) ⇒ Object
Tests for a minimum number of alpha characters
147 148 149 150 |
# File 'lib/password_tests.rb', line 147 def test_minimum_alphas( *params ) params[0] ||= @test_params[current_method_name] minimum_test( /[A-Za-z]/, params[0] ) end |
#test_minimum_lower_alphas(*params) ⇒ Object
Tests for a minimum number of lower case alpha characters
179 180 181 182 |
# File 'lib/password_tests.rb', line 179 def test_minimum_lower_alphas( *params ) params[0] ||= @test_params[current_method_name] minimum_test( /[a-z]/, params[0] ) end |
#test_minimum_non_alpha(*params) ⇒ Object
Tests for a minimum number of non-alpha (i.e. not letters) characters
211 212 213 214 |
# File 'lib/password_tests.rb', line 211 def test_minimum_non_alpha( *params ) params[0] ||= @test_params[current_method_name] minimum_test( /[^A-Za-z]/, params[0] ) end |
#test_minimum_non_alphanumeric(*params) ⇒ Object
Tests for a minimum number of non-alphanumeric characters
227 228 229 230 |
# File 'lib/password_tests.rb', line 227 def test_minimum_non_alphanumeric( *params ) params[0] ||= @test_params[current_method_name] minimum_test( /[^A-Za-z0-9]/, params[0] ) end |
#test_minimum_numerals(*params) ⇒ Object
Tests for a minimum number of numeric characters
195 196 197 198 |
# File 'lib/password_tests.rb', line 195 def test_minimum_numerals( *params ) params[0] ||= @test_params[current_method_name] minimum_test( /[0-9]/, params[0] ) end |
#test_minimum_upper_alphas(*params) ⇒ Object
Tests for a minimum number of upper case alpha characters
163 164 165 166 |
# File 'lib/password_tests.rb', line 163 def test_minimum_upper_alphas( *params ) params[0] ||= @test_params[current_method_name] minimum_test( /[A-Z]/, params[0] ) end |