opensecret 0.0.988 → 0.0.9925
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 +5 -5
- data/README.md +56 -159
- data/bin/opensecret +2 -2
- data/bin/ops +17 -2
- data/lib/extension/string.rb +14 -16
- data/lib/{interpreter.rb → interprete.rb} +53 -29
- data/lib/keytools/binary.map.rb +49 -0
- data/lib/keytools/kdf.api.rb +249 -0
- data/lib/keytools/kdf.bcrypt.rb +64 -29
- data/lib/keytools/kdf.pbkdf2.rb +92 -83
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1281 -0
- data/lib/keytools/key.db.rb +265 -0
- data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +265 -0
- data/lib/keytools/key.mach.rb +248 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +428 -298
- data/lib/keytools/keydebug.txt +295 -0
- data/lib/logging/gem.logging.rb +3 -3
- data/lib/modules/cryptology/collect.rb +20 -0
- data/lib/session/require.gem.rb +1 -1
- data/lib/usecase/cmd.rb +417 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +174 -0
- data/lib/usecase/init.rb +78 -0
- data/lib/usecase/login.rb +70 -0
- data/lib/usecase/logout.rb +30 -0
- data/lib/usecase/open.rb +126 -0
- data/lib/{interprete → usecase}/put.rb +100 -47
- data/lib/usecase/read.rb +89 -0
- data/lib/{interprete → usecase}/safe.rb +0 -0
- data/lib/{interprete → usecase}/set.rb +0 -0
- data/lib/usecase/token.rb +111 -0
- data/lib/{interprete → usecase}/use.rb +0 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +4 -3
- metadata +39 -33
- data/lib/exception/cli.error.rb +0 -53
- data/lib/exception/errors/cli.errors.rb +0 -31
- data/lib/interprete/begin.rb +0 -232
- data/lib/interprete/cmd.rb +0 -621
- data/lib/interprete/export.rb +0 -163
- data/lib/interprete/init.rb +0 -205
- data/lib/interprete/key.rb +0 -119
- data/lib/interprete/open.rb +0 -148
- data/lib/interprete/seal.rb +0 -129
- data/lib/keytools/digester.rb +0 -245
- data/lib/keytools/key.data.rb +0 -227
- data/lib/keytools/key.derivation.rb +0 -341
- data/lib/modules/mappers/collateral.rb +0 -282
- data/lib/modules/mappers/envelope.rb +0 -127
- data/lib/modules/mappers/settings.rb +0 -170
- data/lib/notepad/scratch.pad.rb +0 -224
- data/lib/store-commands.txt +0 -180
data/lib/keytools/kdf.pbkdf2.rb
CHANGED
@@ -15,7 +15,17 @@ module OpenKey
|
|
15
15
|
# performed to create the key.
|
16
16
|
#
|
17
17
|
# <b>One million (1,000,000) should be the iteration count's lower bound.</b>
|
18
|
-
|
18
|
+
#
|
19
|
+
# == Upgrading the OpenSSL <em>pbkdf2_hmac</em> Behaviour
|
20
|
+
#
|
21
|
+
# As soon as the new Ruby and OpenSSL libraries become commonplace this class should
|
22
|
+
# be upgraded to use the <b>new and improved {OpenSSL::KDF.pbkdf2_hmac} behaviour</b>
|
23
|
+
# rather than {OpenSSL::PKCS5.pbkdf2_hmac}.
|
24
|
+
#
|
25
|
+
# The difficulty is in detecting the operating system's C libraries that are directly
|
26
|
+
# accessed for OpenSSL functionality. If the distinction can be made accurately, those
|
27
|
+
# with newer libraries can reap the benefits immediately.
|
28
|
+
class KeyPbkdf2
|
19
29
|
|
20
30
|
|
21
31
|
# <b>One million iterations</b> is necessary due to the
|
@@ -25,46 +35,59 @@ module OpenKey
|
|
25
35
|
PBKDF2_ITERATION_COUNT = 1000000
|
26
36
|
|
27
37
|
|
28
|
-
#
|
29
|
-
# 12 bytes. However the algorithm's minimum byte length is
|
30
|
-
# 16 so that is what we must use.
|
31
|
-
PBKDF2_OUTPUT_KEY_LENGTH = 16
|
32
|
-
|
33
|
-
|
34
|
-
# The documented recommended salt length in bytes is in between
|
35
|
-
# 16 and 24 bytes. The setting here is at the upper bound of
|
36
|
-
# that range.
|
37
|
-
PBKDF2_SALT_LENGTH_BITS = 24 * 8
|
38
|
-
|
39
|
-
|
40
|
-
# When the key is transported using a 64 character set where
|
41
|
-
# each character is represented by 6 bits - the PBKDF2 key
|
42
|
-
# expands to 132 bits rather than the original 128 bits.
|
38
|
+
# Documentation for this algorithm says this about the key length.
|
43
39
|
#
|
44
|
-
#
|
40
|
+
# Make the key length <b>larger than or equal to the output length</b>
|
41
|
+
# of the <b>underlying digest function</b>, otherwise an attacker could
|
42
|
+
# simply try to brute-force the key.
|
45
43
|
#
|
46
|
-
#
|
47
|
-
#
|
44
|
+
# According to PKCS#5, security is limited by the output length of
|
45
|
+
# the underlying digest function, i.e. security is not improved if a
|
46
|
+
# key length strictly larger than the digest output length is chosen.
|
48
47
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
48
|
+
# Therefore, when using PKCS5 for password storage, it suffices to
|
49
|
+
# store values equal to the digest output length, nothing is gained
|
50
|
+
# by storing larger values.
|
51
|
+
PBKDF2_EXPORT_KEY_LENGTH = OpenSSL::Digest::SHA384.new.digest_length
|
52
|
+
|
52
53
|
|
53
|
-
|
54
|
+
# For a 384 bit digest the key length is 48 bytes and the bit length
|
55
|
+
# is 384 bits.
|
56
|
+
PBKDF2_EXPORT_BIT_LENGTH = PBKDF2_EXPORT_KEY_LENGTH * 8
|
54
57
|
|
55
|
-
|
58
|
+
|
59
|
+
|
60
|
+
# The documented recommended salt length in bytes for the PBKDF2
|
61
|
+
# algorithm is between <b>16 and 24 bytes</b>. The setting here is
|
62
|
+
# at the upper bound of that range.
|
63
|
+
PBKDF2_SALT_LENGTH_BYTES = 24
|
64
|
+
|
65
|
+
|
66
|
+
# Return a random cryptographic salt generated from twenty-four
|
56
67
|
# random bytes produced by a secure random number generator. The
|
57
|
-
# returned salt is Base64 encoded
|
68
|
+
# returned salt is a Base64 encoded string that can be stored
|
69
|
+
# and given straight into the {KeyPbkdf2.generate_key} method.
|
70
|
+
#
|
71
|
+
# + ------------ + -------- + ------------ + ------------------- +
|
72
|
+
# | | Bits | Bytes | Base64 |
|
73
|
+
# | ------------ | -------- | ------------ | ------------------- |
|
74
|
+
# | PBKDF2 Salt | 192 Bits | 24 bytes | 32 characters |
|
75
|
+
# + ------------ + -------- + ------------ + ------------------- +
|
76
|
+
#
|
77
|
+
# The returned salt consists of 32 base64 characters which can be
|
78
|
+
# stored and fed into generate key. Before being used, the generate
|
79
|
+
# key method will convert the 32 base64 characters back into a
|
80
|
+
# <b>24 byte binary</b> formatted string.
|
58
81
|
#
|
59
82
|
# @return [String]
|
60
|
-
#
|
61
|
-
# and
|
62
|
-
|
63
|
-
|
83
|
+
# The returned salt consists of 32 base64 characters which can be
|
84
|
+
# stored and fed into the {KeyPbkdf2.generate_key}. These characters
|
85
|
+
# represent twenty-four (24) randomly and securely generated bytes.
|
86
|
+
def self.generate_pbkdf2_salt
|
87
|
+
return Key64.from_bits( Key.to_random_bits( PBKDF2_SALT_LENGTH_BYTES ) )
|
64
88
|
end
|
65
89
|
|
66
90
|
|
67
|
-
|
68
91
|
# Generate a 128 bit binary key from the PBKDF2 password derivation
|
69
92
|
# function. The most important input to this function is the human
|
70
93
|
# generated key. The best responsibly sourced key with at least 95%
|
@@ -81,6 +104,31 @@ module OpenKey
|
|
81
104
|
# The {Key} returned by this method encapsulates the derived
|
82
105
|
# key of the byte (bit) length specified.
|
83
106
|
#
|
107
|
+
# <b>PBKDF2 Output Key Length Note</b>
|
108
|
+
#
|
109
|
+
# Documentation for this algorithm says this about the key length.
|
110
|
+
#
|
111
|
+
# Typically, the key length should be larger than or equal to the
|
112
|
+
# output length of the underlying digest function, otherwise an
|
113
|
+
# attacker could simply try to brute-force the key. According to
|
114
|
+
# PKCS#5, security is limited by the output length of the underlying
|
115
|
+
# digest function, i.e. security is not improved if a key length
|
116
|
+
# strictly larger than the digest output length is chosen.
|
117
|
+
#
|
118
|
+
# Therefore, when using PKCS5 for password storage, it suffices to
|
119
|
+
# store values equal to the digest output length, nothing is gained
|
120
|
+
# by storing larger values.
|
121
|
+
#
|
122
|
+
# <b>Upgrading the OpenSSL <em>pbkdf2_hmac</em> Behaviour</b>
|
123
|
+
#
|
124
|
+
# As soon as the new Ruby and OpenSSL libraries become commonplace this class should
|
125
|
+
# be upgraded to use the <b>new and improved {OpenSSL::KDF.pbkdf2_hmac} behaviour</b>
|
126
|
+
# rather than {OpenSSL::PKCS5.pbkdf2_hmac}.
|
127
|
+
#
|
128
|
+
# The difficulty is in detecting the operating system's C libraries that are directly
|
129
|
+
# accessed for OpenSSL functionality. If the distinction can be made accurately, those
|
130
|
+
# with newer libraries can reap the benefits immediately.
|
131
|
+
#
|
84
132
|
# @param human_secret [String]
|
85
133
|
# a robust human generated password with as much entropy as can
|
86
134
|
# be mustered. Remember that 40 characters spread randomly over
|
@@ -89,9 +137,11 @@ module OpenKey
|
|
89
137
|
# that has embedded a near 100% entropy rating.
|
90
138
|
#
|
91
139
|
# @param pbkdf2_salt [String]
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
140
|
+
# this parameter salt must consist of 32 base64 characters which
|
141
|
+
# will be converted into a <b>24 byte binary</b> formatted string.
|
142
|
+
# The salt string presented here must have either been recently
|
143
|
+
# generated by {generate_pbkdf2salt} or read from a persistence
|
144
|
+
# store and resubmitted here in order to regenerate the same key.
|
95
145
|
#
|
96
146
|
# @return [Key]
|
97
147
|
# a key holder containing the key which can then be accessed via
|
@@ -99,61 +149,20 @@ module OpenKey
|
|
99
149
|
# encapsulates the derived key with the specified byte count.
|
100
150
|
def self.generate_key human_secret, pbkdf2_salt
|
101
151
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
PBKDF2_OUTPUT_KEY_LENGTH,
|
107
|
-
OpenSSL::Digest::SHA512.new
|
108
|
-
)
|
152
|
+
KeyError.not_new pbkdf2_salt, "PBKDF2 Algorithm Salt"
|
153
|
+
binary_salt = Key.to_binary_from_bit_string( Key64.to_bits( pbkdf2_salt ) )
|
154
|
+
err_msg = "Expected salt of #{PBKDF2_SALT_LENGTH_BYTES} bytes not #{binary_salt.length}."
|
155
|
+
raise ArgumentError, err_msg unless binary_salt.length == PBKDF2_SALT_LENGTH_BYTES
|
109
156
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
# ----> -----------------------------------------------------
|
114
|
-
# ----> -----------------------------------------------------
|
115
|
-
# ----> ruby --version
|
116
|
-
# ----> ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
|
117
|
-
# ----> -----------------------------------------------------
|
118
|
-
# ----> -----------------------------------------------------
|
119
|
-
|
120
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
121
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
122
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
123
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
124
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
125
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
126
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
127
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
128
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
129
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
130
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
131
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
132
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
133
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
134
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
135
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
136
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
137
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
138
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
139
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
140
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
141
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
142
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
143
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
144
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
145
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
146
|
-
########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
|
147
|
-
|
148
|
-
pbkdf2_key = OpenSSL::KDF.pbkdf2_hmac(
|
157
|
+
pbkdf2_key = OpenSSL::PKCS5.pbkdf2_hmac(
|
149
158
|
human_secret,
|
150
|
-
|
159
|
+
binary_salt,
|
151
160
|
PBKDF2_ITERATION_COUNT,
|
152
|
-
|
153
|
-
OpenSSL::Digest::
|
161
|
+
PBKDF2_EXPORT_KEY_LENGTH,
|
162
|
+
OpenSSL::Digest::SHA384.new
|
154
163
|
)
|
155
164
|
|
156
|
-
return Key.
|
165
|
+
return Key.from_binary( pbkdf2_key )
|
157
166
|
|
158
167
|
end
|
159
168
|
|
@@ -0,0 +1,190 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module OpenKey
|
5
|
+
|
6
|
+
|
7
|
+
# SCrypt is a <b>Key Derivation Function (KDF)</b> with a reliable OpenSSL
|
8
|
+
# implementation that converts <b>low entropy</b> password-like text to a
|
9
|
+
# high entropy key that is computationally infeasible to acquire through brute
|
10
|
+
# force.
|
11
|
+
#
|
12
|
+
# SCrypt is incredibly resistant to attacks using dedicated hardware with
|
13
|
+
# massive memory to boot.
|
14
|
+
#
|
15
|
+
class KdfSCrypt
|
16
|
+
|
17
|
+
# SCrypt salts are recommended to contain 16 and 32 bytes
|
18
|
+
# inclusive. Here we opt for 24 bytes which unrolls out to
|
19
|
+
# 192 bits which serializes into 32 base64 characters.
|
20
|
+
SCRYPT_SALT_BYTE_LENGTH = 24
|
21
|
+
|
22
|
+
# The iteration count is determined using the powers of
|
23
|
+
# two so if the iteration integer is 12 there will be two
|
24
|
+
# to the power of 12 ( 2^12 ) giving 4096 iterations.
|
25
|
+
# The minimum number is 4 (16 iterations) and the max is 31.
|
26
|
+
# @example
|
27
|
+
# Configuring 16 into this directive results in
|
28
|
+
# 2^16 = 65,536 iterations
|
29
|
+
#
|
30
|
+
# This is a safe default and will slow the derivation time
|
31
|
+
# to about a second on a powerful 2020 laptop.
|
32
|
+
SCRYPT_ITERATION_INTEGER = 16
|
33
|
+
|
34
|
+
# The scrypt algorithm produces a key that is 181 bits in
|
35
|
+
# length. The algorithm then converts the binary 181 bits
|
36
|
+
# into a (6-bit) Radix64 character.
|
37
|
+
#
|
38
|
+
# 181 / 6 = 30 remainder 1 (so 31 characters are needed).
|
39
|
+
SCRYPT_KEY_LENGTH = 31
|
40
|
+
|
41
|
+
|
42
|
+
# When the key is transported using a 64 character set where
|
43
|
+
# each character is represented by 6 bits - the Scrypt key
|
44
|
+
# expands to 186 bits rather than the original 181 bits.
|
45
|
+
#
|
46
|
+
# This expansion is because of the remainder.
|
47
|
+
#
|
48
|
+
# 181 bits divided by 6 is 30 characters plus 1 character
|
49
|
+
# for the extra bit.
|
50
|
+
#
|
51
|
+
# The 31 transported characters then appear as
|
52
|
+
# 31 times 6 which equals 186 bits.
|
53
|
+
SCRYPT_KEY_TRANSPORT_LENGTH = 186
|
54
|
+
|
55
|
+
# The scrypt algorithm salt string should be 22 characters
|
56
|
+
# and may include forward slashes and periods.
|
57
|
+
SCRYPT_SALT_LENGTH = 22
|
58
|
+
|
59
|
+
# Scrypt outputs a single line of text that holds the prefix
|
60
|
+
# then the Radix64 encoded salt and finally the Radix64
|
61
|
+
# encoded hash key.
|
62
|
+
#
|
63
|
+
# The prefix consists of <b>two sections</b> sandwiched within
|
64
|
+
# two dollar <b>$</b> signs at the extremeties and a third dollar
|
65
|
+
# separating them.
|
66
|
+
#
|
67
|
+
# The two sections are the
|
68
|
+
# - Scrypt algorithm <b>version number</b> (2a or 2b) and
|
69
|
+
# - a power of 2 integer defining the no. of interations
|
70
|
+
SCRYPT_OUTPUT_TEXT_PREFIX = "$2a$#{SCRYPT_ITERATION_INTEGER}$"
|
71
|
+
|
72
|
+
|
73
|
+
# Generate a secure random and unpredictable salt suitable for
|
74
|
+
# the SCrypt algorithm. SCrypt salts are recommended to contain
|
75
|
+
# 16 and 32 bytes inclusive. Here we opt for 24 bytes which
|
76
|
+
# unrolls to 192 bits which in turn is 32 base64 characters.
|
77
|
+
#
|
78
|
+
# The {OpenKey::KdfSCrypt::SCRYPT_SALT_BYTE_LENGTH} constant
|
79
|
+
# defines the <b>number of random bytes</b> required for a robust
|
80
|
+
# SCrypt salt.
|
81
|
+
#
|
82
|
+
# The salt can be persisted and then resubmitted in order to
|
83
|
+
# regenerate the same key in the future.
|
84
|
+
#
|
85
|
+
# @return [String]
|
86
|
+
# the salt in a bit string format which can be converted to
|
87
|
+
# in order to feed the derivation function or indeed converted
|
88
|
+
# to base64 in order to persist it.
|
89
|
+
def self.generate_scrypt_salt
|
90
|
+
return Key.to_random_bits( SCRYPT_SALT_BYTE_LENGTH )
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
# Key generators should first use the {generate_salt} method to create
|
96
|
+
# a Scrypt salt string and then submit it to this method together with
|
97
|
+
# a human generated password in order to derive a key.
|
98
|
+
#
|
99
|
+
# The salt can be persisted and then resubmitted again to this method
|
100
|
+
# in order to regenerate the same key at any time in the future.
|
101
|
+
#
|
102
|
+
# Generate a binary key from the scrypt password derivation function.
|
103
|
+
#
|
104
|
+
# This differs from a server side password to hash usage in that we
|
105
|
+
# are interested in the 186bit key that scrypt produces. This method
|
106
|
+
# returns this reproducible key for use during symmetric encryption and
|
107
|
+
# decryption.
|
108
|
+
#
|
109
|
+
# @param secret_text [String]
|
110
|
+
# a robust human generated password with as much entropy as can
|
111
|
+
# be mustered. Remember that 40 characters spread randomly over
|
112
|
+
# the key space of about 90 characters and not relating to any
|
113
|
+
# dictionary word or name is the way to generate a powerful key
|
114
|
+
# that has embedded a near 100% entropy rating.
|
115
|
+
#
|
116
|
+
# @param scrypt_salt [String]
|
117
|
+
# the salt string that has either been recently generated via the
|
118
|
+
# {generate_salt} method or read from a persistence store and
|
119
|
+
# resubmitted here (in the future) to regenerate the same key.
|
120
|
+
#
|
121
|
+
# @return [Key]
|
122
|
+
# a key holder containing the key which can then be accessed via
|
123
|
+
# many different formats.
|
124
|
+
def self.generate_key secret_text, scrypt_salt
|
125
|
+
|
126
|
+
binary_salt = Key.to_binary_from_bit_string( scrypt_salt )
|
127
|
+
|
128
|
+
require "openssl"
|
129
|
+
|
130
|
+
puts ""
|
131
|
+
puts $LOADED_FEATURES.grep(/openssl/)
|
132
|
+
puts ""
|
133
|
+
|
134
|
+
scrypt_key = OpenSSL::KDF.scrypt(secret_text, salt: binary_salt, N: 2**SCRYPT_ITERATION_INTEGER, r: 8, p: 1, length: 33)
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
=begin
|
139
|
+
hashed_secret = Scrypt::Engine.hash_secret( secret_text, to_scrypt_salt(scrypt_salt) )
|
140
|
+
encoded64_key = Scrypt::Password.new( hashed_secret ).to_s
|
141
|
+
key_begin_index = SCRYPT_OUTPUT_TEXT_PREFIX.length + SCRYPT_SALT_LENGTH
|
142
|
+
radix64_key_str = encoded64_key[ key_begin_index .. -1 ]
|
143
|
+
key_length_mesg = "The scrypt key length should have #{SCRYPT_KEY_LENGTH} characters."
|
144
|
+
raise RuntimeError, key_length_mesg unless radix64_key_str.length == SCRYPT_KEY_LENGTH
|
145
|
+
|
146
|
+
return Key.new(radix64_key_str)
|
147
|
+
=end
|
148
|
+
return scrypt_key
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
|
156
|
+
def self.scrypt_test_method
|
157
|
+
|
158
|
+
puts ""
|
159
|
+
puts "##############################################################################"
|
160
|
+
|
161
|
+
key_count = 20
|
162
|
+
for n in 0 .. key_count
|
163
|
+
scrypt_saltbits = OpenKey::KdfSCrypt.generate_scrypt_salt
|
164
|
+
scrypt_key = OpenKey::KdfSCrypt.generate_key( "abonekanoby", scrypt_saltbits )
|
165
|
+
scrypt_saltchar = OpenKey::Key64.from_bits( scrypt_saltbits )
|
166
|
+
puts "#{n} Salt => #{scrypt_saltchar} (#{scrypt_saltchar.length}) => Key => #{scrypt_key} (#{scrypt_key.length})"
|
167
|
+
end
|
168
|
+
|
169
|
+
puts "##############################################################################"
|
170
|
+
puts ""
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
def self.to_scrypt_salt the_salt
|
177
|
+
return SCRYPT_OUTPUT_TEXT_PREFIX + the_salt
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.assert_scrypt_salt the_salt
|
181
|
+
raise RuntimeError, "scrypt salt not expected to be nil." if the_salt.nil?
|
182
|
+
salt_length_msg = "A scrypt salt is expected to contain #{SCRYPT_SALT_LENGTH} characters."
|
183
|
+
raise RuntimeError, salt_length_msg unless the_salt.length == SCRYPT_SALT_LENGTH
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module OpenKey
|
4
|
+
|
5
|
+
# First use the class methods to source keys, then use a key's instance
|
6
|
+
# methods to access its properties and in concert with other symmetrical
|
7
|
+
# information, you can use the keys to lock (encrypt) or unlock (decrypt)
|
8
|
+
# other keys and objects.
|
9
|
+
#
|
10
|
+
# == Sourcing and Deriving Keys
|
11
|
+
#
|
12
|
+
# Keys can be
|
13
|
+
#
|
14
|
+
# - sourced from a secure random byte generating function
|
15
|
+
# - sourced from ciphertext and another (decryption) key
|
16
|
+
# - generated by passing a secret through key derivation functions
|
17
|
+
# - regenerated from a secret and previously stored salts
|
18
|
+
# - sourced from the current unique workstation shell environment
|
19
|
+
# - sourced from an environment variable containing ciphertext
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# Keys need to be viewed (represented) in multiple ways and the essence
|
23
|
+
# of the key viewer is to input keys {as_bits}, {as_bytes} and {as_base64}
|
24
|
+
# and then output the same key (in as far as is possible) - as bits, as
|
25
|
+
# bytes and as base64.
|
26
|
+
#
|
27
|
+
# == Key | To and From Behaviour
|
28
|
+
#
|
29
|
+
# Use the <b>From</b> methods to create Keys from a variety of resources
|
30
|
+
# such as
|
31
|
+
#
|
32
|
+
# - a base64 encoded string
|
33
|
+
# - a binary byte string
|
34
|
+
# - a string of one and zero bits
|
35
|
+
# - a hexadecimal representation
|
36
|
+
#
|
37
|
+
# Once you have instantiated the key, you will then be able to convert it
|
38
|
+
# (within reason due to bit, byte and base64 lengths) to any of the above
|
39
|
+
# key representations.
|
40
|
+
#
|
41
|
+
# == Key | Bits Bytes and Base64
|
42
|
+
#
|
43
|
+
# The shoe doesn't always fit when its on the other foot and this is best
|
44
|
+
# illustratd with a table that maps bits to 8 bit bytes and 6 bit Base64
|
45
|
+
# characters.
|
46
|
+
#
|
47
|
+
# | --------- | -------- | ------------ | ------------------------------- |
|
48
|
+
# | Fit? | Bits | Bytes | (and) Base64 |
|
49
|
+
# | --------- | -------- | ------------ | ------------------------------- |
|
50
|
+
# | Perfect | 168 Bits | is 21 bytes | 28 Chars - bcrypt chops to this |
|
51
|
+
# | Perfect | 216 Bits | is 27 bytes | 36 Chars - |
|
52
|
+
# | Perfect | 264 Bits | is 33 bytes | 44 Chars - holder 4 256bit keys |
|
53
|
+
# | Perfect | 384 Bits | is 48 bytes | 64 Chars - 216 + 168 equals 384 |
|
54
|
+
# | --------- | -------- | ------------ | ------------------------------- |
|
55
|
+
# | Imperfect | 128 Bits | 16 precisely | 22 Chars - 21 + 2 remain bits |
|
56
|
+
# | Imperfect | 186 Bits | 23 remain 2 | 31 Characers precisely |
|
57
|
+
# | Imperfect | 256 Bits | 32 precisely | 43 Chars - 42 + 4 remain bits |
|
58
|
+
# | --------- | -------- | ------------ | ------------------------------- |
|
59
|
+
#
|
60
|
+
# Yes, the shoe doesn't always fit when it's on the other foot.
|
61
|
+
#
|
62
|
+
# == Schoolboy Error
|
63
|
+
#
|
64
|
+
# <b>The strategy is so simple, we call it a schoolboy error.</b>
|
65
|
+
#
|
66
|
+
# If we want to use a key with n bits and either n % 6 or n % 8 (or both)
|
67
|
+
# are not zero - <b>we instantiate a Key</b> with the lowest common
|
68
|
+
# denominator of 6 and 8 that exceeds n.
|
69
|
+
#
|
70
|
+
# So when we request a byte, or base64 representation the viewer will
|
71
|
+
# truncate (not round down) to the desired length.
|
72
|
+
#
|
73
|
+
#
|
74
|
+
# == YACHT 64 | Yet Another Character Table
|
75
|
+
#
|
76
|
+
# This binary key class is a dab hand at converting base64 strings
|
77
|
+
# into their 6-bit binary string equivalents.
|
78
|
+
#
|
79
|
+
# It can convert non-alphanumeric characters within either Base64 or
|
80
|
+
# Radix64 into the OpenKey YACHT64 standard which has a forward slash
|
81
|
+
# but neither a plus sign nor a period character.
|
82
|
+
#
|
83
|
+
# <b>The Big4 Character Sets | Base64 | UrlSafe64 | Radix64 | YACHT64</b>
|
84
|
+
#
|
85
|
+
# Base64 and Radix64 (from OpenBSD) differ in both the order of characters
|
86
|
+
# and their choice of the two non-alphanumeric characters. Base64 can also
|
87
|
+
# contain line breaks and equal signs for padding. UrlSafe base64 has different
|
88
|
+
# choices for the two non alphanum characters in keeping with URL standards.
|
89
|
+
#
|
90
|
+
# The character sets for each of the four 64 fomats are as follows.
|
91
|
+
#
|
92
|
+
# - Base-64 is <b>A to Z</b> then <b>a to z</b> then <b>0 to 9</b> then <b>+</b> then <b>/</b>
|
93
|
+
# - Radix64 is <b>.</b> then <b>/</b> then <b>0 to 9</b> then <b>A to Z</b> then <b>a to z</b>
|
94
|
+
# - UrlSafeBase64 is Base64 but chars 63/64 are an <b>underscore (_)</b> and <b>hyphen (-)</b>
|
95
|
+
# - UrlSafeBase64 <b>does not have line breaks and carriage returns</b> (unlike Base64)
|
96
|
+
# - <b>OpenKey 64 (YACHT64)</b> uses the same 62 characters plus an @ sign and a forward slash
|
97
|
+
# - The 64 <b>OpenKey 64</b> characters are <b>obfuscated into a random order</b>
|
98
|
+
#
|
99
|
+
# == 4 Non-AlphaNumerics | Base64 | Radix64 | YACHT64
|
100
|
+
#
|
101
|
+
# The behaviour here is happy to convert base64 strings produced by either
|
102
|
+
# Radix64 or Base64 or UrlSafe Base64. Howeverr it aware of the
|
103
|
+
# <b>non alpha-numeric characters</b> and converts them before processing
|
104
|
+
# with the modus operandi that says
|
105
|
+
#
|
106
|
+
# - ignore the forward slash in <b>YACHT64, Base64 and Radix64</b>
|
107
|
+
# - convert the <b>plus (+)</b> in Base64 to the <b>@ symbol</b> in YACHT64
|
108
|
+
# - convert the <b>period (.)</b> in <b>Radix64</b> to the @ symbol in YACHT64
|
109
|
+
# - convert <b>hyphen (-)</b> in <b>Url Safe Base64</b> into a fwd slash
|
110
|
+
# - convert <b>underscore (_)</b> in <b>Url Safe Base64</b> to an @ sign
|
111
|
+
# - <b>delete the (=) equals</b> padding character used by Base64
|
112
|
+
#
|
113
|
+
# Neither the OpenBSD backed Radix64 nor the OpenKey (YACHT64) entertain the
|
114
|
+
# concept of padding.
|
115
|
+
#
|
116
|
+
# == Mapping Each Character to 6 Binary Bits
|
117
|
+
#
|
118
|
+
# We need 6 binary bits to represent a base64 character (and 4
|
119
|
+
# bits for hexadecimal). Here is an example mapping between
|
120
|
+
# a base 64 character, an integer and the six bit binary.
|
121
|
+
#
|
122
|
+
# Character Integer Binary (6 Bit)
|
123
|
+
#
|
124
|
+
# a 0 000000
|
125
|
+
# b 1 000001
|
126
|
+
# c 2 000010
|
127
|
+
#
|
128
|
+
# y 25 011001
|
129
|
+
# z 26 011010
|
130
|
+
# A 27 011011
|
131
|
+
# B 28 011100
|
132
|
+
#
|
133
|
+
# 8 60 111100
|
134
|
+
# 9 61 111101
|
135
|
+
# / 62 111110
|
136
|
+
# + 63 111111
|
137
|
+
#
|
138
|
+
class Key64
|
139
|
+
|
140
|
+
# YACHT stands for <b>Yet Another Character Table</b> and it
|
141
|
+
# can map binary sequences onto 64 well chosen characters.
|
142
|
+
#
|
143
|
+
# The 64 character sets are all similar in that they hold 64
|
144
|
+
# characters and they define two non alphanumeric characters
|
145
|
+
# because the 26 lowercase, 26 uppercase and 10 digits only
|
146
|
+
# adds up to an <b><em>agonisingly close</em></b> 62 characters.
|
147
|
+
#
|
148
|
+
YACHT64_CHARACTER_SET = [
|
149
|
+
"a", "9", "W", "B", "f", "K", "O", "z",
|
150
|
+
"3", "s", "1", "5", "c", "n", "E", "J",
|
151
|
+
"L", "A", "l", "6", "I", "w", "o", "g",
|
152
|
+
"k", "N", "t", "Y", "S", "%", "T", "b",
|
153
|
+
"V", "R", "H", "0", "@", "Z", "8", "F",
|
154
|
+
"G", "j", "u", "m", "M", "h", "4", "p",
|
155
|
+
"q", "d", "7", "v", "e", "2", "U", "X",
|
156
|
+
"r", "C", "y", "Q", "D", "x", "P", "i"
|
157
|
+
]
|
158
|
+
|
159
|
+
|
160
|
+
# Radix64 strings can contain period characters in their midst.
|
161
|
+
PERIOD = "."
|
162
|
+
|
163
|
+
# Radix64 strings can contain forward slashes in their midst.
|
164
|
+
FORWARD_SLASH = "/"
|
165
|
+
|
166
|
+
# YACHT64 strings can contain at symbols in their midst.
|
167
|
+
AT_SYMBOL = "@"
|
168
|
+
|
169
|
+
# YACHT64 strings can contain percent signs in their midst.
|
170
|
+
PERCENT_SIGN = "%"
|
171
|
+
|
172
|
+
|
173
|
+
# Convert the parameter string of ones and zeroes into an
|
174
|
+
# internal base64 character set known as YACHT for yet another
|
175
|
+
# character table.
|
176
|
+
#
|
177
|
+
# @param bit_string [String]
|
178
|
+
# a string of ones and zeroes that can be sliced into
|
179
|
+
# six character chunks with each chunk then being mapped
|
180
|
+
# to a YACHT64 character.
|
181
|
+
#
|
182
|
+
# @return [String]
|
183
|
+
# printable characters from a set of 62 alpha-numerics
|
184
|
+
# plus an @ symbol and a percent % sign.
|
185
|
+
#
|
186
|
+
# @raise ArgumentError
|
187
|
+
# If the bit string is nil.
|
188
|
+
# Or if the bit string length is not a multiple of six.
|
189
|
+
# Or if it contains any character that is not a 1 or 0.
|
190
|
+
def self.from_bits bit_string
|
191
|
+
|
192
|
+
nil_err_msg = "The parameter bit string cannot be nil."
|
193
|
+
raise ArgumentError, nil_err_msg if bit_string.nil?
|
194
|
+
|
195
|
+
bit_size_msg = "The bit string length is not a multiple of #{SIX}."
|
196
|
+
raise ArgumentError, bit_size_msg unless bit_string.length % SIX == 0
|
197
|
+
|
198
|
+
num_unknowns = bit_string.delete("10").length
|
199
|
+
unknowns_msg = "The bit string has #{num_unknowns} characters that are not 1 or 0."
|
200
|
+
raise ArgumentError, unknowns_msg if num_unknowns > 0
|
201
|
+
|
202
|
+
characters64 = ""
|
203
|
+
char_count = bit_string.length / SIX
|
204
|
+
for n in 0 .. (char_count-1)
|
205
|
+
six_bit_chunk = bit_string[ (n*SIX), SIX ]
|
206
|
+
six_bit_index = six_bit_chunk.to_i(2)
|
207
|
+
characters64 += Key64.character(six_bit_index)
|
208
|
+
end
|
209
|
+
|
210
|
+
code_size_msg = "Length is #{characters64.length} but #{char_count} is expected."
|
211
|
+
raise RuntimeError, code_size_msg unless characters64.length == char_count
|
212
|
+
|
213
|
+
return characters64
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
# Convert the parameter characters based on an internal base64
|
219
|
+
# character set (known as YACHT) into a <b>bit string</b> of ones
|
220
|
+
# and zeroes.
|
221
|
+
#
|
222
|
+
# @param char64_string [String]
|
223
|
+
# The base64 character sequence which which will be used to
|
224
|
+
# derive the returned bit string. Naturally this character
|
225
|
+
# sequencee cannot be nil, nor can it contain any characters
|
226
|
+
# that are not present in {Key64::YACHT64_CHARACTER_SET}.
|
227
|
+
#
|
228
|
+
# @return [String]
|
229
|
+
# a string of ones and zeroes that have been strung out
|
230
|
+
# from each YACHT64 character. The returned string length of
|
231
|
+
# ones and zeroes will be exactly 6 times the length of the
|
232
|
+
# input parameter.
|
233
|
+
#
|
234
|
+
# @raise [ArgumentError]
|
235
|
+
# If a nil or zero length character string is received.
|
236
|
+
# Or if the character sequence contains a character not present
|
237
|
+
# in the {Key64::YACHT64_CHARACTER_SET}.
|
238
|
+
#
|
239
|
+
# @raise [RuntimeError]
|
240
|
+
# if the conversion does not result in 6 bits for every character
|
241
|
+
# in the parameter string.
|
242
|
+
def self.to_bits char64_string
|
243
|
+
|
244
|
+
bit_string = ""
|
245
|
+
char64_string.each_char do |the_char|
|
246
|
+
|
247
|
+
yacht64_index = YACHT64_CHARACTER_SET.index(the_char)
|
248
|
+
assert_yacht64_index( the_char, yacht64_index )
|
249
|
+
bit_string += "%06d" % [ yacht64_index.to_s(2) ]
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
assert_bit_lengths char64_string, bit_string
|
254
|
+
return bit_string
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# Convert a string of Radix64 characters into a bit representation which
|
260
|
+
# will be 6 times longer than the input parameter. This method first
|
261
|
+
# converts the string into the internal YACHT64 format and then converts
|
262
|
+
# that to a bit string using the {Key64.to_bits} method.
|
263
|
+
#
|
264
|
+
# @param radix64_string [String]
|
265
|
+
# the radix64 string to convert into bits. This string will be a subset
|
266
|
+
# of the usual 62 character suspects together with period and forward
|
267
|
+
# slash characters.
|
268
|
+
#
|
269
|
+
# This parameter should not contain newlines nor carriage returns.
|
270
|
+
#
|
271
|
+
# @return [String]
|
272
|
+
# a string of ones and zeroes that represent the bits converted from the
|
273
|
+
# radix64 input. The return value will be exactly 6 times the number of
|
274
|
+
# input characters.
|
275
|
+
def self.from_radix64_to_bits radix64_string
|
276
|
+
|
277
|
+
yacht64_chars = radix64_string.gsub( PERIOD, AT_SYMBOL ).gsub( FORWARD_SLASH, PERCENT_SIGN )
|
278
|
+
out_bitstring = to_bits( yacht64_chars )
|
279
|
+
assert_bit_lengths( radix64_string, out_bitstring )
|
280
|
+
return out_bitstring
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
|
286
|
+
private
|
287
|
+
|
288
|
+
|
289
|
+
|
290
|
+
SIX = 6
|
291
|
+
|
292
|
+
def self.character char_index
|
293
|
+
|
294
|
+
index_oob_msg = "The character index must be between 0 and 63 inclusive."
|
295
|
+
index_is_oob = char_index < 0 || char_index > 63
|
296
|
+
raise ArgumentError, index_oob_msg if index_is_oob
|
297
|
+
return YACHT64_CHARACTER_SET[ char_index ]
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
def self.assert_bit_lengths( in_string, out_string )
|
302
|
+
|
303
|
+
in_length = in_string.length
|
304
|
+
out_length = out_string.length
|
305
|
+
good_ratio = out_length == in_length * SIX
|
306
|
+
size_msg = "Bit string length [#{out_length}] not 6 times more than [#{in_length}]."
|
307
|
+
raise RuntimeError, size_msg unless good_ratio
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.assert_yacht64_index the_char, yacht64_index
|
312
|
+
|
313
|
+
nil_msg = "Character [ #{the_char} ] not in YACHT character set."
|
314
|
+
raise ArgumentError, nil_msg if yacht64_index.nil?
|
315
|
+
|
316
|
+
index_msg = "Index of character [ #{the_char} ] not within expected bounds."
|
317
|
+
all_good = ( yacht64_index >= 0 ) && ( yacht64_index <= 63 )
|
318
|
+
raise ArgumentError, index_msg unless all_good
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
end
|