session_keys 0.1.0
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/.coco.yml +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +1245 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +145 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/certs/gem-public_cert_grempe.pem +21 -0
- data/lib/session_keys.rb +188 -0
- data/lib/session_keys/version.rb +3 -0
- data/session_keys.gemspec +68 -0
- metadata +229 -0
- metadata.gz.sig +3 -0
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private lib/**/*.rb - README.md LICENSE.txt CODE_OF_CONDUCT.md
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at glenn@rempe.us. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in session_keys.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# FIXME : remove when @bascule publishes new rbnacl release
|
7
|
+
# including commit : dc1e8d10b8fc4784130b0864d45b9275a9e979dc
|
8
|
+
# https://github.com/cryptosphere/rbnacl/pull/135
|
9
|
+
gem 'rbnacl', git: 'https://github.com/cryptosphere/rbnacl.git'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Glenn Rempe
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# SessionKeys
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/session-keys-rb)
|
4
|
+
[](https://gemnasium.com/github.com/grempe/session-keys-rb)
|
5
|
+
[](https://travis-ci.org/grempe/session-keys-rb)
|
6
|
+
[](https://coveralls.io/github/grempe/session-keys-rb?branch=master)
|
7
|
+
[](https://codeclimate.com/github/grempe/session-keys-rb)
|
8
|
+
[](http://inch-ci.org/github/grempe/session-keys-rb)
|
9
|
+
|
10
|
+
SessionKeys is a cryptographic tool for the deterministic generation of
|
11
|
+
NaCl compatible [Curve25519](https://cr.yp.to/ecdh.html) encryption and
|
12
|
+
[Ed25519](http://ed25519.cr.yp.to) digital signature keys.
|
13
|
+
|
14
|
+
The strength of the system lies in the fact that the keypairs are derived from
|
15
|
+
passing an identifier, such as a username or email address, and a high-entropy
|
16
|
+
passphrase through the `SHA256` hash and the `scrypt` key derivation
|
17
|
+
functions. This means that no private key material need ever be stored to disk.
|
18
|
+
The generated keys are deterministic; for any given ID, password, and
|
19
|
+
strength combination the same keys will always be returned.
|
20
|
+
|
21
|
+
The generated ID is passed through `SHA256` and `scrypt` and is derived from
|
22
|
+
only the ID parameter your provide and a common salt.
|
23
|
+
|
24
|
+
The password is also passed through `SHA256` and `scrypt` and NaCl encryption
|
25
|
+
and signing keypairs are derived from the combination of the stretched ID,
|
26
|
+
your password, and a common salt.
|
27
|
+
|
28
|
+
## WARNING : BETA CODE
|
29
|
+
|
30
|
+
This code is new and has not yet been tested in production. Use at your own risk.
|
31
|
+
The interface should be fairly stable now but should not be considered fully
|
32
|
+
stable until v1.0.0 is released.
|
33
|
+
|
34
|
+
## Installation
|
35
|
+
|
36
|
+
Add this line to your application's Gemfile:
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
gem 'session_keys'
|
40
|
+
```
|
41
|
+
|
42
|
+
And then execute:
|
43
|
+
|
44
|
+
``` text
|
45
|
+
$ bundle
|
46
|
+
```
|
47
|
+
|
48
|
+
Or install it yourself as:
|
49
|
+
|
50
|
+
``` text
|
51
|
+
$ gem install session_keys
|
52
|
+
```
|
53
|
+
|
54
|
+
### Installation Security : Signed Ruby Gem
|
55
|
+
|
56
|
+
The SessionKeys gem is cryptographically signed. To be sure the gem you install hasn’t
|
57
|
+
been tampered with you can install it using the following method:
|
58
|
+
|
59
|
+
Add required public keys (if you haven’t already) as trusted certificates
|
60
|
+
|
61
|
+
``` text
|
62
|
+
# Caveat: Gem certificates are trusted globally, such that adding a
|
63
|
+
# cert.pem for one gem automatically trusts all gems signed by that cert.
|
64
|
+
gem cert --add <(curl -Ls https://raw.githubusercontent.com/cryptosphere/rbnacl/master/bascule.cert)
|
65
|
+
gem cert --add <(curl -Ls https://raw.github.com/grempe/session-keys-rb/master/certs/gem-public_cert_grempe.pem)
|
66
|
+
```
|
67
|
+
|
68
|
+
To install, it is possible to specify either `HighSecurity` or `MediumSecurity`
|
69
|
+
mode. Since the `session_keys` gem depends on one or more gems that are not cryptographically
|
70
|
+
signed you will likely need to use `MediumSecurity`. You should receive a warning
|
71
|
+
if any signed gem does not match its signature.
|
72
|
+
|
73
|
+
``` text
|
74
|
+
# All dependent gems must be signed and verified.
|
75
|
+
gem install session_keys -P HighSecurity
|
76
|
+
```
|
77
|
+
|
78
|
+
``` text
|
79
|
+
# All signed dependent gems must be verified.
|
80
|
+
gem install session_keys -P MediumSecurity
|
81
|
+
```
|
82
|
+
|
83
|
+
``` text
|
84
|
+
# Same as above, except Bundler only recognizes
|
85
|
+
# the long --trust-policy flag, not the short -P
|
86
|
+
bundle --trust-policy MediumSecurity
|
87
|
+
```
|
88
|
+
|
89
|
+
You can [learn more about security and signed Ruby Gems](http://guides.rubygems.org/security/).
|
90
|
+
|
91
|
+
### Installation Security : Signed Git Commits
|
92
|
+
|
93
|
+
Most, if not all, of the commits and tags to the repository for this code are
|
94
|
+
signed with my PGP/GPG code signing key. I have uploaded my code signing public
|
95
|
+
keys to GitHub and you can now verify those signatures with the GitHub UI.
|
96
|
+
See [this list of commits](https://github.com/grempe/session-keys-rb/commits/master)
|
97
|
+
and look for the `Verified` tag next to each commit. You can click on that tag
|
98
|
+
for additional information.
|
99
|
+
|
100
|
+
You can also clone the repository and verify the signatures locally using your
|
101
|
+
own GnuPG installation. You can find my certificates and read about how to conduct
|
102
|
+
this verification at [https://www.rempe.us/keys/](https://www.rempe.us/keys/).
|
103
|
+
|
104
|
+
## Usage
|
105
|
+
|
106
|
+
``` ruby
|
107
|
+
keys = SessionKeys.generate('user@example.com', 'my strong passphrase')
|
108
|
+
#=> {...}
|
109
|
+
```
|
110
|
+
|
111
|
+
## Development
|
112
|
+
|
113
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
114
|
+
run `rake test` to run the tests. You can also run `bin/console` for an
|
115
|
+
interactive prompt that will allow you to experiment.
|
116
|
+
|
117
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
118
|
+
|
119
|
+
## Contributing
|
120
|
+
|
121
|
+
Bug reports and pull requests are welcome on GitHub at
|
122
|
+
[https://github.com/grempe/session-keys-rb](https://github.com/grempe/session-keys-rb).
|
123
|
+
This project is intended to be a safe, welcoming space for collaboration, and
|
124
|
+
contributors are expected to adhere to the
|
125
|
+
[Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
126
|
+
|
127
|
+
## Legal
|
128
|
+
|
129
|
+
### Copyright
|
130
|
+
|
131
|
+
(c) 2016 Glenn Rempe <[glenn@rempe.us](mailto:glenn@rempe.us)> ([https://www.rempe.us/](https://www.rempe.us/))
|
132
|
+
|
133
|
+
### License
|
134
|
+
|
135
|
+
The gem is available as open source under the terms of
|
136
|
+
the [MIT License](http://opensource.org/licenses/MIT).
|
137
|
+
|
138
|
+
### Warranty
|
139
|
+
|
140
|
+
Unless required by applicable law or agreed to in writing,
|
141
|
+
software distributed under the License is distributed on an
|
142
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
143
|
+
either express or implied. See the LICENSE.txt file for the
|
144
|
+
specific language governing permissions and limitations under
|
145
|
+
the License.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'wwtd/tasks'
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.libs << 'lib'
|
8
|
+
t.test_files = FileList['test/**/*_test.rb']
|
9
|
+
t.verbose = false
|
10
|
+
t.warning = false
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :test
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'session_keys'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQUFADA7MQ4wDAYDVQQDDAVnbGVu
|
3
|
+
bjEVMBMGCgmSJomT8ixkARkWBXJlbXBlMRIwEAYKCZImiZPyLGQBGRYCdXMwHhcN
|
4
|
+
MTYwNDExMDI0NTU0WhcNMTcwNDExMDI0NTU0WjA7MQ4wDAYDVQQDDAVnbGVubjEV
|
5
|
+
MBMGCgmSJomT8ixkARkWBXJlbXBlMRIwEAYKCZImiZPyLGQBGRYCdXMwggEiMA0G
|
6
|
+
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZqTH5Jf+D/W2B4BIiL49CpHa86rK/
|
7
|
+
oT+v3xZwuEE92lJea+ygn3IAsidVTW47AKE6Lt3UqUkGQGKxsqH/Dhir08BqjLlD
|
8
|
+
gBUozGZpM3B6uWZnD6QXLbOmZeGVDnwB/QDfzaawN1i3smlYxYT+KNLjl80aN3we
|
9
|
+
/cHAWG7JG47AF/S91mYcg1WgZnDgZt9+RyVR1AsfYbM+SidOSoXEOHPCbuUxLKJb
|
10
|
+
gj5ieCFhm5GNWEugvgiX/ruas+VHV0fF3fzjYlU2fZPTuQyB4UD5FWX4UqdsBf3w
|
11
|
+
jB94TDBsJ3FVGPbggEhLGKd8pbQmBIOqXolGaqhs7dnuf5imu5mAXHC1AgMBAAGj
|
12
|
+
bzBtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRfxEyosUbKjfFa
|
13
|
+
j+gae2CcT3aFCTAZBgNVHREEEjAQgQ5nbGVubkByZW1wZS51czAZBgNVHRIEEjAQ
|
14
|
+
gQ5nbGVubkByZW1wZS51czANBgkqhkiG9w0BAQUFAAOCAQEAzgK20+MNOknR9Kx6
|
15
|
+
RisI3DsioCADjGldxY+INrwoTfPDVmNm4GdTYC+V+/BvxJw1RqHjEbuXSg0iibQC
|
16
|
+
4vN+th0Km7dnas/td1i+EKfGencfyQyecIaG9l3kbCkCWnldRtZ+BS5EfP2ML2u8
|
17
|
+
fyCtze/Piovu8IwXL1W5kGZMnvzLmWxdqI3VPUou40n8F+EiMMLgd53kpzjtNOau
|
18
|
+
4W+mqVGOwlEGVSgI5+0SIsD8pvc62PlPWTv0kn1bcufKKCZmoVmpfbe3j4JpBInq
|
19
|
+
zieXiXZSAojfFx9g91fKdIrlPbInHU/BaCxXSLBwvOM0drE+c2ue9X8gB55XAhzX
|
20
|
+
37oBiw==
|
21
|
+
-----END CERTIFICATE-----
|
data/lib/session_keys.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'session_keys/version'
|
2
|
+
require 'rbnacl/libsodium'
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'zxcvbn'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
# SessionKeys deterministic cryptographic key generation.
|
8
|
+
module SessionKeys
|
9
|
+
# Opslimit represents a maximum amount of computations to perform.
|
10
|
+
# Raising this number will make the function require more CPU cycles to
|
11
|
+
# compute a key.
|
12
|
+
#
|
13
|
+
# Number of scrypt computations for scrypt to perform for interactive security setting.
|
14
|
+
# Set to SCRYPT_MEMLIMIT_INTERACTIVE / 32
|
15
|
+
#
|
16
|
+
# For interactive, online operations, `SCRYPT_OPSLIMIT_INTERACTIVE` and
|
17
|
+
# `SCRYPT_MEMLIMIT_INTERACTIVE` provide a safe base line for these two parameters.
|
18
|
+
# However, using higher values may improve security.
|
19
|
+
#
|
20
|
+
# See : https://download.libsodium.org/doc/password_hashing/scrypt.html
|
21
|
+
SCRYPT_OPSLIMIT_INTERACTIVE = 2**19
|
22
|
+
|
23
|
+
# Memlimit is the maximum amount of RAM that the function will use, in
|
24
|
+
# bytes. It is highly recommended to allow the function to use at least 16
|
25
|
+
# megabytes.
|
26
|
+
#
|
27
|
+
# Max RAM in Bytes to be used by scrypt for interactive security setting.
|
28
|
+
SCRYPT_MEMLIMIT_INTERACTIVE = 2**24
|
29
|
+
|
30
|
+
# Number of scrypt computations for scrypt to perform for sensitive security setting.
|
31
|
+
# Set to SCRYPT_MEMLIMIT_SENSITIVE / 32
|
32
|
+
#
|
33
|
+
# For highly sensitive data, `SCRYPT_OPSLIMIT_SENSITIVE` and `SCRYPT_MEMLIMIT_SENSITIVE` can
|
34
|
+
# be used as an alternative. But with these parameters, deriving a key takes
|
35
|
+
# about 2 seconds on a 2.8 Ghz Core i7 CPU and requires up to 1 gigabyte of
|
36
|
+
# dedicated RAM.
|
37
|
+
SCRYPT_OPSLIMIT_SENSITIVE = 2**25
|
38
|
+
|
39
|
+
# Max RAM in Bytes to be used by scrypt for sensitive security setting.
|
40
|
+
SCRYPT_MEMLIMIT_SENSITIVE = 2**30
|
41
|
+
|
42
|
+
# Size in Bytes of the scrypt derived output for the id
|
43
|
+
SCRYPT_DIGEST_SIZE_ID = 32
|
44
|
+
|
45
|
+
# Size in Bytes of the scrypt derived output for the password
|
46
|
+
SCRYPT_DIGEST_SIZE_PASSWORD = 256
|
47
|
+
|
48
|
+
# A site-wide 32 Byte common random value that will be concatenated with a value
|
49
|
+
# being hashed for some additional measure of security against dictionary
|
50
|
+
# style attacks. This value was randomly chosen but must be the same across
|
51
|
+
# implementations and is assumed public.
|
52
|
+
PEPPER = 'f01f0a0c44a2d1e7e5b00d7dc78941d404474a90ce7f4ae9d1432bf76fa169e7'.freeze
|
53
|
+
|
54
|
+
# Deterministically generates a collection of derived encryption key material from
|
55
|
+
# a provided id and password. Uses SHA256 and scrypt for key derivation.
|
56
|
+
#
|
57
|
+
# @param id [String] a unique UTF-8 String identifier such as a username or
|
58
|
+
# email address. Max length 256 characters.
|
59
|
+
# @param password [String] a cryptographically strong UTF-8 password or
|
60
|
+
# passphrase. Max length 256 characters.
|
61
|
+
# @param strength [Symbol] the desired strength of the key derivation. Can be
|
62
|
+
# the symbols :interactive or (:sensitive).
|
63
|
+
# @param min_password_entropy [Integer] the minimum (75) estimated entropy allowed
|
64
|
+
# for the password. This will be measured with Zxcvbn.
|
65
|
+
# @return [Hash] returns a Hash of keys and derived key material.
|
66
|
+
# @raise [ArgumentError] if invalid arguments are provided.
|
67
|
+
def self.generate(id, password, strength = :sensitive, min_password_entropy = 75)
|
68
|
+
unless id.is_a?(String) && id.encoding.name == 'UTF-8'
|
69
|
+
raise ArgumentError, 'invalid id, not a UTF-8 string'
|
70
|
+
end
|
71
|
+
|
72
|
+
unless id.length.between?(1,256)
|
73
|
+
raise ArgumentError, 'invalid id, must be between 1 and 256 characters in length'
|
74
|
+
end
|
75
|
+
|
76
|
+
unless password.is_a?(String) && password.encoding.name == 'UTF-8'
|
77
|
+
raise ArgumentError, 'invalid password, not a UTF-8 string'
|
78
|
+
end
|
79
|
+
|
80
|
+
# Enforce max length only due to Zxcvbn taking a *long* time to
|
81
|
+
# process long strings and determine entropy.
|
82
|
+
unless password.length.between?(1,256)
|
83
|
+
raise ArgumentError, 'invalid password, must be between 1 and 256 characters in length'
|
84
|
+
end
|
85
|
+
|
86
|
+
unless min_password_entropy.is_a?(Integer) && min_password_entropy.between?(1, 512)
|
87
|
+
raise ArgumentError, 'invalid min_password_entropy, must be an Integer between 1 and 512'
|
88
|
+
end
|
89
|
+
|
90
|
+
password_test = Zxcvbn.test(password)
|
91
|
+
unless password_test.entropy.round >= min_password_entropy
|
92
|
+
raise ArgumentError, "invalid password, must be at least #{min_password_entropy} bits of estimated entropy"
|
93
|
+
end
|
94
|
+
|
95
|
+
unless [:interactive, :sensitive].include?(strength)
|
96
|
+
raise ArgumentError, 'invalid strength, must be :interactive (min), or :sensitive (strong)'
|
97
|
+
end
|
98
|
+
|
99
|
+
start_processing_time = Time.now
|
100
|
+
|
101
|
+
# Run the ID and a 'pepper' (an app common salt) through scrypt. This will be
|
102
|
+
# the system ID for this user. This processing is done to prevent knowledge
|
103
|
+
# of the user on the server side and prevent the ability to reverse this
|
104
|
+
# ID back into a username or email. Using scrypt instead of a SHA256 Hash
|
105
|
+
# is so that it will also take unreasonable effort for someone with a list
|
106
|
+
# of user identifiers from looking up users on the system quickly even if
|
107
|
+
# provided with a local copy of the DB.
|
108
|
+
id_sha256_bytes = RbNaCl::Hash.sha256(id.bytes.pack('C*'))
|
109
|
+
|
110
|
+
id_sha256_pepper_bytes = RbNaCl::Hash.sha256(
|
111
|
+
"#{id}#{id.length}#{PEPPER}#{PEPPER.length}".bytes.pack('C*')
|
112
|
+
)
|
113
|
+
|
114
|
+
id_scrypt_hex = RbNaCl::PasswordHash.scrypt(
|
115
|
+
id_sha256_bytes,
|
116
|
+
id_sha256_pepper_bytes,
|
117
|
+
SCRYPT_OPSLIMIT_INTERACTIVE,
|
118
|
+
SCRYPT_MEMLIMIT_INTERACTIVE,
|
119
|
+
SCRYPT_DIGEST_SIZE_ID
|
120
|
+
).bytes.map { |byte| '%02x' % byte }.join
|
121
|
+
|
122
|
+
# libsodium : By design, a password whose length is 65 bytes or more is
|
123
|
+
# reduced to SHA-256(password). This can have security implications if the
|
124
|
+
# password is present in another password database using raw, unsalted
|
125
|
+
# SHA-256. Or when upgrading passwords previously hashed with unsalted
|
126
|
+
# SHA-256 to scrypt. If this is a concern, passwords should be pre-hashed
|
127
|
+
# before being hashed using scrypt.
|
128
|
+
password_sha256_bytes = RbNaCl::Hash.sha256(password.bytes.pack('C*'))
|
129
|
+
|
130
|
+
password_sha256_pepper_bytes = RbNaCl::Hash.sha256(
|
131
|
+
"#{id_scrypt_hex}#{id_scrypt_hex.length}#{PEPPER}#{PEPPER.length}".bytes.pack('C*')
|
132
|
+
)
|
133
|
+
|
134
|
+
# Derive SCRYPT_DIGEST_SIZE_PASSWORD secret bytes. They will be split
|
135
|
+
# into 32 Byte chunks to serve as deterministic seeds for ID or key
|
136
|
+
# generation. Some derived bytes are reserved for future use.
|
137
|
+
password_digest = RbNaCl::PasswordHash.scrypt(
|
138
|
+
password_sha256_bytes,
|
139
|
+
password_sha256_pepper_bytes,
|
140
|
+
strength == :interactive ? SCRYPT_OPSLIMIT_INTERACTIVE : SCRYPT_OPSLIMIT_SENSITIVE,
|
141
|
+
strength == :interactive ? SCRYPT_MEMLIMIT_INTERACTIVE : SCRYPT_MEMLIMIT_SENSITIVE,
|
142
|
+
SCRYPT_DIGEST_SIZE_PASSWORD
|
143
|
+
).bytes
|
144
|
+
|
145
|
+
# Break up the scrypt digest into 32 Byte seeds.
|
146
|
+
secret_bytes = []
|
147
|
+
(SCRYPT_DIGEST_SIZE_PASSWORD/32).times { secret_bytes << password_digest.shift(32) }
|
148
|
+
|
149
|
+
# Seed 0 : RbNaCl::SimpleBox
|
150
|
+
# The seed bytes are used as a 32 Byte key suitable for
|
151
|
+
# simple symetric key encryption using `RbNaCl::SimpleBox`. SimpleBox is
|
152
|
+
# a wrapper around NaCl SecretBox construct with automated nonce management.
|
153
|
+
#
|
154
|
+
# To encrypt/decreypt with this object try:
|
155
|
+
# ciphertext = nacl_simple_box.encrypt('foobar')
|
156
|
+
# plaintext = nacl_simple_box.decrypt(ciphertext)
|
157
|
+
nacl_simple_box_key = secret_bytes[0].pack('C*').force_encoding('ASCII-8BIT')
|
158
|
+
nacl_simple_box = RbNaCl::SimpleBox.from_secret_key(nacl_simple_box_key)
|
159
|
+
|
160
|
+
# Seed 1 : NaCl Box Keypair
|
161
|
+
nacl_enc_sec_seed = secret_bytes[1].pack('C*').force_encoding('ASCII-8BIT')
|
162
|
+
nacl_enc_sec_key = RbNaCl::PrivateKey.new(nacl_enc_sec_seed)
|
163
|
+
nacl_enc_pub_key = nacl_enc_sec_key.public_key
|
164
|
+
|
165
|
+
# Seed 2 : NaCl Signing Keypair
|
166
|
+
nacl_sig_sec_seed = secret_bytes[2].pack('C*').force_encoding('ASCII-8BIT')
|
167
|
+
nacl_sig_sec_key = RbNaCl::SigningKey.new(nacl_sig_sec_seed)
|
168
|
+
nacl_sig_pub_key = nacl_sig_sec_key.verify_key
|
169
|
+
|
170
|
+
# Seed 3 : Reserved for future use.
|
171
|
+
# Seed 4 : Reserved for future use.
|
172
|
+
# Seed 5 : Reserved for future use.
|
173
|
+
# Seed 6 : Reserved for future use.
|
174
|
+
# Seed 7 : Reserved for future use.
|
175
|
+
|
176
|
+
{
|
177
|
+
id: id_scrypt_hex,
|
178
|
+
nacl_simple_box: nacl_simple_box,
|
179
|
+
nacl_enc_pub_key: nacl_enc_pub_key,
|
180
|
+
nacl_enc_sec_key: nacl_enc_sec_key,
|
181
|
+
nacl_sig_pub_key: nacl_sig_pub_key,
|
182
|
+
nacl_sig_sec_key: nacl_sig_sec_key,
|
183
|
+
nacl_enc_pub_key_b64: Base64.strict_encode64(nacl_enc_pub_key.to_bytes),
|
184
|
+
nacl_sig_pub_key_b64: Base64.strict_encode64(nacl_sig_pub_key.to_bytes),
|
185
|
+
process_time: ((Time.now - start_processing_time)*1000).round(2)
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|