opensecret 0.0.962 → 0.0.988

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -10
  3. data/bin/opensecret +3 -4
  4. data/bin/ops +5 -0
  5. data/lib/extension/string.rb +114 -0
  6. data/lib/factbase/facts.opensecret.io.ini +9 -21
  7. data/lib/interprete/begin.rb +232 -0
  8. data/lib/interprete/cmd.rb +621 -0
  9. data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
  10. data/lib/interprete/init.rb +205 -0
  11. data/lib/interprete/key.rb +119 -0
  12. data/lib/interprete/open.rb +148 -0
  13. data/lib/{plugins/usecases → interprete}/put.rb +19 -6
  14. data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
  15. data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
  16. data/lib/interprete/set.rb +46 -0
  17. data/lib/interprete/use.rb +43 -0
  18. data/lib/interpreter.rb +165 -0
  19. data/lib/keytools/binary.map.rb +245 -0
  20. data/lib/keytools/digester.rb +245 -0
  21. data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
  22. data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
  23. data/lib/keytools/doc.star.schema.strategy.txt +77 -0
  24. data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
  25. data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
  26. data/lib/keytools/kdf.bcrypt.rb +180 -0
  27. data/lib/keytools/kdf.pbkdf2.rb +164 -0
  28. data/lib/keytools/key.data.rb +227 -0
  29. data/lib/keytools/key.derivation.rb +341 -0
  30. data/lib/keytools/key.module.rb +140 -0
  31. data/lib/keytools/key.rb +481 -0
  32. data/lib/logging/gem.logging.rb +1 -2
  33. data/lib/modules/cryptology.md +43 -0
  34. data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
  35. data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
  36. data/lib/modules/cryptology/blowfish.rb +130 -0
  37. data/lib/modules/cryptology/cipher.rb +207 -0
  38. data/lib/modules/cryptology/collect.rb +118 -0
  39. data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
  40. data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
  41. data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
  42. data/lib/modules/mappers/collateral.rb +282 -0
  43. data/lib/modules/mappers/dictionary.rb +288 -0
  44. data/lib/modules/mappers/envelope.rb +127 -0
  45. data/lib/modules/mappers/settings.rb +170 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
  48. data/lib/notepad/scratch.pad.rb +17 -0
  49. data/lib/session/fact.finder.rb +13 -0
  50. data/lib/session/require.gem.rb +5 -0
  51. data/lib/store-commands.txt +180 -0
  52. data/lib/version.rb +1 -1
  53. data/opensecret.gemspec +5 -6
  54. metadata +74 -29
  55. data/lib/crypto/blowfish.rb +0 -85
  56. data/lib/crypto/collect.rb +0 -140
  57. data/lib/crypto/verify.rb +0 -33
  58. data/lib/opensecret.rb +0 -236
  59. data/lib/plugins/cipher.rb +0 -203
  60. data/lib/plugins/ciphers/blowfish.rb +0 -126
  61. data/lib/plugins/coldstore.rb +0 -181
  62. data/lib/plugins/envelope.rb +0 -116
  63. data/lib/plugins/secrets.uc.rb +0 -94
  64. data/lib/plugins/usecase.rb +0 -239
  65. data/lib/plugins/usecases/init.rb +0 -145
  66. data/lib/plugins/usecases/open.rb +0 -108
  67. data/lib/session/attributes.rb +0 -279
  68. data/lib/session/dictionary.rb +0 -191
  69. data/lib/session/file.path.rb +0 -53
  70. data/lib/session/session.rb +0 -80
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # The open key library generates keys, it stores their salts, it produces differing
4
+ # representations of the keys (like base64 for storage and binary for encrypting).
5
+ #
6
+ # == Key Class Names their and Responsibility
7
+ #
8
+ # The 5 core key classes in the open key library are
9
+ #
10
+ # - {Key} represents keys in bits, binary and base 64 formats
11
+ # - {Key64} for converting from to and between base 64 characters
12
+ # - {Key256} uses key derivation functions to produce high entropy keys
13
+ # - {KeyIO} reads and writes key metadata (like salts) from/to persistent storage
14
+ # - {KeyCycle} for creating and locking the keys that underpin the security
15
+ #
16
+ # == The 5 Core Key Classes
17
+ #
18
+ # Key To initialize with a 264 binary bit string. To hold the
19
+ # key and represent it when requested
20
+ # - as a 264 bit binary bit string
21
+ # - as a 256 bit binary bit string
22
+ # - as a 256 bit raw bytes encryption key
23
+ # - as a YACHT64 formatted string
24
+ #
25
+ # Key64 To map in and out of the Yacht64 character set - from and to
26
+ # - a binary bit string sequence
27
+ # - a Base64 character encoding
28
+ # - a UrlSafe Base64 character encoding
29
+ # - a Radix64 character encoding
30
+ #
31
+ # Key256 It generates a key in 3 different and important ways. It can
32
+ # generate
33
+ #
34
+ # (a) from_password
35
+ # (b) from_random (or it can)
36
+ # (c) regenerate
37
+ #
38
+ # When generating from a password it takes a dictionary with
39
+ # a pre-tailored "section" and writes BCrypt and Pbkdf2 salts
40
+ # into it.
41
+ #
42
+ # When generating random it kicks of by creating a 55 byte
43
+ # random key fo BCrypt and a 64 byte random key for Pbkdf2.
44
+ # It then calls upon generate_from_password.
45
+ #
46
+ # When regenerating it queries the dictionary provided at the
47
+ # pre-tailored "section" for the BCrypt and Pbkdf2 salts and
48
+ # then uses input passwords (be they human randomly sourced)
49
+ # and regenerates the keys it produced at an earlier sitting.
50
+ #
51
+ # KeyIO KeyIO is instantiated with a folder path and a "key reference".
52
+ # KeyIO will then manage writing to and rereading from the structure
53
+ # hel inside th efile. The file is named (primarily) by the
54
+ # reference string.
55
+ #
56
+ # KeyCycle KeyLifeCycle implements the merry go round that palms off
57
+ # responsibility to the intra-session cycle and then back again
58
+ # to ever rotary inter-session(ary) cycle.
59
+ ########### Maybe think of a method where we pass in
60
+ ########### 2 secrets - 1 human and 1 55 random bytes (session)
61
+ ###########
62
+ ########### 1 another 55 random key is created (the actual encryption key)
63
+ ########### 2 then the above key is encrypted TWICE (2 diff salts and keys)
64
+ ########### 3 Once by key from human password
65
+ ########### 4 Once by key from machine password
66
+ ########### 5 then the key from 1 is returned
67
+ ########### 6 caller encrypts file .................... (go 4 it)
68
+
69
+
70
+ # Generates a 256 bit symmetric encryption key derived from a random
71
+ # seed sequence of 55 bytes. These 55 bytes are then fed into the
72
+ # {from_password} key derivation function and processed in a similar
73
+ # way to if a human had generated the string.
74
+ #
75
+
76
+
77
+ # <b>Key derivation functions</b> exist to convert <b>low entropy</b> human
78
+ # created passwords into a high entropy key that is computationally difficult
79
+ # to acquire through brute force.
80
+ #
81
+ # == OpenSecret's Underlying Security Strategy
82
+ #
83
+ # <b>Randomly generate a 256 bit encryption key and encrypt it</b> with a key
84
+ # derived from a human password and generated by at least two cryptographic
85
+ # workhorses known as <b>key derivation functions</b>.
86
+ #
87
+ # The encryption key (encrypted by the one derived from a human password) sits
88
+ # at the beginning of a long chain of keys and encryption - so much so that the
89
+ # crypt material being outputted for storage is all but worthless to anyone but
90
+ # its rightful owner.
91
+ #
92
+ # == Key Size vs Crack Time
93
+ #
94
+ # Cracking a 256 bit key would need roughly 2^255 iterations (half the space)
95
+ # and this is akin to the number of atoms in the known universe.
96
+ #
97
+ # <b>The human key can put security at risk.</b>
98
+ #
99
+ # The rule of thumb is that a 40 character password with a good spread of the
100
+ # roughly 90 typable characters, would produce security equivalent to that of
101
+ # an AES 256bit key. As the password size and entropy drop, so does the security,
102
+ # exponentially.
103
+ #
104
+ # As human generated passwords have a relatively small key space, key derivation
105
+ # functions must be slow to compute with any implementation.
106
+ #
107
+ # == Key Derivation Functions for Command Line Apps
108
+ #
109
+ # A command line app (with no recourse to a central server) uses a Key
110
+ # Derivation Function (like BCrypt, Aaron2 or PBKD2) in a manner different
111
+ # to that employed by server side software.
112
+ #
113
+ # - server side passwords are hashed then both salt and hash are persisted
114
+ # - command line apps do not store the key - they only store the salt
115
+ # - both throw away the original password
116
+ #
117
+ # == One Key | One Session | One Crypt
118
+ #
119
+ # Command line apps use the derived key to <b>symmetrically encrypt and decrypt</b>
120
+ # one and only one 48 character key and a new key is derived at the beginning
121
+ # of every session.
122
+ #
123
+ # At the end of the session <b>all material encrypted by the outgoing key</b>
124
+ # is removed. This aggressive key rotation strategy leaves no stone unturned in
125
+ # the quest for ultimate security.
126
+ #
127
+ # == OpenSecret's CLI Key Derivation Architecture
128
+ #
129
+ # OpenSecret never accesses another server and giving its users total control
130
+ # of their secret crypted materials. It strengthens the key derivation process
131
+ # in three important ways.
132
+ #
133
+ # - [1] it does not store the key nor does it store the password
134
+ #
135
+ # - [2] a new master key is generated for every session only to hold the master index file
136
+ #
137
+ # - [3] it uses both <b>BCrypt</b> (Blowfish Crypt) and the indefatigable <b>PBKD2</b>
138
+ module OpenKey
139
+
140
+ end
@@ -0,0 +1,481 @@
1
+ #!/usr/bin/ruby
2
+
3
+ module OpenKey
4
+
5
+ # Keys need to be viewed (represented) in multiple ways and the essence
6
+ # of the key viewer is to input keys {as_bits}, {as_bytes} and {as_base64}
7
+ # and then output the same key (in as far as is possible) - as bits, as
8
+ # bytes and as base64.
9
+ #
10
+ # == Key | To and From Behaviour
11
+ #
12
+ # Use the <b>From</b> methods to create Keys from a variety of resources
13
+ # such as
14
+ #
15
+ # - a base64 encoded string
16
+ # - a binary byte string
17
+ # - a string of one and zero bits
18
+ # - a hexadecimal representation
19
+ #
20
+ # Once you have instantiated the key, you will then be able to convert it
21
+ # (within reason due to bit, byte and base64 lengths) to any of the above
22
+ # key representations.
23
+ #
24
+ # == Key | Bits Bytes and Base64
25
+ #
26
+ # The shoe doesn't always fit when its on the other foot and this is best
27
+ # illustratd with a table that maps bits to 8 bit bytes and 6 bit Base64
28
+ # characters.
29
+ #
30
+ # | --------- | -------- | ------------ | ------------------------------- |
31
+ # | Fit? | Bits | Bytes | (and) Base64 |
32
+ # | --------- | -------- | ------------ | ------------------------------- |
33
+ # | Perfect | 168 Bits | is 21 bytes | 28 Chars - bcrypt chops to this |
34
+ # | Perfect | 216 Bits | is 27 bytes | 36 Chars - |
35
+ # | Perfect | 264 Bits | is 33 bytes | 44 Chars - holder 4 256bit keys |
36
+ # | Perfect | 384 Bits | is 48 bytes | 64 Chars - 216 + 168 equals 384 |
37
+ # | --------- | -------- | ------------ | ------------------------------- |
38
+ # | Imperfect | 128 Bits | 16 precisely | 22 Chars - 21 + 2 remain bits |
39
+ # | Imperfect | 186 Bits | 23 remain 2 | 31 Characers precisely |
40
+ # | Imperfect | 256 Bits | 32 precisely | 43 Chars - 42 + 4 remain bits |
41
+ # | --------- | -------- | ------------ | ------------------------------- |
42
+ #
43
+ # Yes, the shoe doesn't always fit when it's on the other foot.
44
+ #
45
+ # == Schoolboy Error
46
+ #
47
+ # <b>The strategy is so simple, we call it a schoolboy error.</b>
48
+ #
49
+ # If we want to use a key with n bits and either n % 6 or n % 8 (or both)
50
+ # are not zero - <b>we instantiate a Key</b> with the lowest common
51
+ # denominator of 6 and 8 that exceeds n.
52
+ #
53
+ # So when we request a byte, or base64 representation the viewer will
54
+ # truncate (not round down) to the desired length.
55
+ #
56
+ #
57
+ # == YACHT 64 | Yet Another Character Table
58
+ #
59
+ # This binary key class is a dab hand at converting base64 strings
60
+ # into their 6-bit binary string equivalents.
61
+ #
62
+ # It can convert non-alphanumeric characters within either Base64 or
63
+ # Radix64 into the OpenKey YACHT64 standard which has a forward slash
64
+ # but neither a plus sign nor a period character.
65
+ #
66
+ # == Character Order | Base64 | UrlSafe64 | Radix64 | YACHT64
67
+ #
68
+ # The character sets for each of he four 64 fomats are as follows.
69
+ #
70
+ # - 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>
71
+ # - 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>
72
+ # - UrlSafeBase64 is Base64 but chars 63/64 are an <b>underscore (_)</b> and <b>hyphen (-)</b>
73
+ # - UrlSafeBase64 <b>does not have line breaks and carriage returns</b> (unlike Base64)
74
+ # - <b>OpenKey 64 (YACHT64)</b> uses the same 62 characters plus an @ sign and a forward slash
75
+ # - The 64 <b>OpenKey 64</b> characters are <b>obfuscated into a random order</b>
76
+ #
77
+ # <b>Why Order Doesn't Matter</b>
78
+ #
79
+ # Order doesn't matter if string of bits is used in key creation
80
+ # as long as this class is employed to do all the necessary
81
+ # conversions.
82
+ #
83
+ # Base64 and Radix64 (outputted by the OpenBSD inspired bcrypt)
84
+ # differ in both the order of characters and their choice of the
85
+ # two non-alphanumeric characters.
86
+ #
87
+ # == 4 Non-AlphaNumerics | Base64 | Radix64 | YACHT64
88
+ #
89
+ # The behaviour here is happy to convert base64 strings produced by either
90
+ # Radix64 or Base64 or UrlSafe Base64. Howeverr it aware of the
91
+ # <b>non alpha-numeric characters</b> and converts them before processing
92
+ # with the modus operandi that says
93
+ #
94
+ # - ignore the forward slash in <b>YACHT64, Base64 and Radix64</b>
95
+ # - convert the <b>plus (+)</b> in Base64 to the <b>@ symbol</b> in YACHT64
96
+ # - convert the <b>period (.)</b> in <b>Radix64</b> to the @ symbol in YACHT64
97
+ # - convert <b>hyphen (-)</b> in <b>Url Safe Base64</b> into a fwd slash
98
+ # - convert <b>underscore (_)</b> in <b>Url Safe Base64</b> to an @ sign
99
+ # - <b>delete the (=) equals</b> padding character used by Base64
100
+ #
101
+ # Neither the OpenBSD backed Radix64 nor the OpenKey (YACHT64) entertain the
102
+ # concept of padding.
103
+ #
104
+ # == Mapping Each Character to 6 Binary Bits
105
+ #
106
+ # We need 6 binary bits to represent a base64 character (and 4
107
+ # bits for hexadecimal). Here is an example mapping between
108
+ # a base 64 character, an integer and the six bit binary.
109
+ #
110
+ # Character Integer Binary (6 Bit)
111
+ #
112
+ # a 0 000000
113
+ # b 1 000001
114
+ # c 2 000010
115
+ #
116
+ # y 25 011001
117
+ # z 26 011010
118
+ # A 27 011011
119
+ # B 28 011100
120
+ #
121
+ # 8 60 111100
122
+ # 9 61 111101
123
+ # / 62 111110
124
+ # + 63 111111
125
+ #
126
+ class Key
127
+
128
+ # The internal YACHT64 OpenKey character set that can handle
129
+ # conversion from either Radix64 or Base64. The 64 character
130
+ # sets are all similar in that they hold 64 characters and
131
+ # they define two non alphanumeric characters because the
132
+ # 26 lowercase, 26 uppercase and 10 digits only adds up to
133
+ # an agonising close 62 characters.
134
+ YACHT64_CHARACTER_SET = [
135
+ "a", "9", "W", "B", "f", "K", "O", "z",
136
+ "3", "s", "1", "5", "c", "n", "E", "J",
137
+ "L", "A", "l", "6", "I", "w", "o", "g",
138
+ "k", "N", "t", "Y", "S", "%", "T", "b",
139
+ "V", "R", "H", "0", "@", "Z", "8", "F",
140
+ "G", "j", "u", "m", "M", "h", "4", "p",
141
+ "q", "d", "7", "v", "e", "2", "U", "X",
142
+ "r", "C", "y", "Q", "D", "x", "P", "i"
143
+ ]
144
+
145
+
146
+ # Initialize an n bit binary key (of literally ones and zeroes)
147
+ # from the base64 represented string in the parameter.
148
+ #
149
+ # This method can convert either UNIX <b>Radix 64</b> or
150
+ # <b>Base 64</b> into a <b>string of ones and zeroes</b>.
151
+ #
152
+ # The difference between Radix64 and Base64 is the ordering
153
+ # of the characters and the choice of the two non alpha-numeric
154
+ # (63rd and 64th or 1st and 2nd) characters.
155
+ #
156
+ # @param base64_string [String]
157
+ # the either Base64 or (unix) Radix 64 encoded string
158
+ #
159
+ # @raise [ArgumentError]
160
+ # if the parameter string contains characters that are not
161
+ # recognized as belonging to either the Base64 or the Radix64
162
+ # character sets. See {OpenKey::Key::YACHT64_CHARACTER_SET} constant.
163
+ #
164
+ # @raise [RuntimeError]
165
+ # if the conversion does not result in 6 bits for every character
166
+ # in the parameter string.
167
+ def self.from_base64 the_base64_string
168
+
169
+ new_key = Key.new
170
+ new_key.instantiate_from_base64 the_base64_string
171
+ return new_key
172
+
173
+ end
174
+
175
+
176
+ def instantiate_from_base64 base64_string
177
+
178
+ @original64_string = replace_yacht64( base64_string )
179
+
180
+ @binary_bit_string = ""
181
+ @original64_string.each_char do |the_char|
182
+
183
+ yacht64_index = YACHT64_CHARACTER_SET.index(the_char)
184
+
185
+ nil_msg = "Character [ #{the_char} ] is not in the YACHT64 table."
186
+ raise ArgumentError, nil_msg if yacht64_index.nil?
187
+
188
+ index_msg = "yacht64 index should run between 0 and 63 inclusive."
189
+ all_good = ( yacht64_index >= 0 ) && ( yacht64_index <= 63 )
190
+ raise ArgumentError, index_msg unless all_good
191
+
192
+ @binary_bit_string += "%06d" % [ yacht64_index.to_s(2) ]
193
+
194
+ end
195
+
196
+ assert_in_out_lengths @original64_string, @binary_bit_string
197
+
198
+ end
199
+
200
+
201
+
202
+ def self.from_byte_array the_byte_array
203
+
204
+ new_key = Key.new
205
+ new_key.instantiate_from_base64( Base64.urlsafe_encode64( the_byte_array ) )
206
+ return new_key
207
+
208
+ end
209
+
210
+
211
+
212
+ # Retrieve a cryptographically strong key that is 64 encoded
213
+ # with a bit (not byte) count specified by the parameter.
214
+ #
215
+ # <b>Bit Count must be Multiple of 24</b>
216
+ #
217
+ # The bit count parameter must be divisible by both 3 and 4
218
+ # (thus 24) so that it can be represented by a whole number
219
+ # of 8-bit bytes and a whole number of 6-bit base64 encoding
220
+ # characters.
221
+ #
222
+ # If this were not the case the leftover bits could not strictly
223
+ # be called "random" and could change in value when converted
224
+ # from one representation to the other.
225
+ #
226
+ # @param bit_count [Fixnum]
227
+ # the number of bits divisable by 24 (both 3 and 4) so
228
+ # that it can be represented by a whole number of 8-bit
229
+ # bytes and a whole number of 6-bit base64 characters.
230
+ #
231
+ # @raise [ArgumentError]
232
+ # if bit count parameter is not divisible by both 3 and 4
233
+ def self.to_random_yacht64 bit_count
234
+
235
+ mod24_msg = "The bit count of #{bit_count} is not divisable by 24."
236
+ raise ArgumentError, mod24_msg unless bit_count % 24 == 0
237
+
238
+ num_6bit_blocks = bit_count / 6
239
+ random64_string = SecureRandom.base64( num_6bit_blocks + 4 )
240
+ perfect_rand_64 = random64_string[ 0 .. num_6bit_blocks ]
241
+ length_64string = perfect_rand_64.length
242
+
243
+ length_msg = "Expected [#{num_6bit_blocks}] characters not #{length_64string}."
244
+ raise RuntimeError, length_msg unless length_64string == num_6bit_blocks
245
+
246
+ ##################### return to_yacht64( perfect_rand_64 )
247
+
248
+ end
249
+
250
+
251
+ # Return a representation of this key in YACHT64 format which is
252
+ # simply <b>Yet Another Coding Hieroglyphics-like Table (YACHT).
253
+ #
254
+ # This new format can suck in RADIX64 as well as Base64 and is
255
+ # safe for transportation with carriers such as
256
+ #
257
+ # - URLs
258
+ # - <b>one line text</b> (without newlines and carriage returns)
259
+ # - <b>environment variables</b>
260
+ # - INI, JSON, YAML and XML files
261
+ #
262
+ # <b>Character Order | Base64 | UrlSafe64 | Radix64 | YACHT64</b>
263
+ #
264
+ # The character sets for each of he four 64 fomats are as follows.
265
+ #
266
+ # - 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>
267
+ # - 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>
268
+ # - UrlSafeBase64 is Base64 but chars 63/64 are an <b>underscore (_)</b> and <b>hyphen (-)</b>
269
+ # - UrlSafeBase64 <b>does not have line breaks and carriage returns</b> (unlike Base64)
270
+ # - <b>OpenKey 64 (YACHT64)</b> uses the same 62 characters plus an @ sign and a forward slash
271
+ # - The 64 <b>OpenKey 64</b> characters are <b>obfuscated into a random order</b>
272
+ #
273
+ # <b>Why Order Doesn't Matter</b>
274
+ #
275
+ # Order doesn't matter if string of bits is used in key creation
276
+ # as long as this class is employed to do all the necessary
277
+ # conversions.
278
+ #
279
+ # Base64 and Radix64 (outputted by the OpenBSD inspired bcrypt)
280
+ # differ in both the order of characters and their choice of the
281
+ # two non-alphanumeric characters.
282
+ #
283
+ # @return [String]
284
+ # Return the YACHT64 formatted textual string in a single line. This
285
+ # string will consist of the 62 alpha-numeric characters and perhaps
286
+ # an @ sign and a forward slash (/).
287
+ #
288
+ # @raise [RuntimeError]
289
+ # raise an error if this class was initialized with a character that
290
+ # was not recognised. Not withstanding the difference in non-whitespace
291
+ # characters like carriage returns and newlines, an error is thrown
292
+ # if the length of the original base64 and the output YACHT64 string are
293
+ # not exactly the same length.
294
+ def to_yacht64
295
+
296
+ almost_base64 = replace_yacht64( @original64_string )
297
+ the_yacht64_encoding = ""
298
+
299
+ almost_base64.each_char do |the_char|
300
+ yacht64_index = YACHT64_CHARACTER_SET.index(the_char)
301
+ assert_yacht64_index yacht64_index
302
+ the_yacht64_encoding += YACHT64_CHARACTER_SET[ yacht64_index ]
303
+ end
304
+
305
+ length_msg = "The base64 and yacht64 string lengths are not equal."
306
+ goodlength = the_yacht64_encoding.length == @original64_string.length
307
+ raise RuntimeError, length_msg unless goodlength
308
+
309
+ return the_yacht64_encoding
310
+
311
+ end
312
+
313
+
314
+ def to_s
315
+ return @binary_bit_string
316
+ end
317
+
318
+
319
+ def to_264_bit_key
320
+
321
+ return [ to_s[ 0 .. (264-1) ] ].pack("B*")
322
+
323
+ end
324
+
325
+
326
+ # This method chops away anything after the first 256 bits and returns
327
+ # the result. Note that if the key was 24 bits long it will return
328
+ # the 24 bits without question.
329
+ #
330
+ # <b>Size of BCrypt and PBKDF2 Derived Keys</b>
331
+ #
332
+ # ----------- | --------- | ----------------- | ----------- |
333
+ # ----------- | --------- | ----------------- | ----------- |
334
+ # | Algorithm | Bit Count | Base64 Chars | 8 Bit Bytes |
335
+ # ----------- | --------- | ----------------- | ----------- |
336
+ # | BCrypt | 168 Bits | 28 characters | 21 bytes |
337
+ # | Pbkdf2 | 96 Bits | 16 characters | 12 bytes |
338
+ # ----------- | --------- | ----------------- | ----------- |
339
+ # | Total | 264 Bits | 44 characters | 33 bytes |
340
+ # ----------- | --------- | ----------------- | ----------- |
341
+ #
342
+ # == 256 Bit Encryption Key | Remove 8 Bits
343
+ #
344
+ # The manufactured encryption key, an amalgam of the above now has
345
+ # 264 bits carried by 44 Base64 characters.
346
+ #
347
+ # Just before it is used to encrypt vital keys, eight (8) bits are
348
+ # removed from the end of the key. The key is then converted into a
349
+ # powerful 32 byte (256 bit) encryption agent and is hashed by the
350
+ # SHA256 digest and delivered.
351
+ #
352
+ # @return [Byte]
353
+ # a binary string of thirty-two (32) eight (8) bit bytes which
354
+ # if appropriate can be used as a symmetric encryption key especially
355
+ # to the powerful AES256 cipher.
356
+ def to_256_bit_key
357
+ return [ to_s[ 0 .. (256-1) ] ].pack("B*")
358
+ end
359
+
360
+
361
+ # Return the <b>un-printable <em>binary</em> bytes</b> representation
362
+ # of this key. If you store 128 bits it will produce 22 characters
363
+ # because 128 divide by 6 is 21 characters and a remainder of two (2)
364
+ # bits.
365
+ #
366
+ # The re-conversion of the 22 characters will now produce 132 bits which
367
+ # is different from the original 128 bits.
368
+ #
369
+ # @return [Byte]
370
+ # a non-printable binary string of eight (8) bit bytes which can be
371
+ # used as input to both digest and symmetric cipher functions.
372
+ def to_binary_bytes
373
+ return [ to_s ].pack("B*")
374
+ end
375
+
376
+
377
+ # Return a key that is stoked with 48 random bytes which translates to
378
+ # a length of either <b>384 bits</b> or <b>64 base64 characters</b>.
379
+ #
380
+ # <b>The 48 Bytes map to 64 Base64 Characters</b>
381
+ #
382
+ # | -------- | ------------ | -------------------------------- |
383
+ # | Bits | Bytes | Base64 |
384
+ # | -------- | ------------ | -------------------------------- |
385
+ # | 384 Bits | is 48 bytes | and 64 characters |
386
+ # | -------- | ------------ | -------------------------------- |
387
+ #
388
+ # This key easily translates to a base64 and/or byte array format because
389
+ # the 384 bit count is a <b>multiple of both 6 and 8</b>.
390
+ #
391
+ # @return [Key]
392
+ # return a key containing 384 random bits (or a random array of 48 bytes)
393
+ # which can if necessary be serialized into 64 base64 characters.
394
+ def self.from_random_bytes
395
+
396
+ NUMBER_OF_BYTES_REQUIRED = 48
397
+ NUMBER_OF_BITS_EXPECTED = 384
398
+ BASE64_CHARACTER_COUNT = 64
399
+
400
+ raw_bytes = SecureRandom.random_bytes( 64 )
401
+
402
+ too_short_msg = "Not enough (only [#{raw_bytes.length}]) random bytes generated."
403
+ raise RuntimeError, too_short_msg unless raw_bytes.length >= NUMBER_OF_BYTES_REQUIRED
404
+
405
+ random_key = Key.from_byte_array( raw_bytes[ 0 .. ( NUMBER_OF_BYTES_REQUIRED - 1 ) ] )
406
+
407
+ bit_length = random_key.to_s.length
408
+ bit_lenth_msg = "Key with #{NUMBER_OF_BITS_EXPECTED} bits expected - not #{bit_length}."
409
+ raise RuntimeError, bit_length_msg unless bit_length == NUMBER_OF_BITS_EXPECTED
410
+
411
+ base64_length = random_key.to_yacht64.length
412
+ base64_lenth_msg = "Expected #{BASE64_CHARACTER_COUNT} base64 chars not #{base64_length}."
413
+ raise RuntimeError, base64_length_msg unless base64_length == BASE64_CHARACTER_COUNT
414
+
415
+ return random_key
416
+
417
+ end
418
+
419
+
420
+
421
+
422
+
423
+ # Convert non-alphanumeric characters within either Base64 or Radix64 into
424
+ # the OpenKey YACHT64 standard which has a forward slash but neither a plus sign
425
+ # nor a period character.
426
+ #
427
+ # <b>Converting the Non-Alphanumeric Characters</b>
428
+ #
429
+ # The behaviour here is happy to convert base64 strings produced by either
430
+ # Radix64 or Base64 or UrlSafe Base64. However, it is aware of the
431
+ # <b>non alpha-numeric characters</b> and converts them before processing
432
+ # with the modus operandi that says
433
+ #
434
+ # - ignore the forward slash in <b>YACHT64, Base64 and Radix64</b>
435
+ # - convert the <b>plus (+)</b> in Base64 to the <b>@ symbol</b> in YACHT64
436
+ # - convert the <b>period (.)</b> in <b>Radix64</b> to the @ symbol in YACHT64
437
+ # - convert <b>hyphen (-)</b> in <b>Url Safe Base64</b> into a fwd slash
438
+ # - convert <b>underscore (_)</b> in <b>Url Safe Base64</b> to an @ sign
439
+ # - <b>delete the (=) equals</b> padding character used by Base64
440
+ #
441
+ # Neither the OpenBSD backed Radix64 nor the OpenKey (YACHT64) entertain the
442
+ # concept of padding.
443
+ #
444
+ # @param char64_string [String]
445
+ # string produced in either the Radix64 or the Base64 format
446
+ #
447
+ # @return [String]
448
+ # an OpenKey YACHT64 standard string that does have a forward slash but neither
449
+ # contains a plus sign, nor a period character - and are replaced by the @ symbol.
450
+ # No equals padding character will be returned.
451
+ def replace_yacht64 char64_string
452
+ return char64_string.gsub(".", "@").gsub("/", "%").gsub("+", "@").gsub("-", "%").gsub("_", "@").delete("=")
453
+ end
454
+
455
+
456
+
457
+ private
458
+
459
+
460
+ def assert_yacht64_index yacht64_index
461
+ index_msg = "yacht64 index should run between 0 and 63 inclusive."
462
+ all_good = yacht64_index >= 0 && yacht64_index <= 63
463
+ raise ArgumentError, index_msg unless all_good
464
+ end
465
+
466
+
467
+ def assert_in_out_lengths( in_string, out_string )
468
+
469
+ in_length = in_string.length
470
+ out_length = out_string.length
471
+ good_ratio = out_length == in_length * 6
472
+ size_msg = "Out string length [#{out_length}] not 6 times bigger than [#{in_length}]."
473
+ raise RuntimeError, size_msg unless good_ratio
474
+
475
+ end
476
+
477
+
478
+ end
479
+
480
+
481
+ end