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.
- checksums.yaml +7 -0
- data/lib/scrypt-util.rb +144 -0
- 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
|
data/lib/scrypt-util.rb
ADDED
@@ -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:
|