Class: SafeDb::KeyId

Inherits:
Object
  • Object
show all
Defined in:
lib/keytools/key.id.rb

Overview

This class derives non secret but unique identifiers based on different combinations of the application, shell and machine (compute element) references.

Identifier Are Not Secrets

And their starting values are retrievable

Note that the principle and practise of identifiers is not about keeping secrets. An identifier can easily give up its starting value/s if and when brute force is applied. The properties of a good iidentifier (ID) are

  • non repeatability (also known as uniqueness)

  • non predictability (of the next identifier)

  • containing alphanumerics (for file/folder/url names)

  • human readable (hence hyphens and separators)

  • non offensive (no swear words popping out)

Story | Identifiers Speak Volumes

I told a friend what the turnover of his company was and how many clients he had. He was shocked and wanted to know how I had gleened this information.

The invoices he sent me (a year apart). Both his invoice IDs (identifiers) and his user IDs where integers that counted up. So I could determine how many new clients he had in the past year, how many clients he had when I got the invoice, and I determined the turnover by guesstimating the average invoice amount.

Many successful website attacks are owed to a predictable customer ID or a counter type session ID within the cookies.

Good Identifiers Need Volumes

IDs are not secrets - but even so, a large number of properties are required to produce a high quality ID.

Constant Summary collapse

IDENTITY_CHUNK_LENGTH =

The identity chunk length is set at four (4) which means each of the fabricated identifiers comprises of four character segments divided by hyphens. Only the 62 alpha-numerics ( a-z, A-Z and 0-9 ) will appear within identifiers - which maintains simplicity and provides an opportunity to re-iterate that identifiers are designed to be unpredictable, but not secret.

4
SEGMENT_CHAR =

A hyphen is the chosen character for dividing the identifier strings into chunks of four (4) as per the IDENTITY_CHUNK_LENGTH constant.

"-"

Class Method Summary collapse

Class Method Details

.derive_app_instance_identifier(app_instance_ref) ⇒ String

Get an identifier that is always the same for the parameter application reference regardless of the machine or shell or even the machine user, coming together to make the request.

The returned identifier will consist only of alphanumeric characters and one hyphen, plus it always starts and ends with an alphanumeric.

Parameters:

  • app_instance_ref (String)

    the string reference of the application instance (or shard) that is in play and needs to be digested into a unique but not-a-secret identifier.

Returns:

  • (String)

    An identifier that is guaranteed to be the same whenever the same application reference is provided on any machine, using any user through any shell interface or command prompt.

    It must be different for any other application reference.



79
80
81
# File 'lib/keytools/key.id.rb', line 79

def self.derive_app_instance_identifier( app_instance_ref )
  return derive_identifier( app_instance_ref )
end

.derive_app_instance_machine_id(app_ref) ⇒ String

Get an identifier that is always the same for the application instance (with reference given in parameter) on this machine and is always different when either/or or both the application ref and machine are different.

The returned identifier will consist of only alphanumeric characters and hyphens - it will always start and end with an alphanumeric.

This behaviour draws a fine line around the concept of machine, virtual machine, workstation and/or compute element.

(aka) The AIM ID

Returned ID is aka the Application Instance Machine (AIM) Id.

Parameters:

  • app_ref (String)

    the string reference of the application instance (or shard) that is being used.

Returns:

  • (String)

    an identifier that is guaranteed to be the same whenever the same application reference is provided on this machine.

    it must be different on another machine even when the same application reference is provided.

    It will also be different on this workstation if the application instance identifier provided is different.



112
113
114
# File 'lib/keytools/key.id.rb', line 112

def self.derive_app_instance_machine_id( app_ref )
  return derive_identifier( app_ref + KeyIdent.derive_machine_identifier() )
end

.derive_identifier(reference) ⇒ String

This method returns a 10 character digest of the parameter reference string.

How to Derive the 10 Character Identifier

So how are the 10 characters derived from the reference provided in the first parameter. The algorithm is this.

  • reverse the reference and feed it to a 256 bit digest

  • chop away the rightmost digits so that 252 bits are left

  • convert the one-zero bit str to 42 (YACHT64) characters

  • remove the (on average 1.5) non-alphanumeric characters

  • cherry pick and return spread out 8 characters

Parameters:

  • reference (String)

    the plaintext reference input to the digest algorithm

Returns:

  • (String)

    a 10 character string that is a digest of the reference string provided in the parameter.

Raises:

  • (RuntimeError)


233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/keytools/key.id.rb', line 233

def self.derive_identifier( reference )

  bitstr_256 = Key.from_binary( Digest::SHA256.digest( reference.reverse ) ).to_s
  bitstr_252 = bitstr_256[ 0 .. ( BIT_LENGTH_252 - 1 ) ]
  id_err_msg = "The ID digest needs #{BIT_LENGTH_252} not #{bitstr_252.length} chars."
  raise RuntimeError, id_err_msg unless bitstr_252.length == BIT_LENGTH_252

  id_chars_pool = Key64.from_bits( bitstr_252 ).to_alphanumeric
  undivided_str = KeyAlgo.cherry_picker( ID_TWO_CHUNK_LEN, id_chars_pool )
  id_characters = undivided_str.insert( IDENTITY_CHUNK_LENGTH, SEGMENT_CHAR )

  min_size_msg = "Id length #{id_characters.length} is not #{(ID_TWO_CHUNK_LEN + 1)} chars."
  raise RuntimeError, min_size_msg unless id_characters.length == ( ID_TWO_CHUNK_LEN + 1 )

  return id_characters.downcase

end

.derive_session_id(session_token) ⇒ String

The session ID generated here is a derivative of the 150 character session token instantiated by SafeDb::KeyLocal.generate_shell_key_and_token and provided here ad verbatim.

The algorithm for deriving the session ID is as follows.

  • convert the 150 characters to an alphanumeric string

  • convert the result to a bit string and then to a key

  • put the key’s binary form through a 384 bit digest

  • convert the digest’s output to 64 YACHT64 characters

  • remove the (on average 2) non-alphanumeric characters

  • cherry pick a spread out 12 characters from the pool

  • hiphenate the character positions five (5) and ten (10)

  • ensure the length of the resultant ID is fourteen (14)

The resulting session id will look something like this

g3sf-pab5-9xvd

Parameters:

Returns:

  • (String)

    a 14 character string that cannot feasibly be repeated within the keyspace of even a gigantic organisation.

    This method guarantees that the session id will always be the same when called by commands within the same shell in the same machine.

Raises:

  • (RuntimeError)


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/keytools/key.id.rb', line 193

def self.derive_session_id( session_token )

  assert_session_token_size( session_token )
  random_length_id_key = Key.from_char64( session_token.to_alphanumeric )
  a_384_bit_key = random_length_id_key.to_384_bit_key()
  a_64_char_str = a_384_bit_key.to_char64()
  base_64_chars = a_64_char_str.to_alphanumeric

  id_chars_pool = KeyAlgo.cherry_picker( ID_TRI_CHUNK_LEN, base_64_chars )
  id_hyphen_one = id_chars_pool.insert( IDENTITY_CHUNK_LENGTH, SEGMENT_CHAR )
  id_characters = id_hyphen_one.insert( ( IDENTITY_CHUNK_LENGTH * 2 + 1 ), SEGMENT_CHAR )

  err_msg = "Shell ID needs #{ID_TRI_TOTAL_LEN} not #{id_characters.length} characters."
  raise RuntimeError, err_msg unless id_characters.length == ID_TRI_TOTAL_LEN

  return id_characters.downcase

end

.derive_universal_id(app_reference, session_token) ⇒ String

The 32 character universal identifier bonds a digested application state identifier with the shell identifier. This method gives dual double guarantees to the effect that

  • a change in one, or in the other, or in both returns a different universal id

  • the same app state identifier in the same shell produces the same universal id

The 32 Character Universal Identifier

The universal identifier is an amalgam of two digests which can be individually retrieved from other methods in this class. An example is

universal id => hg2x0-g3uslf-pa2bl5-09xvbd-n4wcq
the shell id => g3uslf-pa2bl5-09xvbd
app state id => hg2x0-n4wcq

The 32 character universal identifier comprises of 18 session identifier characters (see derive_session_id) sandwiched between ten (10) digested application identifier characters, five (5) in front and five (5) at the back - all segmented by four (4) hyphens.

Parameters:

  • app_reference (String)

    the chosen plaintext application reference identifier that is the input to the digesting (hashing) algorithm.

  • session_token (String)

    a triply segmented (and one liner) text token instantiated by SafeDb::KeyLocal.generate_shell_key_and_token and provided here ad verbatim.

Returns:

  • (String)

    a 32 character string that cannot feasibly be repeated due to the use of one way functions within its derivation. The returned identifier bonds the application state reference with the present session.



151
152
153
154
155
156
157
158
159
160
# File 'lib/keytools/key.id.rb', line 151

def self.derive_universal_id( app_reference, session_token )

  shellid = derive_session_id( session_token )
  app_ref = derive_identifier( app_reference + shellid )
  chunk_1 = app_ref[ 0 .. IDENTITY_CHUNK_LENGTH ]
  chunk_3 = app_ref[ ( IDENTITY_CHUNK_LENGTH + 1 ) .. -1 ]

  return "#{chunk_1}#{shellid}#{SEGMENT_CHAR}#{chunk_3}".downcase

end