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