Class: OpenKey::KeyLocal
- Inherits:
-
Object
- Object
- OpenKey::KeyLocal
- Defined in:
- lib/keytools/key.local.rb
Overview
The command line interface has a high entropy randomly generated key whose purpose is to lock the application’s data key for the duration of the session which is between a login and a logout.
These keys are unique to only one shell session on one workstation and they live lives that are no longer (and mostly shorter) than the life of the parent shell.
The 4 CLI Shell Entities
The four (4) important entities within the shell session are
-
an obfuscator key for locking the shell key during a session
-
a high entropy randomly generated shell key for locking the app data key
-
one environment variable whose value embodies three (3) data segments
-
a session id derived by pushing the env var through a one-way function
Constant Summary collapse
- BCRYPT_SALT_LENGTH =
The number of Radix64 characters that make up a valid BCrypt salt. To create a BCrypt salt use
22- BCRYPT_ITER_COUNT_SIZE =
There are two digits representing the BCrypt iteration count. The minimum is 10 and the maximum is 16.
2- SESSION_TOKEN_SIZE =
The session token comprises of 3 segments with fixed lengths. This triply segmented text token that can be used to decrypt and deliver the shell key.
128 + 22 + BCRYPT_ITER_COUNT_SIZE
- BCRYPT_SALT_START_INDEX =
Given a 152 character session token, what is the index that pinpoints the beginning of the 22 character BCrypt salt? The answer is given by this BCRYPT_SALT_START_INDEX constant.
SESSION_TOKEN_SIZE - BCRYPT_SALT_LENGTH - BCRYPT_ITER_COUNT_SIZE
- BCRYPT_SALT_END_INDEX =
What index pinpoints the end of the BCrypt salt itself. This is easy as the final 2 characters are the iteration count so the end index is the length subtract 1 subtract 2.
SESSION_TOKEN_SIZE - 1
Class Method Summary collapse
-
.derive_session_crypt_key(bcrypt_salt_key, use_grandparent_pid = false) ⇒ OpenKey::Key
Derive a short term (session scoped) encryption key from the surrounding shell and workstation (machine) environment with an important same/different guarantee.
-
.generate_shell_key_and_token ⇒ String
Initialize the session by generating a random high entropy shell token and then generate an obfuscator key which we use to lock the shell key and return a triply segmented text token that can be used to decrypt and deliver the shell key as long as the same shell on the same machine is employed to make the call.
-
.regenerate_shell_key(session_token, use_grandparent_pid = false) ⇒ OpenKey::Key
Regenerate the random shell key that was instantiated and locked during the instantiate_shell_key_and_generate_token method.
Class Method Details
.derive_session_crypt_key(bcrypt_salt_key, use_grandparent_pid = false) ⇒ OpenKey::Key
Derive a short term (session scoped) encryption key from the surrounding shell and workstation (machine) environment with an important same/different guarantee.
The same / different guarantee promises us that the derived key will be
-
the same whenever called from within this executing shell
-
different when the shell and/or workstation are different
This method uses a one-way function to return a combinatorial digested session identification string using a number of distinct parameters that deliver the important behaviours of changing in certain circumstances and remaining unchanged in others.
Change | When Should the key Change?
What is really important is that the key changes when
-
the command shell changes
-
the workstation shell user is switched
-
the host machine workstation is changed
-
the user SSH’s into another shell
A distinct workstation is identified by the first MAC address and the hostname of the machine.
Unchanged | When Should the Key Remain Unchanged?
Remaining unchanged in certain scenarios is a feature that is just as important as changing in others. The key must remain unchanged when
-
the user returns to a command shell
-
the user exits their remote SSH session
-
sudo is used to execute the commands
-
the user comes back to their workstation
-
the clock ticks into another day, month, year …
215 216 217 218 219 220 221 222 223 224 |
# File 'lib/keytools/key.local.rb', line 215 def self.derive_session_crypt_key bcrypt_salt_key, use_grandparent_pid = false shell_id_text = KeyIdent.derive_shell_identifier( use_grandparent_pid ) truncate_text = shell_id_text.length > KdfBCrypt::BCRYPT_MAX_IN_TEXT_LENGTH shell_id_trim = shell_id_text unless truncate_text shell_id_trim = shell_id_text[ 0 .. ( KdfBCrypt::BCRYPT_MAX_IN_TEXT_LENGTH - 1 ) ] if truncate_text return KdfBCrypt.generate_key( shell_id_trim, bcrypt_salt_key ) end |
.generate_shell_key_and_token ⇒ String
Initialize the session by generating a random high entropy shell token and then generate an obfuscator key which we use to lock the shell key and return a triply segmented text token that can be used to decrypt and deliver the shell key as long as the same shell on the same machine is employed to make the call.
The 3 Session Token Segments
The session token is divided up into 3 segments with a total of 150
characters.
| -------- | ------------ | ------------------------------------- |
| Segment | Length | Purpose |
| -------- | ------------ | ------------------------------------- |
| 1 | 16 bytes | AES Encrypt Initialization Vector(IV) |
| 2 | 80 bytes | Cipher text from Random Key AES crypt |
| 3 | 22 chars | Salt for obfuscator key derivation |
| -------- | ------------ | ------------------------------------- |
| Total | 150 chars | Session Token in Environment Variable |
| -------- | ------------ | ------------------------------------- |
Why is the 16 byte salt and the 80 byte BCrypt ciphertext represented by 128 base64 characters?
16 bytes + 80 bytes = 96 bytes
96 bytes x 8 bits = 768 bits
768 bits / 6 bits = 128 base64 characters
85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/keytools/key.local.rb', line 85 def self.generate_shell_key_and_token bcrypt_salt_key = KdfBCrypt.generate_bcrypt_salt obfuscator_key = derive_session_crypt_key( bcrypt_salt_key ) random_key_ciphertext = obfuscator_key.do_encrypt_key( Key.from_random() ) session_token = random_key_ciphertext + bcrypt_salt_key.reverse assert_session_token_size( session_token ) return session_token end |
.regenerate_shell_key(session_token, use_grandparent_pid = false) ⇒ OpenKey::Key
Regenerate the random shell key that was instantiated and locked during the instantiate_shell_key_and_generate_token method.
To successfully reacquire the randomly generated (and then locked) shell key we must be provided with five (5) data points, four (4) of which are embalmed within the 150 character session token parameter.
What we need to Regenerate the Shell Key
Regenerating the shell key is done in two steps when given the four (4) session token segments described below, and the shell identity key described in the Identifier class.
The session token is divided up into 4 segments with a total of 152
characters.
| -------- | ------------ | ------------------------------------- |
| Segment | Length | Purpose |
| -------- | ------------ | ------------------------------------- |
| 1 | 16 bytes | AES Encrypt Initialization Vector(IV) |
| 2 | 80 bytes | Cipher text from Random Key AES crypt |
| 3 | 22 chars | Salt 4 shell identity key derivation |
| 4 | 2 chars | BCrypt iteration parameter (10 to 16) |
| -------- | ------------ | ------------------------------------- |
| Total | 152 chars | Session Token in Environment Variable |
| -------- | ------------ | ------------------------------------- |
143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/keytools/key.local.rb', line 143 def self.regenerate_shell_key( session_token, use_grandparent_pid = false ) assert_session_token_size( session_token ) bcrypt_salt = session_token[ BCRYPT_SALT_START_INDEX .. BCRYPT_SALT_END_INDEX ].reverse assert_bcrypt_salt_size( bcrypt_salt ) key_ciphertext = session_token[ 0 .. ( BCRYPT_SALT_START_INDEX - 1 ) ] obfuscator_key = derive_session_crypt_key( bcrypt_salt, use_grandparent_pid ) regenerated_key = obfuscator_key.do_decrypt_key( key_ciphertext ) return regenerated_key end |