Class: CMAC

Inherits:
Object
  • Object
show all
Defined in:
lib/cmac.rb,
lib/cmac/version.rb,
lib/cmac/exception.rb

Defined Under Namespace

Classes: Exception

Constant Summary collapse

ZeroBlock =
"\0" * 16
ConstantBlock =
("\0" * 15) + "\x87"
VERSION =
'0.1.0'

Instance Method Summary collapse

Constructor Details

#initialize(key) ⇒ CMAC

Returns a new instance of CMAC.



10
11
12
13
14
# File 'lib/cmac.rb', line 10

def initialize(key)
  key.force_encoding('BINARY') if key.respond_to?(:force_encoding)
  @key = _derive_key(key)
  @key1, @key2 = _generate_subkeys(@key)
end

Instance Method Details

#_derive_key(key) ⇒ Object



50
51
52
53
54
55
56
57
# File 'lib/cmac.rb', line 50

def _derive_key(key)
  if key.length == 16
    key
  else
    cmac = CMAC.new(ZeroBlock)
    cmac.encrypt(key)
  end
end

#_encrypt_block(key, block) ⇒ Object



59
60
61
62
63
64
65
# File 'lib/cmac.rb', line 59

def _encrypt_block(key, block)
  cipher = OpenSSL::Cipher.new('AES-128-ECB')
  cipher.encrypt
  cipher.padding = 0
  cipher.key = key
  cipher.update(block) + cipher.final
end

#_generate_subkeys(key) ⇒ Object



67
68
69
70
71
72
# File 'lib/cmac.rb', line 67

def _generate_subkeys(key)
  key0 = _encrypt_block(key, ZeroBlock)
  key1 = _next_key(key0)
  key2 = _next_key(key1)
  [key1, key2]
end

#_leftshift(input) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/cmac.rb', line 86

def _leftshift(input)
  overflow = 0
  words = input.unpack('N4').reverse
  words = words.map do |word|
    new_word = (word << 1) & 0xFFFFFFFF
    new_word |= overflow
    overflow = (word & 0x80000000) >= 0x80000000 ? 1 : 0
    new_word
  end
  words.reverse.pack('N4')
end

#_needs_padding?(message) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/cmac.rb', line 74

def _needs_padding?(message)
  message.length == 0 || message.length % 16 != 0
end

#_next_key(key) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/cmac.rb', line 78

def _next_key(key)
  if key[0].ord < 0x80
    _leftshift(key)
  else
    _xor(_leftshift(key), ConstantBlock)
  end
end

#_pad_message(message) ⇒ Object



98
99
100
101
102
# File 'lib/cmac.rb', line 98

def _pad_message(message)
  padded_length = message.length + 16 - (message.length % 16)
  message = message + "\x80"
  message.ljust(padded_length, "\0")
end

#_secure_compare?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
109
110
111
112
113
114
# File 'lib/cmac.rb', line 104

def _secure_compare?(a, b)
  return false unless a.bytesize == b.bytesize

  bytes = a.unpack("C#{a.bytesize}")

  result = 0
  b.each_byte do |byte|
    result |= byte ^ bytes.shift
  end
  result == 0
end

#_xor(a, b) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/cmac.rb', line 116

def _xor(a, b)
  a.force_encoding('BINARY') if a.respond_to?(:force_encoding)
  b.force_encoding('BINARY') if b.respond_to?(:force_encoding)

  output = ''
  length = [a.length, b.length].min
  length.times do |i|
    output << (a[i].ord ^ b[i].ord).chr
  end
  output
end

#sign(message, truncate = 16) ⇒ Object Also known as: encrypt

Raises:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/cmac.rb', line 16

def sign(message, truncate = 16)
  raise CMAC::Exception.new('Tag cannot be greater than maximum (16 bytes)') if truncate > 16
  raise CMAC::Exception.new('Tag cannot be less than minimum (8 bytes)') if truncate < 8
  message.force_encoding('BINARY') if message.respond_to?(:force_encoding)

  if _needs_padding?(message)
    message = _pad_message(message)
    final_block = @key2
  else
    final_block = @key1
  end

  last_ciphertext = ZeroBlock
  count = message.length / 16
  range = Range.new(0, count - 1)
  blocks = range.map { |i| message.slice(16 * i, 16) }
  blocks.each_with_index do |block, i|
    if i == range.last
      block = _xor(final_block, block)
    end

    block = _xor(block, last_ciphertext)
    last_ciphertext = _encrypt_block(@key, block)
  end

  last_ciphertext.slice(0, truncate)
end

#valid_message?(tag, message) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
# File 'lib/cmac.rb', line 45

def valid_message?(tag, message)
  other_tag = sign(message)
  _secure_compare?(tag, other_tag)
end