openssl-cmac 1.0.0 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +12 -0
- data/.yardopts +4 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/Rakefile +31 -0
- data/lib/openssl/cmac/version.rb +5 -0
- data/lib/openssl/cmac.rb +171 -0
- data/test/test_cmac.rb +167 -0
- metadata +156 -23
- data/lib/openssl_cmac.rb +0 -69
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 16491a689fe7f5d899159d3762cd7618f3150bc2a1ecede264731ef308a5d876
|
4
|
+
data.tar.gz: b4df90a8d51cdba9649101217df4978181a93ea680ac67c3b6cd0b15939e55e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b26a6dd073ebdef048598ccec4a44ab0fe7510a785a5dc656cc28760dfa596b7f7e358abd3d1940709687dc1e4ad730348198a0bd9174df166636055715b2fc
|
7
|
+
data.tar.gz: 82292fbd9b159ca48b8062093c8a6c093960b52813717729a38648f880ad84c2b9843e91b5d42782e91ee51a38dd05288ea76b3bdbe4e8b4552667144a540d5b
|
data/.rubocop.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014, Maxim Chechel, Lars Schmertmann <SmallLars@t-online.de>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/openssl-cmac.png)](http://badge.fury.io/rb/openssl-cmac)
|
2
|
+
[![Dependency Status](https://gemnasium.com/SmallLars/openssl-cmac.png)](https://gemnasium.com/SmallLars/openssl-cmac)
|
3
|
+
[![Build Status](https://travis-ci.org/SmallLars/openssl-cmac.png?branch=master)](https://travis-ci.org/SmallLars/openssl-cmac)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/SmallLars/openssl-cmac/badge.png?branch=master)](https://coveralls.io/r/SmallLars/openssl-cmac)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/SmallLars/openssl-cmac.png)](https://codeclimate.com/github/SmallLars/openssl-cmac)
|
6
|
+
[![Inline docs](http://inch-ci.org/github/smalllars/openssl-cmac.png)](http://inch-ci.org/github/smalllars/openssl-cmac)
|
7
|
+
|
8
|
+
# openssl-cmac
|
9
|
+
|
10
|
+
Ruby Gem for
|
11
|
+
* [RFC 4493 - The AES-CMAC Algorithm](http://tools.ietf.org/html/rfc4493)
|
12
|
+
* [RFC 4494 - The AES-CMAC-96 Algorithm and Its Use with IPsec](http://tools.ietf.org/html/rfc4494)
|
13
|
+
* [RFC 4615 - The Advanced Encryption Standard-Cipher-based Message Authentication Code-Pseudo-Random Function-128 (AES-CMAC-PRF-128) Algorithm for the Internet Key Exchange Protocol (IKE)](http://tools.ietf.org/html/rfc4615)
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'openssl-cmac'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install openssl-cmac
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
Example 1:
|
32
|
+
|
33
|
+
require 'openssl/cmac'
|
34
|
+
mac = OpenSSL::CMAC.digest('AES', 'message', 'key')
|
35
|
+
|
36
|
+
Example 2:
|
37
|
+
|
38
|
+
require 'openssl/cmac'
|
39
|
+
cmac = OpenSSL::CMAC.new('AES', 'key')
|
40
|
+
cmac.update('message chunk 1')
|
41
|
+
...
|
42
|
+
cmac.update('message chunk n')
|
43
|
+
mac = cmac.digest
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require './lib/openssl/cmac/version'
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
task :default => :build
|
6
|
+
|
7
|
+
desc "Run tests"
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs << 'test'
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Create documentation"
|
13
|
+
task :doc do
|
14
|
+
sh "gem rdoc --rdoc openssl-cmac"
|
15
|
+
sh "yardoc"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Uninstall and clean documentation"
|
19
|
+
task :clean do
|
20
|
+
sh "gem uninstall openssl-cmac"
|
21
|
+
begin; sh "rm -R ./coverage"; rescue; end
|
22
|
+
begin; sh "rm -R ./.yardoc"; rescue; end
|
23
|
+
begin; sh "rm -R ./doc"; rescue; end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Development Dependencies"
|
27
|
+
task (:devinst) { sh "gem install --dev ./pkg/openssl-cmac-#{OpenSSL::CMAC::VERSION}.gem" }
|
28
|
+
|
29
|
+
desc "Bundle install"
|
30
|
+
task (:bundle) { sh "bundle install" }
|
31
|
+
|
data/lib/openssl/cmac.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module OpenSSL
|
4
|
+
# CMACError used for wrong parameter resonse.
|
5
|
+
class CMACError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Abstract from http://tools.ietf.org/html/rfc4493:
|
9
|
+
#
|
10
|
+
# The National Institute of Standards and Technology (NIST) has
|
11
|
+
# recently specified the Cipher-based Message Authentication Code
|
12
|
+
# (CMAC), which is equivalent to the One-Key CBC MAC1 (OMAC1) submitted
|
13
|
+
# by Iwata and Kurosawa. This memo specifies an authentication
|
14
|
+
# algorithm based on CMAC with the 128-bit Advanced Encryption Standard
|
15
|
+
# (AES). This new authentication algorithm is named AES-CMAC. The
|
16
|
+
# purpose of this document is to make the AES-CMAC algorithm
|
17
|
+
# conveniently available to the Internet Community.
|
18
|
+
#
|
19
|
+
# http://tools.ietf.org/html/rfc4494
|
20
|
+
# reduces the length of the result from 16 to 12 Byte.
|
21
|
+
#
|
22
|
+
# http://tools.ietf.org/html/rfc4615
|
23
|
+
# allows to use variable key sizes.
|
24
|
+
class CMAC
|
25
|
+
# Searches for supported algorithms within OpenSSL
|
26
|
+
#
|
27
|
+
# @return [[String]] supported algorithms
|
28
|
+
def self.ciphers
|
29
|
+
@ciphers ||= OpenSSL::Cipher.ciphers.select { |c| c.match(/-128-CBC$/i) }.map { |e| e[0..-9].upcase }.uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the authentication code as a binary string. The cipher parameter
|
33
|
+
# must be an entry of OpenSSL::CMAC.ciphers.
|
34
|
+
#
|
35
|
+
# @param cipher [String] entry of OpenSSL::CMAC.ciphers
|
36
|
+
# @param key [String] binary key string
|
37
|
+
# @param data [String] binary data string
|
38
|
+
# @param length [Number] length of the authentication code
|
39
|
+
#
|
40
|
+
# @return [String] authentication code
|
41
|
+
def self.digest(cipher, key, data, length = 16)
|
42
|
+
CMAC.new(cipher, key).update(data).digest(length)
|
43
|
+
end
|
44
|
+
|
45
|
+
public
|
46
|
+
|
47
|
+
# Returns an instance of OpenSSL::CMAC set with the cipher algorithm and
|
48
|
+
# key to be used. The instance represents the initial state of the message
|
49
|
+
# authentication code before any data has been processed. To process data
|
50
|
+
# with it, use the instance method update with your data as an argument.
|
51
|
+
#
|
52
|
+
# @param cipher [String] entry of OpenSSL::CMAC.ciphers
|
53
|
+
# @param key [String] binary key string
|
54
|
+
#
|
55
|
+
# @return [Object] the new CMAC object
|
56
|
+
def initialize(cipher, key = '')
|
57
|
+
unless CMAC.ciphers.include?(cipher.upcase)
|
58
|
+
fail CMACError, "unsupported cipher algorithm (#{cipher})"
|
59
|
+
end
|
60
|
+
|
61
|
+
@keys = []
|
62
|
+
@buffer = ''.force_encoding('ASCII-8BIT')
|
63
|
+
@cipher = OpenSSL::Cipher.new("#{cipher.upcase}-128-CBC")
|
64
|
+
|
65
|
+
self.key = key unless key == ''
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns self as it was when it was first initialized with new key,
|
69
|
+
# with all processed data cleared from it.
|
70
|
+
#
|
71
|
+
# @param key [String] binary key string
|
72
|
+
#
|
73
|
+
# @return [Object] self with initial state and new key
|
74
|
+
def key=(key)
|
75
|
+
reset
|
76
|
+
key = CMAC.digest('AES', "\x00" * 16, key, 16) unless key.b.length == 16
|
77
|
+
|
78
|
+
@keys[0] = key.dup
|
79
|
+
@cipher.key = @keys[0]
|
80
|
+
|
81
|
+
cipher = OpenSSL::Cipher.new(@cipher.name)
|
82
|
+
cipher.encrypt
|
83
|
+
cipher.key = @keys[0]
|
84
|
+
k = (cipher.update("\x00" * 16) + cipher.final).bytes[0...16]
|
85
|
+
1.upto(2) do |i|
|
86
|
+
k = k.pack('C*').unpack('B*')[0]
|
87
|
+
msb = k.slice!(0)
|
88
|
+
k = [k, '0'].pack('B*').bytes
|
89
|
+
k[15] ^= 0x87 if msb == '1'
|
90
|
+
@keys[i] = k.dup
|
91
|
+
end
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Alias for: update
|
96
|
+
def <<(data)
|
97
|
+
update(data)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the block length of the used cipher algorithm.
|
101
|
+
#
|
102
|
+
# @return [Number] length of the used cipher algorithm
|
103
|
+
def block_length
|
104
|
+
16
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the maximum length of the resulting digest.
|
108
|
+
#
|
109
|
+
# @return [Number] maximum length of the resulting digest
|
110
|
+
def digest_max_length
|
111
|
+
16
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the name of the used authentication code algorithm.
|
115
|
+
#
|
116
|
+
# @return [String] name of the used authentication code algorithm
|
117
|
+
def name
|
118
|
+
"CMAC with #{@cipher.name[0..-9]}"
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns self as it was when it was first initialized,
|
122
|
+
# with all processed data cleared from it.
|
123
|
+
#
|
124
|
+
# @return [Object] self with initial state
|
125
|
+
def reset
|
126
|
+
@keys.clear
|
127
|
+
@buffer.clear
|
128
|
+
@cipher.reset unless @keys[0].nil?
|
129
|
+
@cipher.iv = "\x00" * 16
|
130
|
+
@cipher.encrypt
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns self updated with the message to be authenticated.
|
135
|
+
# Can be called repeatedly with chunks of the message.
|
136
|
+
#
|
137
|
+
# @param data [String] binary data string
|
138
|
+
#
|
139
|
+
# @return [Object] self with new state
|
140
|
+
def update(data)
|
141
|
+
fail CMACError, 'no key is set' if @keys[0].nil?
|
142
|
+
|
143
|
+
@buffer += data
|
144
|
+
@cipher.update(@buffer.slice!(0...16)) while @buffer.length > 16
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the authentication code an instance represents as a binary string.
|
149
|
+
#
|
150
|
+
# @param length [Number] length of the authentication code
|
151
|
+
def digest(length = 16)
|
152
|
+
fail CMACError, 'no key is set' if @keys[0].nil?
|
153
|
+
fail CMACError, 'no key is set' unless length.between?(1, 16)
|
154
|
+
|
155
|
+
block = @buffer.bytes
|
156
|
+
@buffer.clear
|
157
|
+
k = @keys[block.length == 16 ? 1 : 2].dup
|
158
|
+
i = block.length.times { |t| k[t] ^= block[t] }
|
159
|
+
k[i] ^= 0x80 if i < 16
|
160
|
+
mac = @cipher.update(k.pack('C*')) + @cipher.final
|
161
|
+
@cipher.reset
|
162
|
+
@cipher.encrypt
|
163
|
+
@cipher.key = @keys[0]
|
164
|
+
@cipher.iv = "\x00" * 16
|
165
|
+
# Each block is 16-bytes and the last block will always be PKCS#7 padding
|
166
|
+
# which we want to discard. Take the last block prior to the padding for
|
167
|
+
# the MAC.
|
168
|
+
mac[-32...(-32 + length)]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/test/test_cmac.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
require 'test/unit'
|
4
|
+
require 'openssl/cmac'
|
5
|
+
|
6
|
+
# Testclass with Test Vectors from RFC's
|
7
|
+
class CMACTest < Test::Unit::TestCase
|
8
|
+
# http://tools.ietf.org/html/rfc4493#section-4
|
9
|
+
KEY = ['2b7e151628aed2a6abf7158809cf4f3c'].pack('H*')
|
10
|
+
DATA = [[''].pack('H*'),
|
11
|
+
['6bc1bee22e409f96e93d7e117393172a'].pack('H*'),
|
12
|
+
['6bc1bee22e409f96e93d7e117393172a'\
|
13
|
+
'ae2d8a571e03ac9c9eb76fac45af8e51'\
|
14
|
+
'30c81c46a35ce411'].pack('H*'),
|
15
|
+
['6bc1bee22e409f96e93d7e117393172a'\
|
16
|
+
'ae2d8a571e03ac9c9eb76fac45af8e51'\
|
17
|
+
'30c81c46a35ce411e5fbc1191a0a52ef'\
|
18
|
+
'f69f2445df4f9b17ad2b417be66c3710'].pack('H*')]
|
19
|
+
MAC = %w(bb1d6929e95937287fa37d129b756746
|
20
|
+
070a16b46b4d4144f79bdd9dd04a287c
|
21
|
+
dfa66747de9ae63030ca32611497c827
|
22
|
+
51f0bebf7e3b9d92fc49741779363cfe)
|
23
|
+
|
24
|
+
# http://tools.ietf.org/html/rfc4615#section-4
|
25
|
+
PRF_KEYS = [['000102030405060708090a0b0c0d0e0fedcb'].pack('H*'),
|
26
|
+
['000102030405060708090a0b0c0d0e0f'].pack('H*'),
|
27
|
+
['00010203040506070809'].pack('H*')]
|
28
|
+
PRF_DATA = ['000102030405060708090a0b0c0d0e0f10111213'].pack('H*')
|
29
|
+
PRF_OUTS = %w(84a348a4a45d235babfffc0d2b4da09a
|
30
|
+
980ae87b5f4c9c5214f5b6a8455e4c2d
|
31
|
+
290d9e112edb09ee141fcf64c0b72f3d)
|
32
|
+
|
33
|
+
def test_cmac_keys
|
34
|
+
cmac = OpenSSL::CMAC.new('AES')
|
35
|
+
cmac.key = KEY
|
36
|
+
check_keys(cmac)
|
37
|
+
|
38
|
+
cmac = OpenSSL::CMAC.new('AES', KEY)
|
39
|
+
check_keys(cmac)
|
40
|
+
|
41
|
+
assert(cmac.instance_variable_get(:@buffer).empty?, 'Wrong buffer')
|
42
|
+
cmac.update(DATA[2])
|
43
|
+
assert(cmac.instance_variable_get(:@buffer).length == 8, 'Wrong buffer')
|
44
|
+
cmac.update(DATA[2])
|
45
|
+
assert(cmac.instance_variable_get(:@buffer).length == 16, 'Wrong buffer')
|
46
|
+
|
47
|
+
cmac.reset
|
48
|
+
assert(cmac.instance_variable_get(:@keys)[0].nil?, 'Reset fail')
|
49
|
+
assert(cmac.instance_variable_get(:@keys)[1].nil?, 'Reset fail')
|
50
|
+
assert(cmac.instance_variable_get(:@keys)[2].nil?, 'Reset fail')
|
51
|
+
assert_equal('', cmac.instance_variable_get(:@buffer), 'Reset fail')
|
52
|
+
|
53
|
+
assert_raise(OpenSSL::CMACError) { cmac.update(DATA[2]) }
|
54
|
+
assert_raise(OpenSSL::CMACError) { cmac.digest }
|
55
|
+
|
56
|
+
cmac.key = KEY
|
57
|
+
check_keys(cmac)
|
58
|
+
|
59
|
+
m = cmac.update(DATA[2]).digest.unpack('H*')[0]
|
60
|
+
assert_equal(MAC[2], m)
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_keys(cmac)
|
64
|
+
assert_equal(
|
65
|
+
'2b7e151628aed2a6abf7158809cf4f3c',
|
66
|
+
cmac.instance_variable_get(:@keys)[0].unpack('H*')[0],
|
67
|
+
'Key ERROR'
|
68
|
+
)
|
69
|
+
assert_equal(
|
70
|
+
'fbeed618357133667c85e08f7236a8de',
|
71
|
+
cmac.instance_variable_get(:@keys)[1].pack('C*').unpack('H*')[0],
|
72
|
+
'SubKey 1 ERROR'
|
73
|
+
)
|
74
|
+
|
75
|
+
assert_equal(
|
76
|
+
'f7ddac306ae266ccf90bc11ee46d513b',
|
77
|
+
cmac.instance_variable_get(:@keys)[2].pack('C*').unpack('H*')[0],
|
78
|
+
'SubKey 2 ERROR'
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_cmac_vars
|
83
|
+
cmac = OpenSSL::CMAC.new('AES')
|
84
|
+
assert_equal(16, cmac.block_length)
|
85
|
+
assert_equal(16, cmac.digest_max_length)
|
86
|
+
assert_equal('CMAC with AES', cmac.name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_cmac_update
|
90
|
+
for cipher in ['aes', 'AES']
|
91
|
+
# Test with 1 call of update and new CCM object for each test.
|
92
|
+
DATA.length.times do |i|
|
93
|
+
cmac = OpenSSL::CMAC.new(cipher, KEY)
|
94
|
+
m = cmac.update(DATA[i]).digest.unpack('H*')[0]
|
95
|
+
assert_equal(MAC[i], m, "Test: 1, Vector: #{i + 1}")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Test with 1 call of update and same CCM object for each test.
|
99
|
+
# There is no reset, because it should be possible to calculate
|
100
|
+
# a new mac after digest without reset.
|
101
|
+
cmac = OpenSSL::CMAC.new(cipher, KEY)
|
102
|
+
DATA.length.times do |i|
|
103
|
+
m = cmac.update(DATA[i]).digest.unpack('H*')[0]
|
104
|
+
assert_equal(MAC[i], m, "Test: 2, Vector: #{i + 1}")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Test with multiple calls of update and new CCM object for each test
|
108
|
+
1.upto(DATA.length - 1) do |i|
|
109
|
+
1.upto(17) do |c|
|
110
|
+
cmac = OpenSSL::CMAC.new(cipher, KEY)
|
111
|
+
DATA[i].bytes.each_slice(c) { |w| cmac.update(w.pack('C*')) }
|
112
|
+
m = cmac.digest.unpack('H*')[0]
|
113
|
+
assert_equal(MAC[i], m, "Test: 3, Vector: #{i + 1}, Tokenlen: #{c}")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Test with multiple calls of update and same CCM object for each test
|
118
|
+
cmac = OpenSSL::CMAC.new(cipher, KEY)
|
119
|
+
1.upto(DATA.length - 1) do |i|
|
120
|
+
1.upto(17) do |c|
|
121
|
+
DATA[i].bytes.each_slice(c) { |w| cmac.update(w.pack('C*')) }
|
122
|
+
m = cmac.digest.unpack('H*')[0]
|
123
|
+
assert_equal(MAC[i], m, "Test: 4, Vector: #{i + 1}, Tokenlen: #{c}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Test for Operator <<
|
129
|
+
DATA[3].bytes.each_slice(5) { |w| cmac << w.pack('C*') }
|
130
|
+
m = cmac.digest.unpack('H*')[0]
|
131
|
+
assert_equal(MAC[3], m, 'Test: 5, Vector: 4, Tokenlen: 5')
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_cmac_digest
|
135
|
+
for cipher in ['aes', 'AES']
|
136
|
+
cmac = OpenSSL::CMAC.new(cipher, KEY)
|
137
|
+
m = cmac.update(DATA[3]).digest.unpack('H*')[0]
|
138
|
+
assert_equal(MAC[3], m, 'Digest with no update')
|
139
|
+
|
140
|
+
cmac.update(DATA[3].b[0...20])
|
141
|
+
m = cmac.update(DATA[3].b[20...64]).digest.unpack('H*')[0]
|
142
|
+
assert_equal(MAC[3], m, 'Digest after update')
|
143
|
+
|
144
|
+
cmac.update(DATA[3])
|
145
|
+
m = cmac.update('').digest.unpack('H*')[0]
|
146
|
+
assert_equal(MAC[3], m, 'Empty digest')
|
147
|
+
|
148
|
+
DATA.length.times do |i|
|
149
|
+
m = OpenSSL::CMAC.digest(cipher, KEY, DATA[i]).unpack('H*')[0]
|
150
|
+
assert_equal(MAC[i], m, "Vector: #{i + 1}")
|
151
|
+
|
152
|
+
m = OpenSSL::CMAC.digest(cipher, KEY, DATA[i], 12).unpack('H*')[0]
|
153
|
+
assert_equal(24, m.length, "Vector: #{i + 1} - length")
|
154
|
+
assert_equal(MAC[i][0...24], m, "Vector: #{i + 1} - 12")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_cmac_prf
|
160
|
+
cmac = OpenSSL::CMAC.new('AES')
|
161
|
+
3.times do |i|
|
162
|
+
cmac.key = PRF_KEYS[i]
|
163
|
+
m = cmac.update(PRF_DATA).digest.unpack('H*')[0]
|
164
|
+
assert_equal(PRF_OUTS[i], m, "Vector: #{i + 1}")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
metadata
CHANGED
@@ -1,46 +1,179 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openssl-cmac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
5
|
-
prerelease:
|
4
|
+
version: 2.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Maxim M. Chechel
|
9
|
-
|
8
|
+
- Lars Schmertmann
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
12
|
+
date: 2022-07-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '12.3'
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 12.3.2
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '12.3'
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 12.3.2
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rdoc
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.3'
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 4.3.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "~>"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '4.3'
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 4.3.0
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: yard
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.9'
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.9.16
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0.9'
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.9.16
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: rubocop
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0.50'
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 0.50.0
|
84
|
+
type: :development
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.50'
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.50.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: test-unit
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '3.2'
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.2.9
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.2'
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 3.2.9
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: coveralls
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0.8'
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 0.8.22
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0.8'
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 0.8.22
|
134
|
+
description: Ruby Gem for RFC 4493, 4494, 4615 - The AES-CMAC Algorithm
|
135
|
+
email:
|
136
|
+
- maximchick@gmail.com
|
137
|
+
- SmallLars@t-online.de
|
16
138
|
executables: []
|
17
139
|
extensions: []
|
18
|
-
extra_rdoc_files:
|
140
|
+
extra_rdoc_files:
|
141
|
+
- README.md
|
142
|
+
- LICENSE
|
19
143
|
files:
|
20
|
-
-
|
21
|
-
|
144
|
+
- ".rubocop.yml"
|
145
|
+
- ".yardopts"
|
146
|
+
- Gemfile
|
147
|
+
- LICENSE
|
148
|
+
- README.md
|
149
|
+
- Rakefile
|
150
|
+
- lib/openssl/cmac.rb
|
151
|
+
- lib/openssl/cmac/version.rb
|
152
|
+
- test/test_cmac.rb
|
153
|
+
homepage: https://github.com/smalllars/openssl-cmac
|
22
154
|
licenses:
|
23
155
|
- MIT
|
24
|
-
|
25
|
-
|
156
|
+
metadata: {}
|
157
|
+
post_install_message: Thanks for installing!
|
158
|
+
rdoc_options:
|
159
|
+
- "-x"
|
160
|
+
- test/data_*
|
26
161
|
require_paths:
|
27
162
|
- lib
|
28
163
|
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
-
none: false
|
30
164
|
requirements:
|
31
|
-
- -
|
165
|
+
- - ">="
|
32
166
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
167
|
+
version: 2.0.0
|
34
168
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
-
none: false
|
36
169
|
requirements:
|
37
|
-
- -
|
170
|
+
- - ">="
|
38
171
|
- !ruby/object:Gem::Version
|
39
172
|
version: '0'
|
40
173
|
requirements: []
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
174
|
+
rubygems_version: 3.3.15
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: RFC 4493, 4494, 4615 - CMAC
|
178
|
+
test_files:
|
179
|
+
- test/test_cmac.rb
|
data/lib/openssl_cmac.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# This is an implementation of AES-CMAC Algorithm:
|
2
|
-
# http://tools.ietf.org/html/rfc4493
|
3
|
-
#
|
4
|
-
# OpenSSL version > 1.0.1 already has a native implementation of CMAC
|
5
|
-
# but there are no corresponding bindings in Ruby OpenSSL standard library
|
6
|
-
|
7
|
-
require 'openssl'
|
8
|
-
|
9
|
-
module OpenSSL
|
10
|
-
class CMAC
|
11
|
-
CONST_ZERO = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".force_encoding('ASCII-8BIT')
|
12
|
-
CONST_RB = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]
|
13
|
-
|
14
|
-
# key - base 128 bit AES key
|
15
|
-
def initialize(key)
|
16
|
-
@key = key
|
17
|
-
@k1, @k2 = CMAC.gen_subkeys(@key)
|
18
|
-
end
|
19
|
-
|
20
|
-
def generate(data)
|
21
|
-
data8 = data.dup.force_encoding('ASCII-8BIT')
|
22
|
-
|
23
|
-
xor_key = @k1
|
24
|
-
unless data8.size > 0 && 0 == data8.size % 16
|
25
|
-
xor_key = @k2
|
26
|
-
padding = "\x80"
|
27
|
-
padding << "\x00" * (15 - data8.size % 16)
|
28
|
-
data8 << padding
|
29
|
-
end
|
30
|
-
|
31
|
-
data8[-16, 16].unpack('C*').each_with_index do |e, i|
|
32
|
-
data8[data8.size - 16 + i] = (e ^ xor_key[i]).chr
|
33
|
-
end
|
34
|
-
|
35
|
-
cipher = Cipher::AES.new(128, :CBC)
|
36
|
-
cipher.encrypt
|
37
|
-
cipher.key = @key
|
38
|
-
|
39
|
-
cipher.update(data8)[-16, 16]
|
40
|
-
end
|
41
|
-
|
42
|
-
def verify(data, cmac)
|
43
|
-
generate(data) == cmac
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.gen_subkeys(key)
|
47
|
-
cipher = Cipher::AES.new(128, :ECB)
|
48
|
-
cipher.encrypt
|
49
|
-
cipher.key = key
|
50
|
-
|
51
|
-
k1 = (cipher.update(CONST_ZERO)).unpack('C*')
|
52
|
-
xor_flag = k1[0] >= 0x80
|
53
|
-
|
54
|
-
k2 = Array.new(16)
|
55
|
-
|
56
|
-
k1.each_with_index {|e, i|
|
57
|
-
lsb = i == 15 ? 0 : (k1[i+1] & 0x80) / 0x80
|
58
|
-
k1[i] = (k1[i] << 1) % 256 | lsb
|
59
|
-
k1[i] ^= CONST_RB[i] if xor_flag
|
60
|
-
|
61
|
-
lsb = i == 15 ? 0 : (k1[i+1] << 1 & 0x80) / 0x80
|
62
|
-
k2[i] = (k1[i] << 1) % 256 | lsb
|
63
|
-
k2[i] ^= CONST_RB[i] if k1[0] >= 0x80
|
64
|
-
}
|
65
|
-
|
66
|
-
[k1, k2]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|