cmac 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ # CMAC [![Build Status](https://secure.travis-ci.org/jtdowney/cmac.png?branch=master)](https://travis-ci.org/jtdowney/cmac)
2
+
3
+ This gem is ruby implementation of the Cipher-based Message Authentication Code (CMAC) as defined in [RFC4493](http://tools.ietf.org/html/rfc4493), [RFC4494](http://tools.ietf.org/html/rfc4494), and [RFC4615](http://tools.ietf.org/html/rfc4615). Message authentication codes provide integrity protection of data given that two parties share a secret key.
4
+
5
+ ```ruby
6
+ key = OpenSSL::Random.random_bytes(16)
7
+ message = 'attack at dawn'
8
+ cmac = CMAC.new(key)
9
+ cmac.sign(message)
10
+ => "\xF6\xB8\xC1L]s\xBF\x1A\x87<\xA4\xA1Z\xE0f\xAA"
11
+ ```
12
+
13
+ Once you've obtained the signature (also called a tag) of a message you can use CMAC to verify it as well.
14
+
15
+ ```ruby
16
+ tag = "\xF6\xB8\xC1L]s\xBF\x1A\x87<\xA4\xA1Z\xE0f\xAA"
17
+ cmac.valid_message?(tag, message)
18
+ => true
19
+ cmac.valid_message?(tag, 'attack at dusk')
20
+ => false
21
+ ```
22
+
23
+ CMAC can also be used with a variable length input key as described in RFC4615.
24
+
25
+ ```ruby
26
+ key = 'setec astronomy'
27
+ message = 'attack at dawn'
28
+ cmac = CMAC.new(key)
29
+ cmac.sign(message)
30
+ => "\\\x11\x90\xE6\x91\xB2\xC4\x82`\x90\xA6\xEC:\x0E\x1C\xF3"
31
+ ```
32
+
33
+ ## License
34
+
35
+ The CMAC gem is released under the [MIT license](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,127 @@
1
+ require 'openssl'
2
+
3
+ require 'cmac/exception'
4
+ require 'cmac/version'
5
+
6
+ class CMAC
7
+ ZeroBlock = "\0" * 16
8
+ ConstantBlock = ("\0" * 15) + "\x87"
9
+
10
+ def initialize(key)
11
+ key.force_encoding('BINARY') if key.respond_to?(:force_encoding)
12
+ @key = _derive_key(key)
13
+ @key1, @key2 = _generate_subkeys(@key)
14
+ end
15
+
16
+ def sign(message, truncate = 16)
17
+ raise CMAC::Exception.new('Tag cannot be greater than maximum (16 bytes)') if truncate > 16
18
+ raise CMAC::Exception.new('Tag cannot be less than minimum (8 bytes)') if truncate < 8
19
+ message.force_encoding('BINARY') if message.respond_to?(:force_encoding)
20
+
21
+ if _needs_padding?(message)
22
+ message = _pad_message(message)
23
+ final_block = @key2
24
+ else
25
+ final_block = @key1
26
+ end
27
+
28
+ last_ciphertext = ZeroBlock
29
+ count = message.length / 16
30
+ range = Range.new(0, count - 1)
31
+ blocks = range.map { |i| message.slice(16 * i, 16) }
32
+ blocks.each_with_index do |block, i|
33
+ if i == range.last
34
+ block = _xor(final_block, block)
35
+ end
36
+
37
+ block = _xor(block, last_ciphertext)
38
+ last_ciphertext = _encrypt_block(@key, block)
39
+ end
40
+
41
+ last_ciphertext.slice(0, truncate)
42
+ end
43
+ alias :encrypt :sign
44
+
45
+ def valid_message?(tag, message)
46
+ other_tag = sign(message)
47
+ _secure_compare?(tag, other_tag)
48
+ end
49
+
50
+ def _derive_key(key)
51
+ if key.length == 16
52
+ key
53
+ else
54
+ cmac = CMAC.new(ZeroBlock)
55
+ cmac.encrypt(key)
56
+ end
57
+ end
58
+
59
+ def _encrypt_block(key, block)
60
+ cipher = OpenSSL::Cipher.new('AES-128-ECB')
61
+ cipher.encrypt
62
+ cipher.padding = 0
63
+ cipher.key = key
64
+ cipher.update(block) + cipher.final
65
+ end
66
+
67
+ def _generate_subkeys(key)
68
+ key0 = _encrypt_block(key, ZeroBlock)
69
+ key1 = _next_key(key0)
70
+ key2 = _next_key(key1)
71
+ [key1, key2]
72
+ end
73
+
74
+ def _needs_padding?(message)
75
+ message.length == 0 || message.length % 16 != 0
76
+ end
77
+
78
+ def _next_key(key)
79
+ if key[0].ord < 0x80
80
+ _leftshift(key)
81
+ else
82
+ _xor(_leftshift(key), ConstantBlock)
83
+ end
84
+ end
85
+
86
+ def _leftshift(input)
87
+ overflow = 0
88
+ words = input.unpack('N4').reverse
89
+ words = words.map do |word|
90
+ new_word = (word << 1) & 0xFFFFFFFF
91
+ new_word |= overflow
92
+ overflow = (word & 0x80000000) >= 0x80000000 ? 1 : 0
93
+ new_word
94
+ end
95
+ words.reverse.pack('N4')
96
+ end
97
+
98
+ def _pad_message(message)
99
+ padded_length = message.length + 16 - (message.length % 16)
100
+ message = message + "\x80"
101
+ message.ljust(padded_length, "\0")
102
+ end
103
+
104
+ def _secure_compare?(a, b)
105
+ return false unless a.bytesize == b.bytesize
106
+
107
+ bytes = a.unpack("C#{a.bytesize}")
108
+
109
+ result = 0
110
+ b.each_byte do |byte|
111
+ result |= byte ^ bytes.shift
112
+ end
113
+ result == 0
114
+ end
115
+
116
+ def _xor(a, b)
117
+ a.force_encoding('BINARY') if a.respond_to?(:force_encoding)
118
+ b.force_encoding('BINARY') if b.respond_to?(:force_encoding)
119
+
120
+ output = ''
121
+ length = [a.length, b.length].min
122
+ length.times do |i|
123
+ output << (a[i].ord ^ b[i].ord).chr
124
+ end
125
+ output
126
+ end
127
+ end
@@ -0,0 +1,4 @@
1
+ class CMAC
2
+ class Exception < StandardError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ class CMAC
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMAC do
4
+ describe 'sign' do
5
+ test_vectors.each do |name, options|
6
+ it "should match the \"#{name}\" test vector" do
7
+ cmac = CMAC.new(options[:Key])
8
+ cmac.sign(options[:Message], options[:Truncate].to_i).should == options[:Tag]
9
+ end
10
+ end
11
+
12
+ it 'should give a truncated output if requested' do
13
+ cmac = CMAC.new(TestKey)
14
+ cmac.sign('attack at dawn', 12).length.should == 12
15
+ end
16
+
17
+ it 'should raise error if truncation request is greater than 16 bytes' do
18
+ cmac = CMAC.new(TestKey)
19
+ expect do
20
+ cmac.sign('attack at dawn', 17)
21
+ end.to raise_error(CMAC::Exception, 'Tag cannot be greater than maximum (16 bytes)')
22
+ end
23
+
24
+ it 'should raise error if truncation request is less than 8 bytes' do
25
+ cmac = CMAC.new(TestKey)
26
+ expect do
27
+ cmac.sign('attack at dawn', 7)
28
+ end.to raise_error(CMAC::Exception, 'Tag cannot be less than minimum (8 bytes)')
29
+ end
30
+ end
31
+
32
+ describe 'valid_message?' do
33
+ it 'should be true for matching messages' do
34
+ message = 'attack at dawn'
35
+ cmac = CMAC.new(TestKey)
36
+ tag = cmac.sign(message)
37
+ cmac.should be_valid_message(tag, message)
38
+ end
39
+
40
+ it 'should be false for modified messages' do
41
+ cmac = CMAC.new(TestKey)
42
+ tag = cmac.sign('attack at dawn')
43
+ cmac.should_not be_valid_message(tag, 'attack at dusk')
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ require 'cmac'
2
+
3
+ TestKey = "\x01" * 16
4
+
5
+ RSpec.configure do |config|
6
+ config.order = 'random'
7
+ end
8
+
9
+ def test_vectors
10
+ test_file = File.expand_path('../test_vectors.txt', __FILE__)
11
+ test_lines = File.readlines(test_file).map(&:strip).reject(&:empty?)
12
+
13
+ vectors = {}
14
+ test_lines.each_slice(5) do |lines|
15
+ name = lines.shift
16
+ values = lines.inject({}) do |hash, line|
17
+ key, value = line.split('=').map(&:strip)
18
+ value = '' unless value
19
+ value = [value.slice(2..-1)].pack('H*') if value.start_with?('0x')
20
+ hash[key.to_sym] = value
21
+ hash
22
+ end
23
+ vectors[name] = values
24
+ end
25
+ vectors
26
+ end
@@ -0,0 +1,65 @@
1
+ Empty Message, no truncation
2
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
3
+ Message =
4
+ Truncate = 16
5
+ Tag = 0xbb1d6929e95937287fa37d129b756746
6
+
7
+ Message of 16 bytes, no truncation
8
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
9
+ Message = 0x6bc1bee22e409f96e93d7e117393172a
10
+ Truncate = 16
11
+ Tag = 0x070a16b46b4d4144f79bdd9dd04a287c
12
+
13
+ Message of 40 bytes, no truncation
14
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
15
+ Message = 0x6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411
16
+ Truncate = 16
17
+ Tag = 0xdfa66747de9ae63030ca32611497c827
18
+
19
+ Message of 64 bytes, no truncation
20
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
21
+ Message = 0x6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710
22
+ Truncate = 16
23
+ Tag = 0x51f0bebf7e3b9d92fc49741779363cfe
24
+
25
+ Empty message, 12 byte truncation
26
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
27
+ Message =
28
+ Truncate = 12
29
+ Tag = 0xbb1d6929e95937287fa37d12
30
+
31
+ Message of 16 bytes, 12 byte truncation
32
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
33
+ Message = 0x6bc1bee22e409f96e93d7e117393172a
34
+ Truncate = 12
35
+ Tag = 0x070a16b46b4d4144f79bdd9d
36
+
37
+ Message of 40 bytes, 12 byte truncation
38
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
39
+ Message = 0x6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411
40
+ Truncate = 12
41
+ Tag = 0xdfa66747de9ae63030ca3261
42
+
43
+ Message of 64 bytes, 12 byte truncation
44
+ Key = 0x2b7e151628aed2a6abf7158809cf4f3c
45
+ Message = 0x6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710
46
+ Truncate = 12
47
+ Tag = 0x51f0bebf7e3b9d92fc497417
48
+
49
+ Test Case AES-CMAC-PRF-128 with 20-octet input, 18 byte key
50
+ Key = 0x000102030405060708090a0b0c0d0e0fedcb
51
+ Message = 0x000102030405060708090a0b0c0d0e0f10111213
52
+ Truncate = 16
53
+ Tag = 0x84a348a4a45d235babfffc0d2b4da09a
54
+
55
+ Test Case AES-CMAC-PRF-128 with 20-octet input, 16 byte key
56
+ Key = 0x000102030405060708090a0b0c0d0e0f
57
+ Message = 0x000102030405060708090a0b0c0d0e0f10111213
58
+ Truncate = 16
59
+ Tag = 0x980ae87b5f4c9c5214f5b6a8455e4c2d
60
+
61
+ Test Case AES-CMAC-PRF-128 with 20-octet input, 10 byte key
62
+ Key = 0x00010203040506070809
63
+ Message = 0x000102030405060708090a0b0c0d0e0f10111213
64
+ Truncate = 16
65
+ Tag = 0x290d9e112edb09ee141fcf64c0b72f3d
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cmac
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Downey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.12.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.12.0
46
+ description: A ruby implementation of RFC4493, RFC4494, and RFC4615. CMAC is a message
47
+ authentication code (MAC) built using AES-128.
48
+ email:
49
+ - jdowney@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/cmac/exception.rb
55
+ - lib/cmac/version.rb
56
+ - lib/cmac.rb
57
+ - spec/cmac_spec.rb
58
+ - spec/spec_helper.rb
59
+ - spec/test_vectors.txt
60
+ - README.md
61
+ homepage: https://github.com/jtdowney/cmac
62
+ licenses: []
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ segments:
74
+ - 0
75
+ hash: -708155467333762105
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ segments:
83
+ - 0
84
+ hash: -708155467333762105
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.23
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Cipher-based Message Authentication Code
91
+ test_files:
92
+ - spec/cmac_spec.rb
93
+ - spec/spec_helper.rb
94
+ - spec/test_vectors.txt