scrypt-util 0.1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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: