Class: CMAC

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

Constant Summary collapse

Exception =
Class.new(StandardError)
ZeroBlock =
"\0" * 16
ConstantBlock =
("\0" * 15) + "\x87"
VERSION =
'0.3.0'

Instance Method Summary collapse

Constructor Details

#initialize(key) ⇒ CMAC

Returns a new instance of CMAC.



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

def initialize(key)
  @key = _derive_key(key.b)
  @key1, @key2 = _generate_subkeys(@key)
end

Instance Method Details

#_derive_key(key) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/cmac.rb', line 54

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

#_encrypt_block(key, block) ⇒ Object



63
64
65
66
67
68
69
# File 'lib/cmac.rb', line 63

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



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

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

#_leftshift(input) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/cmac.rb', line 90

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)


78
79
80
# File 'lib/cmac.rb', line 78

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

#_next_key(key) ⇒ Object



82
83
84
85
86
87
88
# File 'lib/cmac.rb', line 82

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

#_pad_message(message) ⇒ Object



102
103
104
105
106
# File 'lib/cmac.rb', line 102

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

#_secure_compare?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


108
109
110
111
112
113
114
115
116
117
118
# File 'lib/cmac.rb', line 108

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



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/cmac.rb', line 120

def _xor(a, b)
  a = a.b
  b = b.b

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

#inspectObject



15
16
17
# File 'lib/cmac.rb', line 15

def inspect
  "#<CMAC:0x#{object_id.to_s(16)}>"
end

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

Raises:



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

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 = message.b

  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)


49
50
51
52
# File 'lib/cmac.rb', line 49

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