opensecret 0.0.962 → 0.0.988

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