opensecret 0.0.962 → 0.0.988
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -10
- data/bin/opensecret +3 -4
- data/bin/ops +5 -0
- data/lib/extension/string.rb +114 -0
- data/lib/factbase/facts.opensecret.io.ini +9 -21
- data/lib/interprete/begin.rb +232 -0
- data/lib/interprete/cmd.rb +621 -0
- data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
- data/lib/interprete/init.rb +205 -0
- data/lib/interprete/key.rb +119 -0
- data/lib/interprete/open.rb +148 -0
- data/lib/{plugins/usecases → interprete}/put.rb +19 -6
- data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
- data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
- data/lib/interprete/set.rb +46 -0
- data/lib/interprete/use.rb +43 -0
- data/lib/interpreter.rb +165 -0
- data/lib/keytools/binary.map.rb +245 -0
- data/lib/keytools/digester.rb +245 -0
- data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
- data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
- data/lib/keytools/doc.star.schema.strategy.txt +77 -0
- data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
- data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
- data/lib/keytools/kdf.bcrypt.rb +180 -0
- data/lib/keytools/kdf.pbkdf2.rb +164 -0
- data/lib/keytools/key.data.rb +227 -0
- data/lib/keytools/key.derivation.rb +341 -0
- data/lib/keytools/key.module.rb +140 -0
- data/lib/keytools/key.rb +481 -0
- data/lib/logging/gem.logging.rb +1 -2
- data/lib/modules/cryptology.md +43 -0
- data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
- data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +118 -0
- data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
- data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
- data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
- data/lib/modules/mappers/collateral.rb +282 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/mappers/envelope.rb +127 -0
- data/lib/modules/mappers/settings.rb +170 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
- data/lib/notepad/scratch.pad.rb +17 -0
- data/lib/session/fact.finder.rb +13 -0
- data/lib/session/require.gem.rb +5 -0
- data/lib/store-commands.txt +180 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +5 -6
- metadata +74 -29
- data/lib/crypto/blowfish.rb +0 -85
- data/lib/crypto/collect.rb +0 -140
- data/lib/crypto/verify.rb +0 -33
- data/lib/opensecret.rb +0 -236
- data/lib/plugins/cipher.rb +0 -203
- data/lib/plugins/ciphers/blowfish.rb +0 -126
- data/lib/plugins/coldstore.rb +0 -181
- data/lib/plugins/envelope.rb +0 -116
- data/lib/plugins/secrets.uc.rb +0 -94
- data/lib/plugins/usecase.rb +0 -239
- data/lib/plugins/usecases/init.rb +0 -145
- data/lib/plugins/usecases/open.rb +0 -108
- data/lib/session/attributes.rb +0 -279
- data/lib/session/dictionary.rb +0 -191
- data/lib/session/file.path.rb +0 -53
- 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
|
data/lib/keytools/key.rb
ADDED
@@ -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
|