module SimpleSecrets::Primitives

Public: Various methods useful for performing cryptographic operations. WARNING: Using any of these primitives in isolation could be Bad. Take cautious.

Examples

Primitives.nonce
# => "\x02\x0E\xBB\xBE\xA2\xA4\f\x80\x11N\xCDui\xEE<e"

Public Class Methods

binify(string) click to toggle source

Public: Turn a websafe string back into a binary string.

Uses base64url encoding.

string - websafe string

Examples

Primitives.binify('')
# =>

Returns the binary version of this string

# File lib/simple_secrets/primitives.rb, line 248
def self.binify string
  raise 'base64url string required.' unless (string.instance_of?(String) && string =~ /^[a-zA-Z0-9_\-]+$/)

  string += '=' while !(string.size % 4).zero?
  Base64.urlsafe_decode64(string)
end
compare(a, b) click to toggle source

Public: Use a constant-time comparison algorithm to reduce side-channel attacks.

Short-circuits only when the two buffers aren't the same length.

a - a binary string b - a binary string

Examples

Primitives.compare('','')
# =>

Returns true if both buffer contents match

# File lib/simple_secrets/primitives.rb, line 220
def self.compare a, b
  assertBinary(a, b)

  # things must be the same length to compare them.
  return false if a.bytesize != b.bytesize

  # constant-time compare
  #   hat-tip to https://github.com/freewil/scmp for |=
  same = 0;
  (0...a.bytesize).each do |i|
    same |= a.getbyte(i) ^ b.getbyte(i)
  end
  same == 0
end
decrypt(binary, key, iv) click to toggle source

Public: Decrypt buffer with the given key and initialization vector.

Uses AES256.

binary - ciphertext key - the 256-bit encryption key iv - the 128-bit initialization vector

Examples

Primitives.decrypt('', '')
# =>

Returns the plaintext binary string

# File lib/simple_secrets/primitives.rb, line 149
def self.decrypt binary, key, iv
  assertBinary(binary, key, iv)
  assert256BitBinary(key)
  assert128BitBinary(iv)

  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  cipher.decrypt
  cipher.key = key
  cipher.iv = iv

  decrypted = ''.force_encoding('BINARY')
  decrypted << cipher.update(binary)
  decrypted << cipher.final
  decrypted
end
derive_receiver_hmac(master_key) click to toggle source

Public: Generate the authentication key for messages originating from the channel's Receiver side.

Uses the ASCII string 'simple-crypto/receiver-hmac-key' as the role.

master_key - the 256-bit master key of this secure channel

Examples

Primitives.derive_receiver_hmac(master_key)
# =>

Returns 256-bit receiver hmac key

# File lib/simple_secrets/primitives.rb, line 81
def self.derive_receiver_hmac master_key
  derive(master_key, 'simple-crypto/receiver-hmac-key')
end
derive_receiver_key(master_key) click to toggle source

Public: Generate the encryption key for messages originating from the channel's Receiver side.

Uses the ASCII string 'simple-crypto/receiver-cipher-key' as the role.

master_key - the 256-bit master key of this secure channel

Examples

Primitives.derive_receiver_key(master_key)
# =>

Returns 256-bit receiver encryption key

# File lib/simple_secrets/primitives.rb, line 99
def self.derive_receiver_key master_key
  derive(master_key, 'simple-crypto/receiver-cipher-key')
end
derive_sender_hmac(master_key) click to toggle source

Public: Generate the authentication key for messages originating from the channel's Sender side.

Uses the ASCII string 'simple-crypto/sender-hmac-key' as the role.

master_key - the 256-bit master key of this secure channel

Examples

Primitives.derive_sender_hmac(master_key)
# =>

Returns 256-bit sender hmac key

# File lib/simple_secrets/primitives.rb, line 45
def self.derive_sender_hmac master_key
  derive(master_key, 'simple-crypto/sender-hmac-key')
end
derive_sender_key(master_key) click to toggle source

Public: Generate the encryption key for messages originating from the channel's Sender side.

Uses the ASCII string 'simple-crypto/sender-cipher-key' as the role.

master_key - the 256-bit master key of this secure channel

Examples

Primitives.derive_sender_key(master_key)
# =>

Returns 256-bit sender encryption key

# File lib/simple_secrets/primitives.rb, line 63
def self.derive_sender_key master_key
  derive(master_key, 'simple-crypto/sender-cipher-key')
end
deserialize(binary) click to toggle source

Public: Turn a binary representation into a Ruby object suitable for use in application logic. This object possibly originated in a different programming environment—it should be JSON-like in structure.

Uses MsgPack data serialization.

binary - a binary string version of the object

Examples

Primitives.deserialize('')
# =>

Returns the Ruby object

# File lib/simple_secrets/primitives.rb, line 310
def self.deserialize binary
  assertBinary(binary)

  MessagePack.unpack(binary)
end
encrypt(binary, key) click to toggle source

Public: Encrypt buffer with the given key.

Uses AES256 with a random 128-bit initialization vector.

binary - the plaintext binary string key - the 256-bit encryption key

Examples

Primitives.encrypt('', '')
# =>

Returns a binary string of (IV || ciphertext)

# File lib/simple_secrets/primitives.rb, line 117
def self.encrypt binary, key
  assertBinary(binary)
  assertBinary(key)
  assert256BitBinary(key)

  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  cipher.encrypt
  cipher.key = key
  iv = cipher.iv = OpenSSL::Random.random_bytes(16)

  encrypted = ''.force_encoding('BINARY')
  encrypted << iv
  encrypted << cipher.update(binary)
  encrypted << cipher.final
  encrypted
end
identify(binary) click to toggle source

Public: Create a short identifier for potentially sensitive data.

binary - the data to identify

Examples

Primitives.identify('')
# =>

Returns a 6-byte binary string identifier

# File lib/simple_secrets/primitives.rb, line 176
def self.identify binary
  assertBinary(binary)

  hash = OpenSSL::Digest::SHA256.new
  hash << [binary.size].pack("C*")
  hash << binary
  hash.digest[0..5]
end
mac(binary, hmac_key) click to toggle source

Public: Create a message authentication code for the given data. Uses HMAC-SHA256.

binary - data to authenticate hmac_key - the authentication key

Examples

Primitives.mac('','')
# =>

Returns a 32-byte MAC binary string

# File lib/simple_secrets/primitives.rb, line 198
def self.mac binary, hmac_key
  assertBinary(binary, hmac_key)
  assert256BitBinary(hmac_key)

  OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, hmac_key, binary)
end
nonce() click to toggle source

Public: Provide 16 securely random bytes.

Examples

nonce
# => "\x02\x0E\xBB\xBE\xA2\xA4\f\x80\x11N\xCDui\xEE<e"

Returns 16 random bytes in a binary string

# File lib/simple_secrets/primitives.rb, line 27
def self.nonce
  OpenSSL::Random.random_bytes 16
end
serialize(object) click to toggle source

Public: Turn a JSON-like object into a binary representation suitable for use in crypto functions. This object will possibly be deserialized in a different programming environment—it should be JSON-like in structure.

Uses MsgPack data serialization.

object - any object without cycles which responds to `to_msgpack`

Examples

Primitives.serialize('')
# =>

Returns the binary version of this object

# File lib/simple_secrets/primitives.rb, line 290
def self.serialize object
  object.to_msgpack
end
stringify(binary) click to toggle source

Public: Turn a binary buffer into a websafe string.

Uses base64url encoding.

binary - data which needs to be websafe

Examples

Primitives.stringify('')
# =>

Returns the websafe string

# File lib/simple_secrets/primitives.rb, line 268
def self.stringify binary
  assertBinary(binary)

  Base64.urlsafe_encode64(binary).gsub('=','')
end
zero(*args) click to toggle source

Public: Overwrite the contents of the buffer with zeroes. This is critical for removing sensitive data from memory.

args - binary strings whose content should be wiped

Examples

Primitives.zero('','')
# =>

Returns an array of references to the strings which have been zeroed

# File lib/simple_secrets/primitives.rb, line 328
def self.zero *args
  assertBinary(*args)
  args.each do |buf|
    buf.gsub!(/./,"\x00")
  end
end

Private Class Methods

assert128BitBinary(binary) click to toggle source
# File lib/simple_secrets/primitives.rb, line 367
def self.assert128BitBinary binary
  raise '128-bit binary string required.' unless binary.size == 16
end
assert256BitBinary(binary) click to toggle source
# File lib/simple_secrets/primitives.rb, line 363
def self.assert256BitBinary binary
  raise "256-bit binary string required. '#{binary.unpack("H*")}'" unless binary.size == 32
end
assertBinary(*binaries) click to toggle source
# File lib/simple_secrets/primitives.rb, line 359
def self.assertBinary *binaries
  binaries.each { |binary| raise "Bad encoding. Binary string required." unless binary.instance_of?(String) && binary.encoding == Encoding::ASCII_8BIT }
end
derive(master_key, role) click to toggle source

Private: Generate an encryption or hmac key from the master key and role.

Uses SHA256(key || role).  [TODO: link or citation]

master_key - The 256-bit binary string master key of this secure channel. role - The part of the protocol in which this key will be used.

Examples

derive "\x0ER\xE5\x88\xC2\xBB<\xAFZ?\xA5\xCCx\xA6@AB(Bc\x962\x7F:\xF7\x0E\x1Cl\xB9\x02Y\xE4", "some-protocol/some-role"
# => "~\x80\xB4\xC3>\xC4\xDEw\xB2\xD2\x92\xC9\x88\xA8\xD7p\xAF\xF6Y\x95\x91\xA3\xFDV\xC5qo\x80U\x19P\xB0"

Returns 256-bit derived key as a 32-byte binary string

# File lib/simple_secrets/primitives.rb, line 350
def self.derive master_key, role
  assertBinary(master_key)
  assert256BitBinary(master_key)
  hash = OpenSSL::Digest::SHA256.new
  hash << master_key
  hash << role.force_encoding('BINARY')
  hash.digest
end