pwdhash 0.3.2 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 +0 -2
- data/pwdhash_test.rb +56 -2
- metadata +5 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 80965617c1934a63980a9dc44272917781ed932ede352980404899742b786166
|
4
|
+
data.tar.gz: f4f462e26e0af2defbd9d8d011e959fc3edac2c4601d991741685e7161fe6dd7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba69797cbb0fad88fdaa469c17f3b21e3d5d532bb71e9070ece6c1178e135e550578e81a982f83f9f3ae82d7be633bdbf6c225479b597b56323a53cd8f9de9b5
|
7
|
+
data.tar.gz: 778b565e64c59a764ac6bab2ca5592336afcb71cdfc1a446c8ddc035e28acc34698f554f88b4675425efed318c5f0e660cac0b1759856cd32770f0c9ea75c750
|
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
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.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Yuen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -30,51 +30,12 @@ dependencies:
|
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
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
|
43
|
-
type: :runtime
|
44
|
-
prerelease: false
|
45
|
-
version_requirements: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '0.4'
|
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
|
73
33
|
description: Command line version of Stanford PwdHash
|
74
34
|
email:
|
75
35
|
- chris@kizzx2.com
|
76
36
|
executables:
|
77
37
|
- pwdhash
|
38
|
+
- pwdhash2
|
78
39
|
extensions: []
|
79
40
|
extra_rdoc_files: []
|
80
41
|
files:
|
@@ -84,6 +45,7 @@ files:
|
|
84
45
|
- README.markdown
|
85
46
|
- Rakefile
|
86
47
|
- bin/pwdhash
|
48
|
+
- bin/pwdhash2
|
87
49
|
- lib/pwdhash/extract_domain.rb
|
88
50
|
- lib/pwdhash/pwdhash.rb
|
89
51
|
- lib/pwdhash/version.rb
|
@@ -109,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
71
|
version: '0'
|
110
72
|
requirements: []
|
111
73
|
rubyforge_project:
|
112
|
-
rubygems_version: 2.
|
74
|
+
rubygems_version: 2.7.6
|
113
75
|
signing_key:
|
114
76
|
specification_version: 4
|
115
77
|
summary: Command line version of Stanford PwdHash
|