-
Notifications
You must be signed in to change notification settings - Fork 2
/
cmac.rb
84 lines (66 loc) · 2.07 KB
/
cmac.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
require 'openssl'
module Digest
class CMAC
BLOCK_SIZE = 16
# Constant defined in the RFC for 16-byte block sizes
RB = "\0" * (BLOCK_SIZE - 1) + "\x87"
# For testing purposes
attr_accessor :l, :lu, :lu2
# Constructs an object to calculate CMACs for data
def initialize(cipher, key)
raise "Cipher block size must be #{BLOCK_SIZE}" unless cipher.block_size == BLOCK_SIZE
@cipher = cipher
@cipher.encrypt
@cipher.key = @key = key
generate_subkeys
reset
end
def reset
@data = ''
@tag = "\0" * BLOCK_SIZE
end
def update(data)
@data += data
complete_block_count = (@data.length / BLOCK_SIZE).floor
if @data.length > BLOCK_SIZE
0.upto(complete_block_count-1) do |i|
break if @data.length == BLOCK_SIZE
block = @data[0..(BLOCK_SIZE-1)]
@data = @data[[email protected]]
raise 'Bad block length' if block.length != BLOCK_SIZE
@tag = xor(@tag, block)
@tag = encrypt_block(@tag)
end
end
end
def digest
raise 'Bad data length' if @data.length > BLOCK_SIZE
if @data.length == BLOCK_SIZE
@data = xor(@data, @lu)
else
@data << "\200" + ("\000" * (BLOCK_SIZE - @data.length - 1))
@data = xor(@data, @lu2)
end
@tag = xor(@tag, @data)
@tag = encrypt_block(@tag)
end
private
def encrypt_block(block)
@cipher.reset
@cipher.update(block)
end
def generate_subkeys
@l = encrypt_block("\0" * BLOCK_SIZE)
@lu = subkey_shift(@l)
@lu2 = subkey_shift(@lu)
end
def subkey_shift(subkey)
msb, tail = subkey.unpack('B*').first.unpack('a a*')
left_shift = [tail, '0'].pack('B*')
msb == '1' ? xor(left_shift, RB) : left_shift
end
def xor(a, b)
a.bytes.zip(b.bytes).map { |x,y| (x^y).chr }.join
end
end
end