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.
- 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
|