session_keys 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/session-keys-rb.svg)](https://badge.fury.io/rb/session-keys-rb)
|
4
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/grempe/session-keys-rb.svg)](https://gemnasium.com/github.com/grempe/session-keys-rb)
|
5
|
+
[![Build Status](https://travis-ci.org/grempe/session-keys-rb.svg?branch=master)](https://travis-ci.org/grempe/session-keys-rb)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/github/grempe/session-keys-rb/badge.svg?branch=master)](https://coveralls.io/github/grempe/session-keys-rb?branch=master)
|
7
|
+
[![Code Climate](https://codeclimate.com/github/grempe/session-keys-rb/badges/gpa.svg)](https://codeclimate.com/github/grempe/session-keys-rb)
|
8
|
+
[![Inline docs](http://inch-ci.org/github/grempe/session-keys-rb.svg?branch=master)](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
|