pwdhash 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.markdown +27 -3
- data/bin/pwdhash2 +36 -0
- data/lib/pwdhash/pwdhash.rb +24 -3
- data/lib/pwdhash/version.rb +1 -1
- data/pwdhash.gemspec +1 -3
- data/pwdhash_test.rb +56 -2
- metadata +10 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c4724abfd27a606ddc0e0136b7b442098f24f67c0755b80ee5622abf580424dd
|
4
|
+
data.tar.gz: 8ccc81e9a1c7be717f30cab699625145b7f08d2fb88ddafe32a071972c8c3c51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41f39488b7b5d0638b6c84aae4c7805eec2d1871f496b049e855ba6bc3d483290df51e2576bdf25822142e34be7251e295047a52c4feaa38b4a2c2910f7ef0f9
|
7
|
+
data.tar.gz: ae7fc1d0ee62e3a1144b5ae02ddfe45e9f9639a731e0377d2a0643a2a62ec9cd7a4bbf9a37a086b71065b0adc7b5446aaced38ad15cfd8526adb68959750dbca
|
data/README.markdown
CHANGED
@@ -6,7 +6,7 @@ Generates PwdHash passwords from the command line.
|
|
6
6
|
|
7
7
|
This is a simple Ruby command line tool to generate hashed passwords, using PwdHash's algorithm.
|
8
8
|
|
9
|
-
PwdHash was written as a browser
|
9
|
+
PwdHash was written as a browser plug-in, but sometimes we'd like to extend its use to desktop applications too. e.g. IM clients, Dropbox, etc. just to name a few.
|
10
10
|
|
11
11
|
This tool is essentially a *straight* copy of [Chris Roos' implementation][chris-roos-impl], with a couple of command line interface improvements:
|
12
12
|
|
@@ -28,17 +28,41 @@ This tool is essentially a *straight* copy of [Chris Roos' implementation][chris
|
|
28
28
|
Password for example.com:
|
29
29
|
5NBoCKraALBs
|
30
30
|
|
31
|
-
$ pwdhash example.com |
|
31
|
+
$ pwdhash example.com | xclip # Put generated password to X clipboard (paste with middle-click)
|
32
|
+
$ pwdhash example.com | xclip -selection c # Put generated password to X clipboard (paste with CTRL+V)
|
32
33
|
$ pwdhash example.com | pbcopy # Or in OS X
|
33
34
|
> pwdhash example.com | clip # Or in Windows
|
34
35
|
$ pwdhash example.com | putclip # Or in Cygwin
|
35
36
|
|
37
|
+
## PwdHash2
|
38
|
+
|
39
|
+
Some vulnerabilities of PwdHash have been exposed in [Cracking PwdHash: A Brute-force Attack on Client-side Password Hashing][cracking-pwdhash], along with a [proof-of-concept][pwdhash-poc] update to PwdHash. The updated algorithm (with PBKDF2-SHA256 + salt + multiple iterations) has been implemented as a Firefox Add-On at [GWuk/PwdHash2][gwuk-pwdhash2].
|
40
|
+
|
41
|
+
The corresponding algorithm can be used with:
|
42
|
+
|
43
|
+
$ pwdhash2 URI [SALT] [ITERATIONS]
|
44
|
+
|
45
|
+
$ pwdhash2 example.com ReplaceThisSalt 50_000
|
46
|
+
Password for example.com (salt = 'ReplaceThisSalt', iterations = 50000):
|
47
|
+
IHC8WhmO40v
|
48
|
+
|
49
|
+
*SALT* can be defined in `PWDHASH2_SALT` environment variable.
|
50
|
+
|
51
|
+
*ITERATIONS* can be defined in `PWDHASH2_ITERATIONS` environment variable.
|
52
|
+
|
53
|
+
$ pwdhash2 example.com | xclip -selection c # Put generated password to X clipboard (paste with CTRL+V)
|
54
|
+
|
36
55
|
## Attribution
|
37
56
|
|
38
57
|
The library part of this tool is a *straight* copy of [Chris Roos' implementation][chris-roos-impl]. The reason this git repository is set up is because: a) Chris Roos decided to put the code in an obscure corner in a pile of code which makes it hard for people to find and b) GitHub uses git :p
|
39
58
|
|
40
|
-
The PwdHash
|
59
|
+
The PwdHash plug-in and the PwdHash algorithm is contributed by [Stanford PwdHash][stanford-pwdhash].
|
60
|
+
|
61
|
+
The PwdHash2 algorithm is contributed by [David Llewellyn-Jones and Graham Rymer][pwdhash-poc].
|
41
62
|
|
42
63
|
[chris-roos-impl]: http://chrisroos.co.uk/blog/2007-04-11-getting-to-grips-with-pwdhash
|
43
64
|
[stanford-pwdhash]: http://pwdhash.com
|
44
65
|
[usenix-paper]: http://crypto.stanford.edu/PwdHash/pwdhash.pdf
|
66
|
+
[cracking-pwdhash]: https://www.researchgate.net/publication/316267246_Cracking_PwdHash_A_Bruteforce_Attack_on_Client-side_Password_Hashing
|
67
|
+
[pwdhash-poc]: https://github.com/llewelld/pwdhash-poc
|
68
|
+
[gwuk-pwdhash2]: https://github.com/GWuk/PwdHash2/
|
data/bin/pwdhash2
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'highline/import'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
require 'pwdhash/pwdhash'
|
8
|
+
require 'pwdhash/extract_domain'
|
9
|
+
require 'pwdhash/version'
|
10
|
+
|
11
|
+
require 'backports'
|
12
|
+
|
13
|
+
def usage
|
14
|
+
puts "pwdhash.rb #{PwdHash::VERSION} - Chris Yuen, Chris Roos (2012) Eric Duminil (2019)"
|
15
|
+
puts "Usage: pwdhash2 URI [SALT] [ITERATIONS]"
|
16
|
+
puts "Example: pwdhash2 https://mail.google.com"
|
17
|
+
puts "Example: pwdhash2 https://mail.google.com SomeSalt"
|
18
|
+
puts "Example: pwdhash2 https://mail.google.com SomeSalt 10_000"
|
19
|
+
puts
|
20
|
+
puts "SALT can also be defined in PWDHASH2_SALT environment variable."
|
21
|
+
puts "ITERATIONS can also be defined in PWDHASH2_ITERATIONS environment variable."
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
usage if ARGV.length == 0
|
26
|
+
|
27
|
+
realm = extract_domain(ARGV[0])
|
28
|
+
|
29
|
+
salt = ARGV[1] || ENV['PWDHASH2_SALT']
|
30
|
+
iterations = (ARGV[2] || ENV['PWDHASH2_ITERATIONS'] || 50_000).to_i
|
31
|
+
|
32
|
+
hl = HighLine.new($stdin, $stderr)
|
33
|
+
hl.say("WARNING: Empty salt!") if salt.empty?
|
34
|
+
password = hl.ask("Password for #{realm} (salt = '#{salt}', iterations = #{iterations}): ") {|q| q.echo = false}
|
35
|
+
|
36
|
+
print get_hashed_password2(password, realm, salt, iterations)
|
data/lib/pwdhash/pwdhash.rb
CHANGED
@@ -26,7 +26,7 @@ def contains(result, regex)
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def apply_constraints(hash, size, nonalphanumeric)
|
29
|
-
startingSize = size - 4 # Leave room for some extra characters
|
29
|
+
startingSize = [size - 4, 0].max # Leave room for some extra characters
|
30
30
|
result = hash[0, startingSize]
|
31
31
|
extras = (hash[startingSize, hash.length] || "").split('')
|
32
32
|
|
@@ -46,8 +46,8 @@ def apply_constraints(hash, size, nonalphanumeric)
|
|
46
46
|
return result.join('')
|
47
47
|
end
|
48
48
|
|
49
|
-
require 'hmac-md5'
|
50
49
|
require 'base64'
|
50
|
+
require 'openssl'
|
51
51
|
|
52
52
|
module PwdHash
|
53
53
|
class Hash
|
@@ -65,12 +65,28 @@ module PwdHash
|
|
65
65
|
end
|
66
66
|
private
|
67
67
|
def hash!
|
68
|
-
@hash = Base64.encode64(HMAC
|
68
|
+
@hash = Base64.encode64(OpenSSL::HMAC.digest("MD5", @password, @realm)).strip
|
69
69
|
end
|
70
70
|
def remove_base64_pad_character
|
71
71
|
@hash.sub!(/=+$/, '')
|
72
72
|
end
|
73
73
|
end
|
74
|
+
|
75
|
+
class Hash2 < Hash
|
76
|
+
DIGEST = OpenSSL::Digest::SHA256.new
|
77
|
+
|
78
|
+
def initialize(realm, password, salt, iterations)
|
79
|
+
@salt, @iterations = salt, iterations
|
80
|
+
super(realm, password)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def hash!
|
86
|
+
# Based on https://github.com/GWuk/PwdHash2/blob/gh-pages/pwdhash2/hashed-password.js#L44
|
87
|
+
@hash = Base64.encode64(OpenSSL::PKCS5.pbkdf2_hmac([@password , @salt].join, @realm, @iterations, (2 * size / 3) + 16, DIGEST))
|
88
|
+
end
|
89
|
+
end
|
74
90
|
end
|
75
91
|
|
76
92
|
def get_hashed_password(password, realm)
|
@@ -78,3 +94,8 @@ def get_hashed_password(password, realm)
|
|
78
94
|
apply_constraints(hash.hash, hash.size, hash.contains_non_alphanumeric?)
|
79
95
|
end
|
80
96
|
|
97
|
+
def get_hashed_password2(password, realm, salt, iterations)
|
98
|
+
hash = PwdHash::Hash2.new(realm, password, salt, iterations)
|
99
|
+
apply_constraints(hash.hash, hash.size, hash.contains_non_alphanumeric?)
|
100
|
+
end
|
101
|
+
|
data/lib/pwdhash/version.rb
CHANGED
data/pwdhash.gemspec
CHANGED
@@ -19,7 +19,5 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
gem.version = PwdHash::VERSION
|
21
21
|
|
22
|
-
gem.add_dependency('highline', '~>
|
23
|
-
gem.add_dependency('ruby-hmac', '~> 0.4', '>= 0.4.0')
|
24
|
-
gem.add_dependency('backports', '~> 3.6', '>= 3.6.4')
|
22
|
+
gem.add_dependency('highline', '~> 3.1.2')
|
25
23
|
end
|
data/pwdhash_test.rb
CHANGED
@@ -1,18 +1,72 @@
|
|
1
|
+
require 'minitest'
|
1
2
|
require 'minitest/unit'
|
2
3
|
require 'minitest/pride'
|
3
4
|
require 'minitest/autorun'
|
4
5
|
|
5
6
|
require './lib/pwdhash/pwdhash'
|
7
|
+
require './lib/pwdhash/extract_domain'
|
6
8
|
|
7
|
-
class PwdHashTest <
|
8
|
-
def
|
9
|
+
class PwdHashTest < Minitest::Test
|
10
|
+
def test_passwords_with_domains
|
9
11
|
[
|
10
12
|
['v0F0B', 'foo', 'skype.com'],
|
11
13
|
['paZTVGZwtewiq1+uCk', 'a l0ng p4assw0rd', 'google.com'],
|
12
14
|
['nRDL7WNyFODhF29gAkNmpA', 'qwertyuiop0987654321', 'google.com'],
|
13
15
|
['edi6wHWRQVA1rK8o9zaluwAAAA', 'qwertyuiop0987654321bingo', 'google.com'],
|
16
|
+
['8JyURsRs', 'foobar', 'thetimes.co.uk'],
|
14
17
|
].each do |expected, password, realm|
|
15
18
|
assert_equal expected, get_hashed_password(password, realm)
|
16
19
|
end
|
17
20
|
end
|
21
|
+
|
22
|
+
def test_passwords_with_complete_urls
|
23
|
+
[
|
24
|
+
['v0F0B', 'foo', 'https://www.whatever.skype.com'],
|
25
|
+
['paZTVGZwtewiq1+uCk', 'a l0ng p4assw0rd', 'https://images.google.com'],
|
26
|
+
['nRDL7WNyFODhF29gAkNmpA', 'qwertyuiop0987654321', 'http://google.com'],
|
27
|
+
['edi6wHWRQVA1rK8o9zaluwAAAA', 'qwertyuiop0987654321bingo', 'https://news.google.com'],
|
28
|
+
['8JyURsRs', 'foobar', 'https://www.thetimes.co.uk/'],
|
29
|
+
].each do |expected, password, url|
|
30
|
+
assert_equal expected, get_hashed_password(password, extract_domain(url))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class PwdHash2Test < Minitest::Test
|
36
|
+
def test_passwords_with_complete_urls
|
37
|
+
# expected results calculated with https://gwuk.github.io/PwdHash2/pwdhash2/
|
38
|
+
[
|
39
|
+
['5YrAI', 'foo', 10000, 'SomeSalt', 'https://www.skype.com/en/'],
|
40
|
+
['r8oIM4CR', 'foobar', 1, 'ChangeMe', 'https://google.com'],
|
41
|
+
['3PvCifzNoYaTNBMcJzVvDfDBeVK', 'correcthorsebatterystaple', 50000, 'COVwVNWVhwCsd7vlQ2T5BuIJBccYCu1RzR8rQFVHYVkGVQkZXHLkglnttWFQJYIN', 'https://about.google/intl/en/?fg=1&utm_source=google-EN&utm_medium=referral&utm_campaign=hp-header'],
|
42
|
+
['APC8mNJI', 'foobar', 1000, 'ChangeMe', 'https://google.com'],
|
43
|
+
].each do |expected, password, iterations, salt, url|
|
44
|
+
assert_equal expected, get_hashed_password2(password, extract_domain(url), salt, iterations)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_collisions_with_weak_passwords
|
49
|
+
# => Use a longer password!
|
50
|
+
[
|
51
|
+
['foo', 1000, 'bar', 'https://manifolds.org', 'https://boxwoods.com'],
|
52
|
+
['foo', 50_000, 'aYcErTYgi0AoB2tDbP80fwR5GAWwUvg8', 'http://dainty.co.uk', 'http://polemic.com'],
|
53
|
+
['foobar', 100, 'salt', 'https://abounds.edu.au', 'https://coaxed.co.nz'],
|
54
|
+
].each do |password, iterations, salt, url1, url2|
|
55
|
+
assert_equal get_hashed_password2(password, extract_domain(url1), salt, iterations),
|
56
|
+
get_hashed_password2(password, extract_domain(url2), salt, iterations)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_edge_cases
|
61
|
+
# For testing only! Please always define salt and password. Number of iterations shouldn't be too low and password should be long enough.
|
62
|
+
[
|
63
|
+
['WWNEC9x1', 'foobar', 1000, '', 'https://google.com'],
|
64
|
+
['EBr2', '', 1000, '', 'https://google.com'],
|
65
|
+
['w0WD', '', 1, '', 'https://google.com'],
|
66
|
+
['w0WD', '', 0, '', 'https://google.com'],
|
67
|
+
['w0WD', '', -1, '', 'https://google.com'],
|
68
|
+
].each do |expected, password, iterations, salt, url|
|
69
|
+
assert_equal expected, get_hashed_password2(password, extract_domain(url), salt, iterations)
|
70
|
+
end
|
71
|
+
end
|
18
72
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pwdhash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Yuen
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -16,65 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
- - ">="
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 1.6.12
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.6'
|
30
|
-
- - ">="
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 1.6.12
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: ruby-hmac
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '0.4'
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: 0.4.0
|
19
|
+
version: 3.1.2
|
43
20
|
type: :runtime
|
44
21
|
prerelease: false
|
45
22
|
version_requirements: !ruby/object:Gem::Requirement
|
46
23
|
requirements:
|
47
24
|
- - "~>"
|
48
25
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: 0.4.0
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: backports
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '3.6'
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 3.6.4
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - "~>"
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '3.6'
|
70
|
-
- - ">="
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
version: 3.6.4
|
26
|
+
version: 3.1.2
|
73
27
|
description: Command line version of Stanford PwdHash
|
74
28
|
email:
|
75
29
|
- chris@kizzx2.com
|
76
30
|
executables:
|
77
31
|
- pwdhash
|
32
|
+
- pwdhash2
|
78
33
|
extensions: []
|
79
34
|
extra_rdoc_files: []
|
80
35
|
files:
|
@@ -84,6 +39,7 @@ files:
|
|
84
39
|
- README.markdown
|
85
40
|
- Rakefile
|
86
41
|
- bin/pwdhash
|
42
|
+
- bin/pwdhash2
|
87
43
|
- lib/pwdhash/extract_domain.rb
|
88
44
|
- lib/pwdhash/pwdhash.rb
|
89
45
|
- lib/pwdhash/version.rb
|
@@ -93,7 +49,7 @@ homepage: https://github.com/kizzx2/pwdhash.rb
|
|
93
49
|
licenses:
|
94
50
|
- GPL-3.0
|
95
51
|
metadata: {}
|
96
|
-
post_install_message:
|
52
|
+
post_install_message:
|
97
53
|
rdoc_options: []
|
98
54
|
require_paths:
|
99
55
|
- lib
|
@@ -108,9 +64,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
64
|
- !ruby/object:Gem::Version
|
109
65
|
version: '0'
|
110
66
|
requirements: []
|
111
|
-
|
112
|
-
|
113
|
-
signing_key:
|
67
|
+
rubygems_version: 3.4.19
|
68
|
+
signing_key:
|
114
69
|
specification_version: 4
|
115
70
|
summary: Command line version of Stanford PwdHash
|
116
71
|
test_files: []
|