scrypt-util 0.1.4.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/scrypt-util.rb +144 -0
  3. metadata +61 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 25e0186d4d354b523b3906e4bb1032b2e342bffd
4
+ data.tar.gz: 4b18f33517eed4a9f19fae900bf148c302d36b25
5
+ SHA512:
6
+ metadata.gz: 05698c2a9fee97e956e8e27a897b45a8f3c7c73a0234ec5c6d086c703496ba128413e8151c983b4342f631ff6c7c0f0970138828dd9b1af66a9ff5a7cf8864fb
7
+ data.tar.gz: be48509e49b9e0ed47eb7286c7425db277c50128040294f05033338dc4ce30be5abba97326bb155e7104ea851c4a0bc3c63bee403e277cf788ef5c06ea3f6caa
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # Released under the MIT License
4
+ # (See the LICENSE file)
5
+ #
6
+ # Copyright (c) 2016, Pandu POLUAN
7
+
8
+ # Prerequisites: See EndNote[2]
9
+
10
+ require "base64"
11
+ require "scrypt"
12
+
13
+ # Provides the Java.scrypt-like functions
14
+ #
15
+ # @author Pandu POLUAN <pepoluan@gmail.com>
16
+ # @note Unlike Java.scrypt, the scrypt() function has default values
17
+ module SCryptUtil
18
+
19
+ # Version of this module/gem
20
+ VERSION = '0.1.4.1'
21
+
22
+ @@HashLength = 32
23
+ @@SaltLength = 16
24
+
25
+ # Not sure why but required by generate_salt()
26
+ # A module that's too smart for its own good...
27
+ SCrypt::Engine.calibrate!
28
+
29
+ # Takes a password and outputs a hash (in MCF syntax) using an internally generated salt
30
+ #
31
+ # @param password [String] The password to be hashed
32
+ # @param n [Int] CPU/Memory cost parameter, must be power of 2, larger than 1. Default 32768
33
+ # @param r [Int] Block size parameter, 0 < r < 256. Default 8
34
+ # @param p [Int] Parallelization parameter, 0 < p < 256. Default 1
35
+ # @raise [ArgumentError] If any of the parameters do not fulfill the requirements
36
+ # @return [String] The hash (and parameters and salt) in MCF syntax
37
+ # @note In the future, the default values for n, r, and p might need to be increased to adapt to
38
+ # Moore's Law
39
+ def SCryptUtil.scrypt(password, n = 32768, r = 8, p = 1)
40
+
41
+ raise ArgumentError, 'n must be power of 2, larger than 1' unless ( n > 1 && ( (n & (n-1)) == 0 ) )
42
+ raise ArgumentError, '0 < r < 256' unless ( r < 256 && r > 0 )
43
+ rause ArgumentError, '0 < p < 256' unless ( p < 256 && p > 0 )
44
+
45
+ nlog = 0
46
+ while n > 1 do
47
+ nlog += 1
48
+ n = n >> 1
49
+ end
50
+
51
+ # Generate a "long enough" salt. The function will return a hexstring
52
+ salt_hex = SCrypt::Engine.generate_salt()
53
+ # Trim salt to desired # of bytes; 1 byte = 2 hex digits
54
+ salt_hex = salt_hex[0,@@SaltLength*2]
55
+ # Change hex digit pairs to actual bytes
56
+ salt_bin = [salt_hex].pack('H*')
57
+
58
+ # Unlike the generate_salt() function, the scrypt() function returns binary bytes
59
+ hash_bin = SCrypt::Engine.scrypt(password, salt_bin, (2 ** nlog), r, p, @@HashLength)
60
+
61
+ params_i = (nlog << 16) | (r << 8) | p
62
+ salt64 = Base64.strict_encode64(salt_bin)
63
+ hash64 = Base64.strict_encode64(hash_bin)
64
+
65
+ hash_mcf = '$s0$' + params_i.to_s(16) + '$' + salt64 + '$' + hash64
66
+
67
+ return hash_mcf
68
+ end
69
+
70
+ # Checks if a password actually matches a hash (in MCF syntax, probably from DB)
71
+ #
72
+ # @param password [String] The password to be checked
73
+ # @param hash_mcf [String] The hash (in MCF syntax) to be compared to
74
+ # @param secure_compare [Boolean] Whether to use the more secure but much slower string comparison function
75
+ # @return [Boolean] True if password matches the hash
76
+ def SCryptUtil.check(password, hash_mcf, secure_compare = true)
77
+
78
+ # See EndNote[3]
79
+ return false if hash_mcf.nil? || hash_mcf.empty?
80
+
81
+ elems = hash_mcf.split('$')
82
+ raise "Unrecognized MCF hash format: " + hash_mcf if elems.length != 5
83
+ signature, params_hex, salt64, hash64 = elems[1..4]
84
+
85
+ return "Unrecognized MCF hash format: " + hash_mcf if \
86
+ signature.empty? || params_hex.empty? || salt64.empty? || hash64.empty?
87
+
88
+ salt_bin = Base64.decode64(salt64)
89
+
90
+ params_i = params_hex.to_i(16)
91
+ nlog = (params_i >> 16)
92
+ r = (params_i >> 8) & 0xff
93
+ p = params_i & 0xff
94
+
95
+ calculated = SCrypt::Engine.scrypt(password, salt_bin, (2 ** nlog), r, p, @@HashLength)
96
+ calculated64 = Base64.strict_encode64(calculated)
97
+
98
+ return secure_compare ? SCryptUtil.secure_compare_string(hash64, calculated64) : (hash64 == calculated64)
99
+ end
100
+
101
+ # A string-compare function which is (hopefully) resistant to timing attacks since the time to perform
102
+ # comparison depends only on the length of the longest string, and unlike memcmp() does not depend
103
+ # on how when the first equality happened.
104
+ #
105
+ # @author Pandu POLUAN <pepoluan@gmail.com>
106
+ # @param str1 [String] First string to compare
107
+ # @param str2 [String] Second string to compare
108
+ # @note This function is SLOW, and it is kind-of by design.
109
+ def SCryptUtil.secure_compare_string(str1, str2)
110
+ raise ArgumentError, 'str1 and str2 must both be a non-empty string' if \
111
+ str1.nil? || str1.empty? || str2.nil? || str2.empty?
112
+ s1 = str1.bytes
113
+ s2 = str2.bytes
114
+ shortest, longest = [s1.length - 1, s2.length - 1].minmax
115
+ rslt = 0
116
+ for idx in (0..longest)
117
+ # See EndNote[1]
118
+ rslt |= (idx > shortest) ? ((s1[0] ^ s2[0]) | 0xFF) : (s1[idx] ^ s2[idx])
119
+ end
120
+ return rslt == 0
121
+ end
122
+
123
+ end
124
+
125
+ =begin
126
+
127
+ EndNotes
128
+ ========
129
+
130
+ [1] Why XOR-ing s1[0] with s2[0] repeatedly? The concept is to try to make the left side
131
+ of the colon to take approximately the same amount of time with the right side of
132
+ the colon, making comparison time to wholly depend on the length of the longest string.
133
+
134
+ [2] On Ubuntu 14.04/16.04 Fresh Install, you *must* do:
135
+ apt-get install build-essential ruby ruby-dev
136
+ gem install rake
137
+ gem install scrypt
138
+
139
+ [3] Return 'false' instead of raising exception, because hash_mcf might come from a database
140
+ whose 'hash' column is empty/NULL, representing a user whose password had been reset or
141
+ who need to set their password for the first time or...
142
+ Well, in short, there are many possible explanations, so this is not unexpected.
143
+
144
+ =end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scrypt-util
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Pandu POLUAN
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: scrypt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.0
27
+ description: This gem introduces two function that act as the simplified wrapper around
28
+ the 'scrypt' gem, and also provides input/output compatibility with the scrypt for
29
+ Java implementation (available on https://github.com/wg/scrypt )
30
+ email: pepoluan@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/scrypt-util.rb
36
+ homepage: https://bitbucket.org/pepoluan/scrypt-util-ruby
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.4.8
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Simplified wrapper around the scrypt gem
60
+ test_files: []
61
+ has_rdoc: