ruby-password 0.15.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/CHANGES +148 -0
- data/COPYING +340 -0
- data/Changelog +840 -0
- data/INSTALL +34 -0
- data/README +60 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/example/example.rb +42 -0
- data/example/pwgen +118 -0
- data/extconf.rb +62 -0
- data/lib/password.rb +432 -0
- data/pwgen.1 +83 -0
- data/rbcrack.c +113 -0
- data/ruby-password.spec +153 -0
- data/test/tc_password.rb +89 -0
- metadata +115 -0
data/INSTALL
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$Id: INSTALL,v 1.3 2004/04/07 09:29:03 ianmacd Exp $
|
2
|
+
|
3
|
+
|
4
|
+
At the prompt, type:
|
5
|
+
|
6
|
+
$ ruby extconf.rb
|
7
|
+
|
8
|
+
This will create a Makefile for your system.
|
9
|
+
|
10
|
+
Next, type:
|
11
|
+
|
12
|
+
$ make
|
13
|
+
|
14
|
+
This will build the software for your system.
|
15
|
+
|
16
|
+
Finally, type:
|
17
|
+
|
18
|
+
$ make install
|
19
|
+
|
20
|
+
This will install the software on your system. The software consists of
|
21
|
+
crack.so, a low level shared object interface to LibCrack for checking
|
22
|
+
password security stength, and password.rb, a higher level API for dealing
|
23
|
+
with passwords.
|
24
|
+
|
25
|
+
If, for any reason, you wish to install the software elsewhere, you can
|
26
|
+
pass make(1) relevant arguments via environment variables:
|
27
|
+
|
28
|
+
$ make DESTDIR=/tmp install
|
29
|
+
|
30
|
+
You can generate RDoc documentation as follows:
|
31
|
+
|
32
|
+
$ rdoc -x CVS rbcrack.c lib
|
33
|
+
|
34
|
+
This will generate HTML and documentation in the doc/ subdirectory.
|
data/README
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
WHAT IS IT?
|
2
|
+
-----------
|
3
|
+
|
4
|
+
Ruby/Password is a suite of password handling methods for Ruby. It supports
|
5
|
+
the manual entry of passwords from the keyboard in both buffered and
|
6
|
+
unbuffered modes, password strength checking, random password generation,
|
7
|
+
phonemic password generation (for easy memorisation by human-beings) and the
|
8
|
+
encryption of passwords.
|
9
|
+
|
10
|
+
The common CrackLib library is used to perform password strength checking.
|
11
|
+
|
12
|
+
From the CrackLib README:
|
13
|
+
|
14
|
+
CrackLib makes literally hundreds of tests to determine whether you've
|
15
|
+
chosen a bad password.
|
16
|
+
|
17
|
+
* It tries to generate words from your username and GECOS entry and tries
|
18
|
+
to match them against the password you've chosen.
|
19
|
+
|
20
|
+
* It checks for simplistic patterns.
|
21
|
+
|
22
|
+
* It then tries to reverse-engineer your password into a dictionary
|
23
|
+
word, and searches for it in your dictionary.
|
24
|
+
|
25
|
+
- after all that, it's PROBABLY a safe(-ish) password. 8-)
|
26
|
+
|
27
|
+
|
28
|
+
The target audience for this library is system administrators who need
|
29
|
+
to write Ruby programs that prompt for, generate, verify and encrypt
|
30
|
+
passwords.
|
31
|
+
|
32
|
+
|
33
|
+
THIS FORK
|
34
|
+
---------
|
35
|
+
|
36
|
+
This is a fork from the original software written by Ian Macdonald. I've (Albert Lash of Savonix / Docunext) forked it to try and package it as a Ruby 1.9.1 gem
|
37
|
+
for my own convenience. I made the version 0.15.4 to try and differentiate from Ian's tree.
|
38
|
+
|
39
|
+
INSTALLATION
|
40
|
+
------------
|
41
|
+
|
42
|
+
Please see the INSTALL file for details of how to install Ruby/Password.
|
43
|
+
|
44
|
+
|
45
|
+
USAGE
|
46
|
+
-----
|
47
|
+
|
48
|
+
Please see the RDoc documentation for details of how to use Ruby/Password.
|
49
|
+
|
50
|
+
|
51
|
+
LICENCE
|
52
|
+
-------
|
53
|
+
|
54
|
+
This program is free software; you can redistribute it and/or modify
|
55
|
+
it under the terms of the GNU General Public License as published by
|
56
|
+
the Free Software Foundation; either version 2, or (at your option)
|
57
|
+
any later version.
|
58
|
+
|
59
|
+
Please see the file COPYING for the terms of the licence.
|
60
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = "ruby-password"
|
7
|
+
gem.summary = %Q{A password handling library for Ruby with interface to CrackLib}
|
8
|
+
gem.description = %Q{Ruby/Password is a suite of password handling methods for Ruby. It supports
|
9
|
+
the manual entry of passwords from the keyboard in both buffered and
|
10
|
+
unbuffered modes, password strength checking, random password generation,
|
11
|
+
phonemic password generation (for easy memorisation by human-beings) and the
|
12
|
+
encryption of passwords.}
|
13
|
+
gem.email = "albert.lash@docunext.com"
|
14
|
+
gem.homepage = "http://www.docunext.com/"
|
15
|
+
gem.authors = ["Albert Lash", "Ian Macdonald"]
|
16
|
+
gem.add_dependency "ruby-termios"
|
17
|
+
gem.add_development_dependency "shoulda"
|
18
|
+
gem.extensions = FileList['extconf.rb']
|
19
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
24
|
+
end
|
25
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.15.5
|
data/example/example.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
#
|
3
|
+
# $Id: example.rb,v 1.7 2004/04/07 09:49:06 ianmacd Exp $
|
4
|
+
#
|
5
|
+
# Copyright (C) 2002-2004 Ian Macdonald
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation; either version 2, or (at your option)
|
10
|
+
# any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program; if not, write to the Free Software Foundation,
|
19
|
+
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
20
|
+
|
21
|
+
require 'password'
|
22
|
+
|
23
|
+
def handle_password( pw )
|
24
|
+
pw.check
|
25
|
+
puts pw.crypt( `uname` == "Linux\n" ? Password::MD5 : Password::DES )
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
my_string = Password.get( "Password with get: " )
|
30
|
+
handle_password( my_string )
|
31
|
+
rescue Password::WeakPassword => reason
|
32
|
+
puts reason
|
33
|
+
retry
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
my_string = Password.getc( "Password with getc: ", 'X' )
|
38
|
+
handle_password( my_string )
|
39
|
+
rescue Password::WeakPassword => reason
|
40
|
+
puts reason
|
41
|
+
retry
|
42
|
+
end
|
data/example/pwgen
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
#
|
3
|
+
# $Id: pwgen,v 1.3 2004/09/04 22:20:27 ianmacd Exp $
|
4
|
+
#
|
5
|
+
# Copyright (C) 2004 Ian Macdonald
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation; either version 2, or (at your option)
|
10
|
+
# any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program; if not, write to the Free Software Foundation,
|
19
|
+
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
20
|
+
|
21
|
+
|
22
|
+
require 'optparse'
|
23
|
+
require 'ostruct'
|
24
|
+
require 'password'
|
25
|
+
|
26
|
+
|
27
|
+
class Optparse
|
28
|
+
|
29
|
+
USAGE_BANNER = "Usage: pwgen [ OPTIONS ] [ password_length ] [ number_passwords ]"
|
30
|
+
|
31
|
+
def self.parse(args)
|
32
|
+
options = OpenStruct.new
|
33
|
+
options.columns = $stdout.tty?
|
34
|
+
options.one_case = $stdout.tty? ? Password::ONE_CASE : 0
|
35
|
+
options.one_digit = $stdout.tty? ? Password::ONE_DIGIT : 0
|
36
|
+
options.secure = false
|
37
|
+
|
38
|
+
opts = OptionParser.new do |opts|
|
39
|
+
opts.banner = USAGE_BANNER
|
40
|
+
|
41
|
+
opts.on( "-C", "Print the generated passwords in columns") do
|
42
|
+
options.columns = true
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on( "-1", "Don't print the generated passwords",
|
46
|
+
" in columns") do
|
47
|
+
options.columns = false
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on( "-c", "--[no-]capitalise", "--[no-]capitalize",
|
51
|
+
"Include at least one capital letter in",
|
52
|
+
" the password" ) do |opt|
|
53
|
+
options.one_case = opt ? Password::ONE_CASE : 0
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on( "-n", "--[no-]numerals",
|
57
|
+
"Include at least one digit in the password" ) do |opt|
|
58
|
+
options.one_digit = opt ? Password::ONE_DIGIT : 0
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on( "-s", "--secure", "Generate completely random passwords" ) do
|
62
|
+
options.secure = true
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on( "-v", "--version", "Display version and exit" ) do
|
66
|
+
puts PWGEN_VERSION
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on_tail( "-h", "--help", "Display this usage message and exit" ) do
|
71
|
+
puts opts
|
72
|
+
exit
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.parse!(args)
|
78
|
+
options
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
PWGEN_VERSION = '0.5.2'
|
83
|
+
TERM_WIDTH = 80
|
84
|
+
|
85
|
+
options = Optparse.parse(ARGV)
|
86
|
+
|
87
|
+
unless [ 0, 2 ].include? ARGV.size
|
88
|
+
puts Optparse::USAGE_BANNER
|
89
|
+
exit 1
|
90
|
+
end
|
91
|
+
|
92
|
+
length, number = *ARGV.map { |arg| arg.to_i }
|
93
|
+
length ||= 8
|
94
|
+
|
95
|
+
generator = if length < 5 || options.secure
|
96
|
+
Proc.new { Password.random( length ) }
|
97
|
+
else
|
98
|
+
Proc.new { Password.phonemic( length, options.one_case |
|
99
|
+
options.one_digit ) }
|
100
|
+
end
|
101
|
+
|
102
|
+
columns = options.columns ? TERM_WIDTH / ( length + 1 ) : 1
|
103
|
+
columns = 1 if columns == 0
|
104
|
+
number ||= options.columns ? columns * 20 : 1
|
105
|
+
|
106
|
+
need_new_line = false
|
107
|
+
|
108
|
+
0.upto number - 1 do |n|
|
109
|
+
if ! options.columns || n % columns == columns - 1
|
110
|
+
puts generator.call
|
111
|
+
need_new_line = false
|
112
|
+
else
|
113
|
+
print generator.call, ' '
|
114
|
+
need_new_line = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
puts if need_new_line
|
data/extconf.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# extconf for Ruby/Password
|
2
|
+
#
|
3
|
+
# $Id: extconf.rb,v 1.13 2006/03/02 17:35:06 ianmacd Exp $
|
4
|
+
|
5
|
+
require 'mkmf'
|
6
|
+
|
7
|
+
search_dicts = %w(
|
8
|
+
/usr/local/lib/pw_dict.pwd
|
9
|
+
/usr/lib/pw_dict.pwd
|
10
|
+
/opt/lib/pw_dict.pwd
|
11
|
+
/usr/local/lib/cracklib_dict.pwd
|
12
|
+
/usr/lib/cracklib_dict.pwd
|
13
|
+
/opt/lib/cracklib_dict.pwd
|
14
|
+
/var/cache/cracklib/cracklib_dict.pwd
|
15
|
+
)
|
16
|
+
|
17
|
+
if dict = with_config('crack-dict')
|
18
|
+
search_dicts.unshift(dict)
|
19
|
+
end
|
20
|
+
|
21
|
+
crack_dict = nil
|
22
|
+
|
23
|
+
# find the crack dictionary
|
24
|
+
print "checking for cracklib dictionary... "
|
25
|
+
|
26
|
+
search_dicts.each do |dict|
|
27
|
+
# create a header file pointing to the crack dictionary
|
28
|
+
if File.exist?(dict)
|
29
|
+
puts dict
|
30
|
+
crack_dict = dict.sub(/\.pwd/, '')
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if crack_dict.nil?
|
36
|
+
puts "no\nCouldn't find a cracklib dictionary on this system"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
|
40
|
+
hfile = File.new("rbcrack.h", 'w')
|
41
|
+
hfile.printf("#define CRACK_DICT \"%s\"\n", crack_dict)
|
42
|
+
hfile.close
|
43
|
+
|
44
|
+
have_header('crack.h') && have_library('crack', 'FascistCheck') or exit 1
|
45
|
+
|
46
|
+
create_makefile('cracklib')
|
47
|
+
|
48
|
+
File.open('Makefile', 'a') do |f|
|
49
|
+
f.print <<EOF
|
50
|
+
|
51
|
+
extra-clean: distclean
|
52
|
+
-rm -rf rbcrack.h doc/
|
53
|
+
|
54
|
+
docs:
|
55
|
+
-rdoc -x CVS rbcrack.c lib
|
56
|
+
|
57
|
+
test: cracklib.so FORCE
|
58
|
+
-cd test; ./tc_password.rb
|
59
|
+
|
60
|
+
FORCE:
|
61
|
+
EOF
|
62
|
+
end
|
data/lib/password.rb
ADDED
@@ -0,0 +1,432 @@
|
|
1
|
+
# $Id: password.rb,v 1.24 2006/03/02 19:42:33 ianmacd Exp $
|
2
|
+
#
|
3
|
+
# Version : 0.5.3
|
4
|
+
# Author : Ian Macdonald <ian@caliban.org>
|
5
|
+
#
|
6
|
+
# Copyright (C) 2002-2006 Ian Macdonald
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation; either version 2, or (at your option)
|
11
|
+
# any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software Foundation,
|
20
|
+
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
21
|
+
|
22
|
+
|
23
|
+
require 'cracklib'
|
24
|
+
require 'termios'
|
25
|
+
|
26
|
+
|
27
|
+
# Ruby/Password is a collection of password handling routines for Ruby,
|
28
|
+
# including an interface to CrackLib for the purposes of testing password
|
29
|
+
# strength.
|
30
|
+
#
|
31
|
+
# require 'password'
|
32
|
+
#
|
33
|
+
# # Define and check a password in code
|
34
|
+
# pw = Password.new( "bigblackcat" )
|
35
|
+
# pw.check
|
36
|
+
#
|
37
|
+
# # Get and check a password from the keyboard
|
38
|
+
# begin
|
39
|
+
# password = Password.get( "New password: " )
|
40
|
+
# password.check
|
41
|
+
# rescue Password::WeakPassword => reason
|
42
|
+
# puts reason
|
43
|
+
# retry
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # Automatically generate and encrypt a password
|
47
|
+
# password = Password.phonemic( 12, Password:ONE_CASE | Password::ONE_DIGIT )
|
48
|
+
# crypted = password.crypt
|
49
|
+
#
|
50
|
+
#
|
51
|
+
class Password < String
|
52
|
+
|
53
|
+
# This exception class is raised if an error occurs during password
|
54
|
+
# encryption when calling Password#crypt.
|
55
|
+
#
|
56
|
+
class CryptError < StandardError; end
|
57
|
+
|
58
|
+
# This exception class is raised if a bad dictionary path is detected by
|
59
|
+
# Password#check.
|
60
|
+
#
|
61
|
+
class DictionaryError < StandardError; end
|
62
|
+
|
63
|
+
# This exception class is raised if a weak password is detected by
|
64
|
+
# Password#check.
|
65
|
+
#
|
66
|
+
class WeakPassword < StandardError; end
|
67
|
+
|
68
|
+
VERSION = '0.5.3'
|
69
|
+
|
70
|
+
# DES algorithm
|
71
|
+
#
|
72
|
+
DES = true
|
73
|
+
|
74
|
+
# MD5 algorithm (see <em>crypt(3)</em> for more information)
|
75
|
+
#
|
76
|
+
MD5 = false
|
77
|
+
|
78
|
+
# This flag is used in conjunction with Password.phonemic and states that a
|
79
|
+
# password must include a digit.
|
80
|
+
#
|
81
|
+
ONE_DIGIT = 1
|
82
|
+
|
83
|
+
# This flag is used in conjunction with Password.phonemic and states that a
|
84
|
+
# password must include a capital letter.
|
85
|
+
#
|
86
|
+
ONE_CASE = 1 << 1
|
87
|
+
|
88
|
+
# Characters that may appear in generated passwords. Password.urandom may
|
89
|
+
# also use the characters + and /.
|
90
|
+
#
|
91
|
+
PASSWD_CHARS = '0123456789' +
|
92
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
|
93
|
+
'abcdefghijklmnopqrstuvwxyz'
|
94
|
+
|
95
|
+
# Valid salt characters for use by Password#crypt.
|
96
|
+
#
|
97
|
+
SALT_CHARS = '0123456789' +
|
98
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
|
99
|
+
'abcdefghijklmnopqrstuvwxyz' +
|
100
|
+
'./'
|
101
|
+
|
102
|
+
# :stopdoc:
|
103
|
+
|
104
|
+
# phoneme flags
|
105
|
+
#
|
106
|
+
CONSONANT = 1
|
107
|
+
VOWEL = 1 << 1
|
108
|
+
DIPHTHONG = 1 << 2
|
109
|
+
NOT_FIRST = 1 << 3 # indicates that a given phoneme may not occur first
|
110
|
+
|
111
|
+
PHONEMES = {
|
112
|
+
:a => VOWEL,
|
113
|
+
:ae => VOWEL | DIPHTHONG,
|
114
|
+
:ah => VOWEL | DIPHTHONG,
|
115
|
+
:ai => VOWEL | DIPHTHONG,
|
116
|
+
:b => CONSONANT,
|
117
|
+
:c => CONSONANT,
|
118
|
+
:ch => CONSONANT | DIPHTHONG,
|
119
|
+
:d => CONSONANT,
|
120
|
+
:e => VOWEL,
|
121
|
+
:ee => VOWEL | DIPHTHONG,
|
122
|
+
:ei => VOWEL | DIPHTHONG,
|
123
|
+
:f => CONSONANT,
|
124
|
+
:g => CONSONANT,
|
125
|
+
:gh => CONSONANT | DIPHTHONG | NOT_FIRST,
|
126
|
+
:h => CONSONANT,
|
127
|
+
:i => VOWEL,
|
128
|
+
:ie => VOWEL | DIPHTHONG,
|
129
|
+
:j => CONSONANT,
|
130
|
+
:k => CONSONANT,
|
131
|
+
:l => CONSONANT,
|
132
|
+
:m => CONSONANT,
|
133
|
+
:n => CONSONANT,
|
134
|
+
:ng => CONSONANT | DIPHTHONG | NOT_FIRST,
|
135
|
+
:o => VOWEL,
|
136
|
+
:oh => VOWEL | DIPHTHONG,
|
137
|
+
:oo => VOWEL | DIPHTHONG,
|
138
|
+
:p => CONSONANT,
|
139
|
+
:ph => CONSONANT | DIPHTHONG,
|
140
|
+
:qu => CONSONANT | DIPHTHONG,
|
141
|
+
:r => CONSONANT,
|
142
|
+
:s => CONSONANT,
|
143
|
+
:sh => CONSONANT | DIPHTHONG,
|
144
|
+
:t => CONSONANT,
|
145
|
+
:th => CONSONANT | DIPHTHONG,
|
146
|
+
:u => VOWEL,
|
147
|
+
:v => CONSONANT,
|
148
|
+
:w => CONSONANT,
|
149
|
+
:x => CONSONANT,
|
150
|
+
:y => CONSONANT,
|
151
|
+
:z => CONSONANT
|
152
|
+
}
|
153
|
+
|
154
|
+
# :startdoc:
|
155
|
+
|
156
|
+
|
157
|
+
# Turn local terminal echo on or off. This method is used for securing the
|
158
|
+
# display, so that a soon to be entered password will not be echoed to the
|
159
|
+
# screen. It is also used for restoring the display afterwards.
|
160
|
+
#
|
161
|
+
# If _masked_ is +true+, the keyboard is put into unbuffered mode, allowing
|
162
|
+
# the retrieval of characters one at a time. _masked_ has no effect when
|
163
|
+
# _on_ is +false+. You are unlikely to need this method in the course of
|
164
|
+
# normal operations.
|
165
|
+
#
|
166
|
+
def Password.echo(on=true, masked=false)
|
167
|
+
term = Termios::getattr( $stdin )
|
168
|
+
|
169
|
+
if on
|
170
|
+
term.c_lflag |= ( Termios::ECHO | Termios::ICANON )
|
171
|
+
else # off
|
172
|
+
term.c_lflag &= ~Termios::ECHO
|
173
|
+
term.c_lflag &= ~Termios::ICANON if masked
|
174
|
+
end
|
175
|
+
|
176
|
+
Termios::setattr( $stdin, Termios::TCSANOW, term )
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# Get a password from _STDIN_, using buffered line input and displaying
|
181
|
+
# _message_ as the prompt. No output will appear while the password is being
|
182
|
+
# typed. Hitting <b>[Enter]</b> completes password entry. If _STDIN_ is not
|
183
|
+
# connected to a tty, no prompt will be displayed.
|
184
|
+
#
|
185
|
+
def Password.get(message="Password: ")
|
186
|
+
begin
|
187
|
+
if $stdin.tty?
|
188
|
+
Password.echo false
|
189
|
+
print message if message
|
190
|
+
end
|
191
|
+
|
192
|
+
pw = Password.new( $stdin.gets || "" )
|
193
|
+
pw.chomp!
|
194
|
+
|
195
|
+
ensure
|
196
|
+
if $stdin.tty?
|
197
|
+
Password.echo true
|
198
|
+
print "\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
# Get a password from _STDIN_ in unbuffered mode, i.e. one key at a time.
|
205
|
+
# _message_ will be displayed as the prompt and each key press with echo
|
206
|
+
# _mask_ to the terminal. There is no need to hit <b>[Enter]</b> at the end.
|
207
|
+
#
|
208
|
+
def Password.getc(message="Password: ", mask='*')
|
209
|
+
# Save current buffering mode
|
210
|
+
buffering = $stdout.sync
|
211
|
+
|
212
|
+
# Turn off buffering
|
213
|
+
$stdout.sync = true
|
214
|
+
|
215
|
+
begin
|
216
|
+
Password.echo(false, true)
|
217
|
+
print message if message
|
218
|
+
pw = ""
|
219
|
+
|
220
|
+
while ( char = $stdin.getc ) != 10 # break after [Enter]
|
221
|
+
putc mask
|
222
|
+
pw << char
|
223
|
+
end
|
224
|
+
|
225
|
+
ensure
|
226
|
+
Password.echo true
|
227
|
+
print "\n"
|
228
|
+
end
|
229
|
+
|
230
|
+
# Restore original buffering mode
|
231
|
+
$stdout.sync = buffering
|
232
|
+
|
233
|
+
Password.new( pw )
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
# :stopdoc:
|
238
|
+
|
239
|
+
# Determine whether next character should be a vowel or consonant.
|
240
|
+
#
|
241
|
+
def Password.get_vowel_or_consonant
|
242
|
+
rand( 2 ) == 1 ? VOWEL : CONSONANT
|
243
|
+
end
|
244
|
+
|
245
|
+
# :startdoc:
|
246
|
+
|
247
|
+
|
248
|
+
# Generate a memorable password of _length_ characters, using phonemes that
|
249
|
+
# a human-being can easily remember. _flags_ is one or more of
|
250
|
+
# <em>Password::ONE_DIGIT</em> and <em>Password::ONE_CASE</em>, logically
|
251
|
+
# OR'ed together. For example:
|
252
|
+
#
|
253
|
+
# pw = Password.phonemic( 8, Password::ONE_DIGIT | Password::ONE_CASE )
|
254
|
+
#
|
255
|
+
# This would generate an eight character password, containing a digit and an
|
256
|
+
# upper-case letter, such as <b>Ug2shoth</b>.
|
257
|
+
#
|
258
|
+
# This method was inspired by the
|
259
|
+
# pwgen[http://sourceforge.net/projects/pwgen/] tool, written by Theodore
|
260
|
+
# Ts'o.
|
261
|
+
#
|
262
|
+
# Generated passwords may contain any of the characters in
|
263
|
+
# <em>Password::PASSWD_CHARS</em>.
|
264
|
+
#
|
265
|
+
def Password.phonemic(length=8, flags=nil)
|
266
|
+
|
267
|
+
pw = nil
|
268
|
+
ph_flags = flags
|
269
|
+
|
270
|
+
loop do
|
271
|
+
|
272
|
+
pw = ""
|
273
|
+
|
274
|
+
# Separate the flags integer into an array of individual flags
|
275
|
+
feature_flags = [ flags & ONE_DIGIT, flags & ONE_CASE ]
|
276
|
+
|
277
|
+
prev = []
|
278
|
+
first = true
|
279
|
+
desired = Password.get_vowel_or_consonant
|
280
|
+
|
281
|
+
# Get an Array of all of the phonemes
|
282
|
+
phonemes = PHONEMES.keys.map { |ph| ph.to_s }
|
283
|
+
nr_phonemes = phonemes.size
|
284
|
+
|
285
|
+
while pw.length < length do
|
286
|
+
|
287
|
+
# Get a random phoneme and its length
|
288
|
+
phoneme = phonemes[ rand( nr_phonemes ) ]
|
289
|
+
ph_len = phoneme.length
|
290
|
+
|
291
|
+
# Get its flags as an Array
|
292
|
+
ph_flags = PHONEMES[ phoneme.to_sym ]
|
293
|
+
ph_flags = [ ph_flags & CONSONANT, ph_flags & VOWEL,
|
294
|
+
ph_flags & DIPHTHONG, ph_flags & NOT_FIRST ]
|
295
|
+
|
296
|
+
# Filter on the basic type of the next phoneme
|
297
|
+
next if ph_flags.include? desired
|
298
|
+
|
299
|
+
# Handle the NOT_FIRST flag
|
300
|
+
next if first and ph_flags.include? NOT_FIRST
|
301
|
+
|
302
|
+
# Don't allow a VOWEL followed a vowel/diphthong pair
|
303
|
+
next if prev.include? VOWEL and ph_flags.include? VOWEL and
|
304
|
+
ph_flags.include? DIPHTHONG
|
305
|
+
|
306
|
+
# Don't allow us to go longer than the desired length
|
307
|
+
next if ph_len > length - pw.length
|
308
|
+
|
309
|
+
# We've found a phoneme that meets our criteria
|
310
|
+
pw << phoneme
|
311
|
+
|
312
|
+
# Handle ONE_CASE
|
313
|
+
if feature_flags.include? ONE_CASE
|
314
|
+
|
315
|
+
if (first or ph_flags.include? CONSONANT) and rand( 10 ) < 3
|
316
|
+
pw[-ph_len, 1] = pw[-ph_len, 1].upcase
|
317
|
+
feature_flags.delete ONE_CASE
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
# Is password already long enough?
|
323
|
+
break if pw.length >= length
|
324
|
+
|
325
|
+
# Handle ONE_DIGIT
|
326
|
+
if feature_flags.include? ONE_DIGIT
|
327
|
+
|
328
|
+
if ! first and rand( 10 ) < 3
|
329
|
+
pw << ( rand( 10 ) + ?0 ).chr
|
330
|
+
feature_flags.delete ONE_DIGIT
|
331
|
+
|
332
|
+
first = true
|
333
|
+
prev = []
|
334
|
+
desired = Password.get_vowel_or_consonant
|
335
|
+
next
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
if desired == CONSONANT
|
341
|
+
desired = VOWEL
|
342
|
+
elsif prev.include? VOWEL or ph_flags.include? DIPHTHONG or
|
343
|
+
rand(10) > 3
|
344
|
+
desired = CONSONANT
|
345
|
+
else
|
346
|
+
desired = VOWEL
|
347
|
+
end
|
348
|
+
|
349
|
+
prev = ph_flags
|
350
|
+
first = false
|
351
|
+
end
|
352
|
+
|
353
|
+
# Try again
|
354
|
+
break unless feature_flags.include? ONE_CASE or
|
355
|
+
feature_flags.include? ONE_DIGIT
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
Password.new( pw )
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
# Generate a random password of _length_ characters. Unlike the
|
365
|
+
# Password.phonemic method, no attempt will be made to generate a memorable
|
366
|
+
# password. Generated passwords may contain any of the characters in
|
367
|
+
# <em>Password::PASSWD_CHARS</em>.
|
368
|
+
#
|
369
|
+
#
|
370
|
+
def Password.random(length=8)
|
371
|
+
pw = ""
|
372
|
+
nr_chars = PASSWD_CHARS.size
|
373
|
+
|
374
|
+
length.times { pw << PASSWD_CHARS[ rand( nr_chars ) ] }
|
375
|
+
|
376
|
+
Password.new( pw )
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
# An alternative to Password.random. It uses the <tt>/dev/urandom</tt>
|
381
|
+
# device to generate passwords, returning +nil+ on systems that do not
|
382
|
+
# implement the device. The passwords it generates may contain any of the
|
383
|
+
# characters in <em>Password::PASSWD_CHARS</em>, plus the additional
|
384
|
+
# characters + and /.
|
385
|
+
#
|
386
|
+
def Password.urandom(length=8)
|
387
|
+
return nil unless File.chardev? '/dev/urandom'
|
388
|
+
|
389
|
+
rand_data = nil
|
390
|
+
File.open( "/dev/urandom" ) { |f| rand_data = f.read( length ) }
|
391
|
+
|
392
|
+
# Base64 encode it
|
393
|
+
Password.new( [ rand_data ].pack( 'm' )[ 0 .. length - 1 ] )
|
394
|
+
end
|
395
|
+
|
396
|
+
|
397
|
+
# Encrypt a password using _type_ encryption. _salt_, if supplied, will be
|
398
|
+
# used to perturb the encryption algorithm and should be chosen from the
|
399
|
+
# <em>Password::SALT_CHARS</em>. If no salt is given, a randomly generated
|
400
|
+
# salt will be used.
|
401
|
+
#
|
402
|
+
def crypt(type=DES, salt='')
|
403
|
+
|
404
|
+
unless ( salt.split( // ) - SALT_CHARS.split( // ) ).empty?
|
405
|
+
raise CryptError, 'bad salt'
|
406
|
+
end
|
407
|
+
|
408
|
+
salt = Password.random( type ? 2 : 8 ) if salt.empty?
|
409
|
+
|
410
|
+
# (Linux glibc2 interprets a salt prefix of '$1$' as a call to use MD5
|
411
|
+
# instead of DES when calling crypt(3))
|
412
|
+
salt = '$1$' + salt if type == MD5
|
413
|
+
|
414
|
+
# Pass to crypt in class String (our parent class)
|
415
|
+
crypt = super( salt )
|
416
|
+
|
417
|
+
# Raise an exception if MD5 was wanted, but result is not recognisable
|
418
|
+
if type == MD5 && crypt !~ /^\$1\$/
|
419
|
+
raise CryptError, 'MD5 not implemented'
|
420
|
+
end
|
421
|
+
|
422
|
+
crypt
|
423
|
+
end
|
424
|
+
|
425
|
+
end
|
426
|
+
|
427
|
+
|
428
|
+
# Display a phonemic password, if run directly.
|
429
|
+
#
|
430
|
+
if $0 == __FILE__
|
431
|
+
puts Password.phonemic
|
432
|
+
end
|