krypt-cmac 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +156 -0
- data/lib/krypt/cmac/cmac_96.rb +31 -0
- data/lib/krypt/cmac/cmac_prf_128.rb +48 -0
- data/lib/krypt/cmac/errors.rb +10 -0
- data/lib/krypt/cmac/version.rb +5 -0
- data/lib/krypt/cmac.rb +245 -0
- data/spec/krypt/cmac/aes-cmac-prf-128_spec.rb +16 -0
- data/spec/krypt/cmac/aes_128_spec.rb +16 -0
- data/spec/krypt/cmac/aes_192_spec.rb +16 -0
- data/spec/krypt/cmac/aes_256_spec.rb +16 -0
- data/spec/krypt/cmac/aes_cmac_96_spec.rb +16 -0
- data/spec/krypt/cmac/cmac_spec.rb +82 -0
- data/spec/shared_contexts/reference_samples.rb +73 -0
- data/spec/shared_contexts/string_helpers.rb +17 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/reference_samples.rb +129 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 625c065a66091f29a4ba6f4c362021440776de5c970f0bab96acf746c4b4cee5
|
|
4
|
+
data.tar.gz: 955abe821538bf06e71ebe8bef363693acaac7ecec8654f2673ec6472e0b69dc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8354e0d2338081a7600f14d977c534ea25c4c67ba392ff4eaa28aae1b4229a9a291c6f3de071ca71472679b58fff54f1476054be35ada47b304c268e1ccc54fa
|
|
7
|
+
data.tar.gz: 5ca01c08aa99204e1b509db1298024ddc5dd0b6d1fea419a72a58b17f785d389454bdd94e3ad08da02db69977f06a7ef43e4443cce52e73e252f45d85f87e474
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Martin Boßlet
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Krypt::Cmac
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/krypt-cmac)
|
|
4
|
+
|
|
5
|
+
First off, don't use CMAC unless you really need to. HMAC is usually faster, more robust, and easier to use.
|
|
6
|
+
Only go for CMAC if it's already been decided and you need to work with it.
|
|
7
|
+
|
|
8
|
+
Krypt::Cmac provides implementations for all versions of the AES-CMAC algorithm as specified in:
|
|
9
|
+
|
|
10
|
+
- [NIST SP 800-38B](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38b.pdf)
|
|
11
|
+
- [RFC 4493](https://tools.ietf.org/html/rfc4493)
|
|
12
|
+
- [RFC 4494](https://tools.ietf.org/html/rfc4494)
|
|
13
|
+
- [RFC 4615](https://tools.ietf.org/html/rfc4615)
|
|
14
|
+
|
|
15
|
+
It supports 128, 192, and 256-bit keys, variable length keys, and can handle streaming processing.
|
|
16
|
+
Only AES is supported as the underlying block cipher algorithm. The implementations offer the same
|
|
17
|
+
public API as `OpenSSL::HMAC` except for the `reset` method.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add this line to your application's Gemfile:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'krypt-cmac'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
And then execute:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If you aren't using `bundler` for managing dependencies, you may install the gem directly by executing:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
gem install krypt-cmac
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### AES-CMAC with 128, 192 or 256 bits (NIST SP 800-38B, RFC 4493)
|
|
42
|
+
|
|
43
|
+
When using the default implementation, the size of the key determines the version of AES being used. This
|
|
44
|
+
implies that keys must be either 128, 192 or 256 bits long, resulting in AES-128-CMAC, AES-192-CMAC or
|
|
45
|
+
AES-256-CMAC tags. See below for variable key lengths.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require 'krypt/cmac'
|
|
49
|
+
|
|
50
|
+
key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
|
|
51
|
+
message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")
|
|
52
|
+
|
|
53
|
+
# One-shot computation
|
|
54
|
+
cmac = Krypt::Cmac.new(key)
|
|
55
|
+
tag = cmac.digest(message)
|
|
56
|
+
|
|
57
|
+
# Streaming computation
|
|
58
|
+
cmac = Krypt::Cmac.new(key)
|
|
59
|
+
cmac.update(message)
|
|
60
|
+
tag = cmac.digest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### AES-CMAC-96 (RFC 4494)
|
|
64
|
+
|
|
65
|
+
To generate CMAC tags that are 96 bits long instead of the default 128 bits, use `Krypt::Cmac::Cmac96`.
|
|
66
|
+
Note that CMAC-96 tags are simply regular tags truncated to 96 bits. If you need any other tag size
|
|
67
|
+
below 128 bits, you can truncate the regular tag manually.
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
require 'krypt/cmac'
|
|
71
|
+
|
|
72
|
+
key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
|
|
73
|
+
message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")
|
|
74
|
+
|
|
75
|
+
# AES-CMAC-96 one-shot computation
|
|
76
|
+
cmac = Krypt::Cmac::Cmac96.new(key)
|
|
77
|
+
tag = cmac.digest(message)
|
|
78
|
+
|
|
79
|
+
# AES-CMAC-96 streaming computation
|
|
80
|
+
cmac = Krypt::Cmac::Cmac96.new(key)
|
|
81
|
+
cmac.update(message)
|
|
82
|
+
tag = cmac.digest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### AES-CMAC-PRF-128 (RFC 4615)
|
|
86
|
+
|
|
87
|
+
If you need to generate CMAC tags from keys of varying lengths and not the usual 128, 192, or 256 bit
|
|
88
|
+
range, use AES-CMAC-PRF-128 as provided by `Krypt::Cmac::CmacPrf128`. It computes a regular AES-CMAC on the
|
|
89
|
+
key first and uses the 128 bit result as the actual key for CMAC computation. You might also use it if
|
|
90
|
+
you need an AES-128-CMAC tag for keys that are 192 or 256 bits long. Using regular AES-CMAC with such
|
|
91
|
+
keys would compute AES-192-CMAC and AES-256-CMAC tags respectively.
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
require 'krypt/cmac'
|
|
95
|
+
|
|
96
|
+
key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
|
|
97
|
+
message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")
|
|
98
|
+
|
|
99
|
+
# AES-CMAC-PRF-128 one-shot computation
|
|
100
|
+
cmac = Krypt::Cmac::CmacPrf128.new(key)
|
|
101
|
+
tag = cmac.digest(message)
|
|
102
|
+
|
|
103
|
+
# AES-CMAC-PRF-128 streaming computation
|
|
104
|
+
cmac = Krypt::Cmac::CmacPrf128.new(key)
|
|
105
|
+
cmac.update(message)
|
|
106
|
+
tag = cmac.digest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Tag verification
|
|
110
|
+
|
|
111
|
+
Verifying a given tag means recomputing it and then comparing the two. However, it is crucial for security reasons
|
|
112
|
+
to avoid comparing them with `==` or similar comparisons subject to
|
|
113
|
+
[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation). To securely verify a tag, use:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
require 'krypt/cmac'
|
|
117
|
+
|
|
118
|
+
key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
|
|
119
|
+
message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")
|
|
120
|
+
tag = ["070a16b46b4d4144f79bdd9dd04a287c"].pack("H*")
|
|
121
|
+
|
|
122
|
+
# Verifying a tag with data supplied to the method
|
|
123
|
+
cmac = Krypt::Cmac.new(key)
|
|
124
|
+
valid = cmac.verify(tag, message)
|
|
125
|
+
|
|
126
|
+
# Verifying a tag without data supplied to the method
|
|
127
|
+
cmac = Krypt::Cmac.new(key)
|
|
128
|
+
cmac.update(message)
|
|
129
|
+
begin
|
|
130
|
+
valid = cmac.verify(tag)
|
|
131
|
+
puts "Tag successfully verified"
|
|
132
|
+
rescue Krypt::Cmac::TagMismatchError => e
|
|
133
|
+
# tag invalid
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Even though the `verify` method returns `true` on successful verification, it still raises a
|
|
138
|
+
`Krypt::Cmac::TagMismatchError` on invalid tags. This ensures that invalid tags cannot go undetected if the
|
|
139
|
+
verifying code forgets to check for `true` explicitly.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
|
|
144
|
+
also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
145
|
+
|
|
146
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
|
147
|
+
version number in `lib/krypt/cmac/version.rb`, and then run `bundle exec rake release`, which will create a git tag for
|
|
148
|
+
the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/krypt/krypt-cmac.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Krypt
|
|
2
|
+
class Cmac
|
|
3
|
+
# Implements AES-CMAC-96 as defined in RFC 4494. Computes a regular
|
|
4
|
+
# 128 bit tag and truncates it at 96 bit. All methods of Krypt::Cmac are available.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
|
|
8
|
+
# message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")
|
|
9
|
+
# prf = Krypt::Cmac::Cmac96.new(key)
|
|
10
|
+
# tag = prf.digest("data")
|
|
11
|
+
#
|
|
12
|
+
# @see Krypt::Cmac
|
|
13
|
+
#
|
|
14
|
+
# References:
|
|
15
|
+
# - AES-CMAC-96 RFC: https://tools.ietf.org/html/rfc4494
|
|
16
|
+
class Cmac96 < Cmac
|
|
17
|
+
def initialize(key)
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns the computed MAC tag as a 96-bit string as described in RFC 4494.
|
|
22
|
+
#
|
|
23
|
+
# @param data [String] The data to update the CMAC computation with before finalizing.
|
|
24
|
+
# If nil, the CMAC computation is finalized without updating with any data.
|
|
25
|
+
# @return [String] The computed MAC tag as a 96-bit string.
|
|
26
|
+
def digest(data = nil)
|
|
27
|
+
super.byteslice(0, 12)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Krypt
|
|
2
|
+
class Cmac
|
|
3
|
+
# Implements AES-CMAC-PRF-128 as defined in RFC 4615. Variable length keys are supported,
|
|
4
|
+
# but if the key is not 128 bits long, it is derived using the AES-CMAC algorithm to enforce
|
|
5
|
+
# a 128 bit key and a CMAC computed with AES-128. All methods of Krypt::Cmac are available.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# key = ["0102030405"].pack("H*")
|
|
9
|
+
# prf = Krypt::Cmac::CmacPrf128.new(key)
|
|
10
|
+
# tag = prf.digest("data")
|
|
11
|
+
#
|
|
12
|
+
# @see Krypt::Cmac
|
|
13
|
+
#
|
|
14
|
+
# References:
|
|
15
|
+
# - AES-CMAC-PRF-128 RFC: https://tools.ietf.org/html/rfc4615
|
|
16
|
+
class CmacPrf128
|
|
17
|
+
def initialize(key)
|
|
18
|
+
@key = derive_key(key)
|
|
19
|
+
@cmac = Krypt::Cmac.new(@key)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Delegates to the underlying Krypt::Cmac instance.
|
|
23
|
+
def method_missing(method, *args, &block)
|
|
24
|
+
if @cmac.respond_to?(method)
|
|
25
|
+
@cmac.send(method, *args, &block)
|
|
26
|
+
else
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Delegates to the underlying Krypt::Cmac instance.
|
|
32
|
+
def respond_to_missing?(method, include_private = false)
|
|
33
|
+
@cmac.respond_to?(method) || super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def derive_key(key)
|
|
39
|
+
# If the key is already 128 bits, return it as is
|
|
40
|
+
return key if key.bytesize == 16
|
|
41
|
+
|
|
42
|
+
# Otherwise, derive a 128-bit key using the AES-CMAC algorithm
|
|
43
|
+
cmac = Krypt::Cmac.new(Krypt::Cmac::BLOCK_OF_ZEROS)
|
|
44
|
+
cmac.digest(key)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Krypt
|
|
2
|
+
class Cmac
|
|
3
|
+
# Raised when the CMAC is in an invalid state for the operation, e.g. when calling `update` after `digest`
|
|
4
|
+
# or by supplying additional data to `digest` after finalization.
|
|
5
|
+
class InvalidStateError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when MAC tag verification fails.
|
|
8
|
+
class TagMismatchError < StandardError; end
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/krypt/cmac.rb
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
require_relative "cmac/version"
|
|
3
|
+
require_relative "cmac/errors"
|
|
4
|
+
require_relative "cmac/cmac_96"
|
|
5
|
+
require_relative "cmac/cmac_prf_128"
|
|
6
|
+
|
|
7
|
+
module Krypt
|
|
8
|
+
# Based on the CMAC algorithm described in RFC 4493 and NIST SP 800-38B.
|
|
9
|
+
# Calculates a message authentication code (MAC) for a message using AES as the block cipher.
|
|
10
|
+
# The underlying AES key size can be 128, 192, or 256 bits, this governs the
|
|
11
|
+
# version of AES being used to compute the MAC. The MAC size is always 128 bits.
|
|
12
|
+
#
|
|
13
|
+
# If a 96-bit MAC is required (as in RFC 4494), {#cmac_96} can be used. If other
|
|
14
|
+
# reduced versions are required, the MAC tag can be truncated manually after calling
|
|
15
|
+
# {#digest}.
|
|
16
|
+
#
|
|
17
|
+
# If variable length keys such as in AES-CMAC-PRF-128 (RFC 4615) must be supported,
|
|
18
|
+
# or if the MAC shall be computed with AES-128 - regardless of the key length,
|
|
19
|
+
# {Krypt::Cmac::CmacPrf128} can be used. This class derives a 128-bit key from the
|
|
20
|
+
# given key using the AES-CMAC algorithm.
|
|
21
|
+
#
|
|
22
|
+
# The CMAC computation can be updated with data in multiple calls to {#update}
|
|
23
|
+
# or by using the << operator. The MAC tag is finalized by calling {#digest}.
|
|
24
|
+
# The computation can be updated with data before finalizing by passing the
|
|
25
|
+
# data as an argument to {#digest}, allowing for one-shot tag computation.
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
|
|
29
|
+
# message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")
|
|
30
|
+
# message2 = ["ae2d8a57"].pack("H*")
|
|
31
|
+
#
|
|
32
|
+
# # One-shot computation
|
|
33
|
+
# cmac = Krypt::Cmac.new(key)
|
|
34
|
+
# tag = cmac.digest(message)
|
|
35
|
+
#
|
|
36
|
+
# # Streaming computation
|
|
37
|
+
# cmac = Krypt::Cmac.new(key)
|
|
38
|
+
# cmac.update(message) # Or: cmac << message
|
|
39
|
+
# cmac.update(message2)
|
|
40
|
+
# tag = cmac.digest
|
|
41
|
+
#
|
|
42
|
+
# # Streaming computation with chaining
|
|
43
|
+
# tag = Krypt::Cmac.new(key).update(message).update(message2).digest
|
|
44
|
+
#
|
|
45
|
+
# @see Krypt::Cmac::Cmac96
|
|
46
|
+
# @see Krypt::Cmac::CmacPrf128
|
|
47
|
+
#
|
|
48
|
+
# References:
|
|
49
|
+
# - NIST SP 800-38B: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38b.pdf
|
|
50
|
+
# - CMAC RFC: https://tools.ietf.org/html/rfc4493
|
|
51
|
+
# - AES-CMAC-96 RFC: https://tools.ietf.org/html/rfc4494
|
|
52
|
+
# - AES-CMAC-PRF-128 RFC: https://tools.ietf.org/html/rfc4615
|
|
53
|
+
class Cmac
|
|
54
|
+
# The constant string for subkey generation for a cipher with block size 128.
|
|
55
|
+
# Note that this is the same for different key lengths, as AES block size is always 128 bits.
|
|
56
|
+
RB = 0x87
|
|
57
|
+
# AES block size in bytes, which is always 128 (i.e. 16 bytes) regardless of key length.
|
|
58
|
+
AES_BLOCK_SIZE = 16
|
|
59
|
+
# A block of zeros, used for padding and initialization vectors.
|
|
60
|
+
BLOCK_OF_ZEROS = "\0".b * AES_BLOCK_SIZE
|
|
61
|
+
|
|
62
|
+
attr_reader :key, :k1, :k2, :l, :mac_tag
|
|
63
|
+
|
|
64
|
+
# Creates a new CMAC instance with the given key. The key length determines
|
|
65
|
+
# the version of AES to use for the CMAC computation.
|
|
66
|
+
#
|
|
67
|
+
# @param key [String] The key to use for the CMAC computation.
|
|
68
|
+
# The key must be 128, 192, or 256 bits long, selecting the AES version to use.
|
|
69
|
+
# @raise [ArgumentError] If the key length is not 128, 192, or 256 bits.
|
|
70
|
+
def initialize(key)
|
|
71
|
+
unless [16, 24, 32].include?(key.bytesize)
|
|
72
|
+
raise ArgumentError, "Key must be 128, 192, or 256 bits long"
|
|
73
|
+
end
|
|
74
|
+
@key = key.b
|
|
75
|
+
@k1, @k2, @l = generate_subkeys(@key)
|
|
76
|
+
@buffer = "".b
|
|
77
|
+
@aes_cbc = cipher_for_key(@key, :CBC)
|
|
78
|
+
@aes_cbc.iv = BLOCK_OF_ZEROS
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Updates the CMAC computation with the given data. The data can be any
|
|
82
|
+
# string, and the CMAC computation is updated with the data. The data can
|
|
83
|
+
# be given in multiple calls to update, and the CMAC computation is
|
|
84
|
+
# updated with each call.
|
|
85
|
+
#
|
|
86
|
+
# @param data [String] The data to update the CMAC computation with.
|
|
87
|
+
# @return [self] The CMAC instance itself, to allow chaining.
|
|
88
|
+
# @raise [InvalidStateError] If the CMAC has already been finalized.
|
|
89
|
+
def update(data)
|
|
90
|
+
return self if data.nil?
|
|
91
|
+
raise InvalidStateError.new("CMAC has already been finalized") if @mac_tag
|
|
92
|
+
|
|
93
|
+
@buffer << data
|
|
94
|
+
buffer_len = @buffer.bytesize
|
|
95
|
+
|
|
96
|
+
# Return early if the buffer does not contain enough data to form a block
|
|
97
|
+
return self if buffer_len <= AES_BLOCK_SIZE
|
|
98
|
+
|
|
99
|
+
# Ensure that we do not process the final block yet. The final block is
|
|
100
|
+
# processed in the digest method. This is to ensure that the final block
|
|
101
|
+
# is padded correctly. For now, just process the remaining full blocks.
|
|
102
|
+
remainder = buffer_len % AES_BLOCK_SIZE
|
|
103
|
+
remainder = AES_BLOCK_SIZE if remainder == 0
|
|
104
|
+
@aes_cbc.update(@buffer.slice!(0...-remainder))
|
|
105
|
+
|
|
106
|
+
self # Return self to allow chaining
|
|
107
|
+
end
|
|
108
|
+
# Allow the << operator to be used as an alias for update.
|
|
109
|
+
# This allows the CMAC computation to be updated with the << operator.
|
|
110
|
+
# (@see #update)
|
|
111
|
+
alias_method :<<, :update
|
|
112
|
+
|
|
113
|
+
# Finalizes the CMAC computation and returns the computed MAC tag. If data
|
|
114
|
+
# is given, the CMAC computation is updated with the data before finalizing.
|
|
115
|
+
# The CMAC computation is finalized after calling this method, and no further
|
|
116
|
+
# updates are allowed.
|
|
117
|
+
#
|
|
118
|
+
# @param data [String] The data to update the CMAC computation with before finalizing.
|
|
119
|
+
# If nil, the CMAC computation is finalized without updating with any data.
|
|
120
|
+
# @return [String] The computed MAC tag.
|
|
121
|
+
# @raise [ArgumentError] If data is given after the CMAC has already been finalized.
|
|
122
|
+
def digest(data = nil)
|
|
123
|
+
raise ArgumentError.new("CMAC has already been finalized") if data && @mac_tag
|
|
124
|
+
return @mac_tag if @mac_tag
|
|
125
|
+
|
|
126
|
+
update(data) if data
|
|
127
|
+
@mac_tag = @aes_cbc.update(pad_last_block(@buffer)) # No need to call final because no padding is used
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns the computed MAC tag as a hex-encoded string.
|
|
131
|
+
#
|
|
132
|
+
# @param data [String] The data to update the CMAC computation with before finalizing.
|
|
133
|
+
# If nil, the CMAC computation is finalized without updating with any data.
|
|
134
|
+
# @return [String] The computed MAC tag as a hex-encoded string.
|
|
135
|
+
def hexdigest(data = nil)
|
|
136
|
+
digest(data).unpack1("H*")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns the computed MAC tag as a Base64-encoded string.
|
|
140
|
+
#
|
|
141
|
+
# @param data [String] The data to update the CMAC computation with before finalizing.
|
|
142
|
+
# If nil, the CMAC computation is finalized without updating with any data.
|
|
143
|
+
# @return [String] The computed MAC tag as a Base64-encoded string.
|
|
144
|
+
def base64digest(data = nil)
|
|
145
|
+
[digest(data)].pack("m0")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Verifies the given MAC tag against the computed MAC tag for the given data.
|
|
149
|
+
#
|
|
150
|
+
# @param tag [String] The MAC tag to verify.
|
|
151
|
+
# @param data [String] The data to verify the MAC tag against.
|
|
152
|
+
# If nil, the MAC tag is verified against the current CMAC computation.
|
|
153
|
+
# @return [Boolean] True if the MAC tag is verified. Raises otherwise.
|
|
154
|
+
# @raise [TagMismatchError] If the MAC tag verification fails.
|
|
155
|
+
def verify(tag, data = nil)
|
|
156
|
+
if !secure_compare(tag, digest(data))
|
|
157
|
+
raise TagMismatchError.new("MAC tag verification failed, the tags do not match")
|
|
158
|
+
end
|
|
159
|
+
true # Needs not be checked because an error is raised if the tags do not match
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Compares the CMAC instance with another CMAC instance. The comparison is
|
|
163
|
+
# done by comparing the computed MAC tags. The comparison is done in constant
|
|
164
|
+
# time to prevent timing attacks.
|
|
165
|
+
#
|
|
166
|
+
# @param other [Krypt::Cmac] The other CMAC instance to compare with.
|
|
167
|
+
# @return [Boolean] True if the MAC tags are equal, false otherwise.
|
|
168
|
+
def ==(other)
|
|
169
|
+
return false unless Cmac === other
|
|
170
|
+
return false unless digest.bytesize == other.digest.bytesize
|
|
171
|
+
OpenSSL.fixed_length_secure_compare(digest, other.digest)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def cipher_for_key(key, mode)
|
|
177
|
+
algorithm = "AES-#{key.size * 8}-#{mode}"
|
|
178
|
+
OpenSSL::Cipher.new(algorithm).tap do |cipher|
|
|
179
|
+
cipher.encrypt
|
|
180
|
+
cipher.key = key
|
|
181
|
+
cipher.padding = 0 # Do not use padding
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def generate_subkeys(key)
|
|
186
|
+
aes_ecb = cipher_for_key(key, :ECB)
|
|
187
|
+
l = aes_ecb.update(BLOCK_OF_ZEROS) # No need to call final because no padding is used
|
|
188
|
+
k1 = generate_subkey(l)
|
|
189
|
+
k2 = generate_subkey(k1)
|
|
190
|
+
[k1, k2, l]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def generate_subkey(bytes)
|
|
194
|
+
msb = most_significant_bit_set?(bytes)
|
|
195
|
+
k = shift_left(bytes)
|
|
196
|
+
k.setbyte(-1, k.getbyte(-1) ^ RB) if msb
|
|
197
|
+
k
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def pad_last_block(buffer)
|
|
201
|
+
len = buffer.bytesize # 0 < len <= AES_BLOCK_SIZE
|
|
202
|
+
|
|
203
|
+
if len == AES_BLOCK_SIZE
|
|
204
|
+
xor_block(buffer, k1)
|
|
205
|
+
else
|
|
206
|
+
buffer << "\x80".b # Append 1 bit (0x80 in hex) with ASCII-8BIT encoding
|
|
207
|
+
buffer << "\x00".b * (AES_BLOCK_SIZE - (len + 1)) # Pad the rest with 0 bits
|
|
208
|
+
xor_block(buffer, k2)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def shift_left(byte_string)
|
|
213
|
+
bytes = byte_string.bytes
|
|
214
|
+
carry = 0
|
|
215
|
+
|
|
216
|
+
bytes.reverse_each.with_index do |byte, i|
|
|
217
|
+
new_carry = (byte & 0x80) >> 7
|
|
218
|
+
# Update bytes beginning from the end, so use negative index plus 1
|
|
219
|
+
# i=0 => bytes[-1], i=1 => bytes[-2], etc.
|
|
220
|
+
# Shift left, clear the LSB to ensure it is 0 with 0xFE (whose LSB is 0), and finally add carry
|
|
221
|
+
bytes[-(i + 1)] = ((byte << 1) & 0xFE) | carry
|
|
222
|
+
carry = new_carry
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
bytes.pack("C*") # Convert bytes back to a string
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def most_significant_bit_set?(bytes)
|
|
229
|
+
most_significant_bit(bytes) != 0
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def most_significant_bit(bytes)
|
|
233
|
+
bytes.unpack1("C") & 0x80
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def xor_block(block, key)
|
|
237
|
+
block.bytes.each_with_index.map { |b, i| b ^ key.getbyte(i) }.pack("C*")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def secure_compare(a, b)
|
|
241
|
+
return false unless a.bytesize == b.bytesize
|
|
242
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe Krypt::Cmac::CmacPrf128 do
|
|
2
|
+
include_context "String helpers"
|
|
3
|
+
include_context "reference samples"
|
|
4
|
+
|
|
5
|
+
let(:data) { hex_to_bin(ReferenceSamples::AES_PRF_128[:data]) }
|
|
6
|
+
|
|
7
|
+
(ReferenceSamples::AES_PRF_128.keys - %i[data]).each do |sample_key|
|
|
8
|
+
sample = ReferenceSamples::AES_PRF_128[sample_key]
|
|
9
|
+
context sample[:description] do
|
|
10
|
+
let(:key) { hex_to_bin(sample[:key]) }
|
|
11
|
+
let(:expected_mac) { hex_to_bin(sample[:tag]) }
|
|
12
|
+
|
|
13
|
+
it_behaves_like "correct AES-CMAC-PRF-128"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe "Krypt::Cmac with AES-128 key" do
|
|
2
|
+
include_context "String helpers"
|
|
3
|
+
include_context "reference samples"
|
|
4
|
+
|
|
5
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_128[:key]) }
|
|
6
|
+
|
|
7
|
+
ReferenceSamples.sample_keys.each do |sample_key|
|
|
8
|
+
sample = ReferenceSamples::AES_128[sample_key]
|
|
9
|
+
context sample[:description] do
|
|
10
|
+
let(:data) { hex_to_bin(sample[:data]) }
|
|
11
|
+
let(:expected_mac) { hex_to_bin(sample[:tag]) }
|
|
12
|
+
|
|
13
|
+
it_behaves_like "correct CMAC"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe "Krypt::Cmac with AES-192 key" do
|
|
2
|
+
include_context "String helpers"
|
|
3
|
+
include_context "reference samples"
|
|
4
|
+
|
|
5
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_192[:key]) }
|
|
6
|
+
|
|
7
|
+
ReferenceSamples.sample_keys.each do |sample_key|
|
|
8
|
+
sample = ReferenceSamples::AES_192[sample_key]
|
|
9
|
+
context sample[:description] do
|
|
10
|
+
let(:data) { hex_to_bin(sample[:data]) }
|
|
11
|
+
let(:expected_mac) { hex_to_bin(sample[:tag]) }
|
|
12
|
+
|
|
13
|
+
it_behaves_like "correct CMAC"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe "Krypt::Cmac with AES-256 key" do
|
|
2
|
+
include_context "String helpers"
|
|
3
|
+
include_context "reference samples"
|
|
4
|
+
|
|
5
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_256[:key]) }
|
|
6
|
+
|
|
7
|
+
ReferenceSamples.sample_keys.each do |sample_key|
|
|
8
|
+
sample = ReferenceSamples::AES_256[sample_key]
|
|
9
|
+
context sample[:description] do
|
|
10
|
+
let(:data) { hex_to_bin(sample[:data]) }
|
|
11
|
+
let(:expected_mac) { hex_to_bin(sample[:tag]) }
|
|
12
|
+
|
|
13
|
+
it_behaves_like "correct CMAC"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe Krypt::Cmac::Cmac96 do
|
|
2
|
+
include_context "String helpers"
|
|
3
|
+
include_context "reference samples"
|
|
4
|
+
|
|
5
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_CMAC_96[:key]) }
|
|
6
|
+
|
|
7
|
+
ReferenceSamples.sample_keys.each do |sample_key|
|
|
8
|
+
sample = ReferenceSamples::AES_CMAC_96[sample_key]
|
|
9
|
+
context sample[:description] do
|
|
10
|
+
let(:data) { hex_to_bin(sample[:data]) }
|
|
11
|
+
let(:expected_mac) { hex_to_bin(sample[:tag]) }
|
|
12
|
+
|
|
13
|
+
it_behaves_like "correct AES-CMAC-96"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
RSpec.describe Krypt::Cmac do
|
|
2
|
+
include_context "String helpers"
|
|
3
|
+
|
|
4
|
+
it "has a version number" do
|
|
5
|
+
expect(Krypt::Cmac::VERSION).not_to be nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
context "general usage" do
|
|
9
|
+
let(:sample) { ReferenceSamples::AES_128[:single_block] }
|
|
10
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_128[:key]) }
|
|
11
|
+
let(:data) { hex_to_bin(sample[:data]) }
|
|
12
|
+
let(:tag) { hex_to_bin(sample[:tag]) }
|
|
13
|
+
|
|
14
|
+
subject { Krypt::Cmac.new(key) }
|
|
15
|
+
|
|
16
|
+
it "initializes with a key" do
|
|
17
|
+
expect(subject.key).to eq(key)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "updates with data and allows chaining" do
|
|
21
|
+
expect { subject.update(data).update(data) }.not_to raise_error
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "raises an error if update is called after digest" do
|
|
25
|
+
subject.digest(data)
|
|
26
|
+
expect { subject.update(data) }.to raise_error(Krypt::Cmac::InvalidStateError, "CMAC has already been finalized")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "raises an error if digest is called with data after finalization" do
|
|
30
|
+
subject.digest(data)
|
|
31
|
+
expect { subject.digest(data) }.to raise_error(ArgumentError, "CMAC has already been finalized")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "returns the computed MAC tag if digest is called multiple times" do
|
|
35
|
+
subject.update(data)
|
|
36
|
+
tag1 = subject.digest
|
|
37
|
+
tag2 = subject.digest
|
|
38
|
+
expect(tag1).to eq(tag2)
|
|
39
|
+
expect(tag1).to eq(tag)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns the correct MAC tag when updating and calling digest without arguments" do
|
|
43
|
+
computed_tag = subject.update(data).digest
|
|
44
|
+
expect(computed_tag).to eq(tag)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "returns the correct MAC tag when calling digest with the data as an argument" do
|
|
48
|
+
computed_tag = subject.digest(data)
|
|
49
|
+
expect(computed_tag).to eq(tag)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "initialization with different key sizes" do
|
|
54
|
+
subject { Krypt::Cmac.new(key) }
|
|
55
|
+
|
|
56
|
+
shared_examples "correct key" do
|
|
57
|
+
it { expect(subject.key).to eq(key) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context "128-bit key" do
|
|
61
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_128[:key]) }
|
|
62
|
+
it_behaves_like "correct key"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "192-bit key" do
|
|
66
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_192[:key]) }
|
|
67
|
+
it_behaves_like "correct key"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context "256-bit key" do
|
|
71
|
+
let(:key) { hex_to_bin(ReferenceSamples::AES_256[:key]) }
|
|
72
|
+
it_behaves_like "correct key"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "invalid key size" do
|
|
76
|
+
let(:key) { b64_to_bin("deadbeef") }
|
|
77
|
+
it "raises ArgumentError" do
|
|
78
|
+
expect { subject }.to raise_error(ArgumentError, "Key must be 128, 192, or 256 bits long")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "krypt/cmac"
|
|
2
|
+
|
|
3
|
+
RSpec.shared_context "reference samples" do
|
|
4
|
+
include_context "String helpers"
|
|
5
|
+
|
|
6
|
+
shared_examples "correct computation" do
|
|
7
|
+
subject {
|
|
8
|
+
described_class.new(key)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
it { expect(subject.digest(data)).to eq(expected_mac) }
|
|
12
|
+
|
|
13
|
+
it "computes the correct digest when using update" do
|
|
14
|
+
expect(subject.update(data).digest).to eq(expected_mac)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "computes the correct digest when using <<" do
|
|
18
|
+
subject << data
|
|
19
|
+
expect(subject.digest).to eq(expected_mac)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "computes the correct hexdigest" do
|
|
23
|
+
expect(subject.hexdigest(data)).to eq(bin_to_hex(expected_mac))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "computes the correct base64digest" do
|
|
27
|
+
expect(subject.base64digest(data)).to eq(bin_to_b64(expected_mac))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "returns true when verifying the correct MAC" do
|
|
31
|
+
expect(subject.verify(expected_mac, data)).to be true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "returns true when verifying the correct MAC without supplying data to `verify`" do
|
|
35
|
+
expect(subject.update(data).verify(expected_mac)).to be true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "returns true when comparing two equal CMAC instances" do
|
|
39
|
+
expect(subject.update(data)).to eq(described_class.new(key).update(data))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns false when comparing two different CMAC instances" do
|
|
43
|
+
expect(subject.update("nope")).not_to eq(described_class.new(key).update(data))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "raises an error when verifying an incorrect MAC" do
|
|
47
|
+
expect { subject.verify(Krypt::Cmac::BLOCK_OF_ZEROS, data) }.to raise_error(Krypt::Cmac::TagMismatchError, "MAC tag verification failed, the tags do not match")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "computes the correct MAC when updating the data in chunks" do
|
|
51
|
+
(1..(data.size)).each do |i|
|
|
52
|
+
mac = described_class.new(key)
|
|
53
|
+
data.chars.each_slice(i) { |slice| mac.update(slice.join) }
|
|
54
|
+
expect(mac.digest).to eq(expected_mac)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
shared_examples "correct CMAC" do
|
|
60
|
+
let(:described_class) { Krypt::Cmac }
|
|
61
|
+
include_examples "correct computation"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
shared_examples "correct AES-CMAC-96" do
|
|
65
|
+
let(:described_class) { Krypt::Cmac::Cmac96 }
|
|
66
|
+
include_examples "correct computation"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
shared_examples "correct AES-CMAC-PRF-128" do
|
|
70
|
+
let(:described_class) { Krypt::Cmac::CmacPrf128 }
|
|
71
|
+
include_examples "correct computation"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
RSpec.shared_context "String helpers" do
|
|
2
|
+
def hex_to_bin(hex)
|
|
3
|
+
[hex].pack("H*")
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def bin_to_hex(bin)
|
|
7
|
+
bin.unpack1("H*")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def b64_to_bin(b64)
|
|
11
|
+
b64.unpack1("m")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def bin_to_b64(bin)
|
|
15
|
+
[bin].pack("m0")
|
|
16
|
+
end
|
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "simplecov"
|
|
2
|
+
SimpleCov.start
|
|
3
|
+
|
|
4
|
+
require "krypt/cmac"
|
|
5
|
+
|
|
6
|
+
RSpec.configure do |config|
|
|
7
|
+
# Include shared contexts
|
|
8
|
+
Dir[File.join(__dir__, "shared_contexts", "**", "*.rb")].each { |f| require f }
|
|
9
|
+
# Include all support files
|
|
10
|
+
Dir[File.join(__dir__, "support", "**", "*.rb")].each { |f| require f }
|
|
11
|
+
|
|
12
|
+
# Enable flags like --only-failures and --next-failure
|
|
13
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
|
14
|
+
|
|
15
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
|
16
|
+
config.disable_monkey_patching!
|
|
17
|
+
|
|
18
|
+
config.expect_with :rspec do |c|
|
|
19
|
+
c.syntax = :expect
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require "krypt/cmac"
|
|
2
|
+
|
|
3
|
+
module ReferenceSamples
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def sample_keys
|
|
7
|
+
@_keys ||= (AES_128.keys - %i[key])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Taken from /res/AES_CMAC.pdf
|
|
11
|
+
AES_128 = {
|
|
12
|
+
key: "2b7e151628aed2a6abf7158809cf4f3c",
|
|
13
|
+
empty: {
|
|
14
|
+
description: "empty message (len 0)",
|
|
15
|
+
data: "",
|
|
16
|
+
tag: "bb1d6929e95937287fa37d129b756746"
|
|
17
|
+
},
|
|
18
|
+
single_block: {
|
|
19
|
+
description: "single block message (i.e. len 16)",
|
|
20
|
+
data: "6bc1bee22e409f96e93d7e117393172a",
|
|
21
|
+
tag: "070a16b46b4d4144f79bdd9dd04a287c"
|
|
22
|
+
},
|
|
23
|
+
non_multiple_block: {
|
|
24
|
+
description: "with a message that is not a multiple of the block size, e.g. len 20",
|
|
25
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a57",
|
|
26
|
+
tag: "7d85449ea6ea19c823a7bf78837dfade"
|
|
27
|
+
},
|
|
28
|
+
multiple_block: {
|
|
29
|
+
description: "with a message that is a multiple of the block size, e.g. len 64",
|
|
30
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
|
|
31
|
+
tag: "51f0bebf7e3b9d92fc49741779363cfe"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Taken from /res/AES_CMAC.pdf
|
|
36
|
+
AES_192 = {
|
|
37
|
+
key: "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
|
|
38
|
+
empty: {
|
|
39
|
+
description: "empty message (len 0)",
|
|
40
|
+
data: "",
|
|
41
|
+
tag: "d17ddf46adaacde531cac483de7a9367"
|
|
42
|
+
},
|
|
43
|
+
single_block: {
|
|
44
|
+
description: "single block message (i.e. len 16)",
|
|
45
|
+
data: "6bc1bee22e409f96e93d7e117393172a",
|
|
46
|
+
tag: "9e99a7bf31e710900662f65e617c5184"
|
|
47
|
+
},
|
|
48
|
+
non_multiple_block: {
|
|
49
|
+
description: "with a message that is not a multiple of the block size, e.g. len 20",
|
|
50
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a57",
|
|
51
|
+
tag: "3d75c194ed96070444a9fa7ec740ecf8"
|
|
52
|
+
},
|
|
53
|
+
multiple_block: {
|
|
54
|
+
description: "with a message that is a multiple of the block size, e.g. len 64",
|
|
55
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
|
|
56
|
+
tag: "a1d5df0eed790f794d77589659f39a11"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Taken from /res/AES_CMAC.pdf
|
|
61
|
+
AES_256 = {
|
|
62
|
+
key: "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
|
|
63
|
+
empty: {
|
|
64
|
+
description: "empty message (len 0)",
|
|
65
|
+
data: "",
|
|
66
|
+
tag: "028962f61b7bf89efc6b551f4667d983"
|
|
67
|
+
},
|
|
68
|
+
single_block: {
|
|
69
|
+
description: "single block message (i.e. len 16)",
|
|
70
|
+
data: "6bc1bee22e409f96e93d7e117393172a",
|
|
71
|
+
tag: "28a7023f452e8f82bd4bf28d8c37c35c"
|
|
72
|
+
},
|
|
73
|
+
non_multiple_block: {
|
|
74
|
+
description: "with a message that is not a multiple of the block size, e.g. len 20",
|
|
75
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a57",
|
|
76
|
+
tag: "156727dc0878944a023c1fe03bad6d93"
|
|
77
|
+
},
|
|
78
|
+
multiple_block: {
|
|
79
|
+
description: "with a message that is a multiple of the block size, e.g. len 64",
|
|
80
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
|
|
81
|
+
tag: "e1992190549f6ed5696a2c056c315410"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Taken from RFC 4494
|
|
86
|
+
AES_CMAC_96 = {
|
|
87
|
+
key: "2b7e151628aed2a6abf7158809cf4f3c",
|
|
88
|
+
empty: {
|
|
89
|
+
description: "empty message (len 0)",
|
|
90
|
+
data: "",
|
|
91
|
+
tag: "bb1d6929e95937287fa37d12"
|
|
92
|
+
},
|
|
93
|
+
single_block: {
|
|
94
|
+
description: "single block message (i.e. len 16)",
|
|
95
|
+
data: "6bc1bee22e409f96e93d7e117393172a",
|
|
96
|
+
tag: "070a16b46b4d4144f79bdd9d"
|
|
97
|
+
},
|
|
98
|
+
non_multiple_block: {
|
|
99
|
+
description: "with a message that is not a multiple of the block size, e.g. len 40",
|
|
100
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411",
|
|
101
|
+
tag: "dfa66747de9ae63030ca3261"
|
|
102
|
+
},
|
|
103
|
+
multiple_block: {
|
|
104
|
+
description: "with a message that is a multiple of the block size, e.g. len 64",
|
|
105
|
+
data: "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
|
|
106
|
+
tag: "51f0bebf7e3b9d92fc497417"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Taken from RFC 4615
|
|
111
|
+
AES_PRF_128 = {
|
|
112
|
+
data: "000102030405060708090a0b0c0d0e0f10111213",
|
|
113
|
+
key_length_18: {
|
|
114
|
+
key: "000102030405060708090a0b0c0d0e0fedcb",
|
|
115
|
+
description: "key length 18",
|
|
116
|
+
tag: "84a348a4a45d235babfffc0d2b4da09a"
|
|
117
|
+
},
|
|
118
|
+
key_length_16: {
|
|
119
|
+
key: "000102030405060708090a0b0c0d0e0f",
|
|
120
|
+
description: "key length 16",
|
|
121
|
+
tag: "980ae87b5f4c9c5214f5b6a8455e4c2d"
|
|
122
|
+
},
|
|
123
|
+
key_length_10: {
|
|
124
|
+
key: "00010203040506070809",
|
|
125
|
+
description: "key length 10",
|
|
126
|
+
tag: "290d9e112edb09ee141fcf64c0b72f3d"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: krypt-cmac
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Martin Boßlet
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2025-03-06 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |2
|
|
13
|
+
An implementation of AES-CMAC for 128, 192, and 256 bit keys as specified in NIST SP 800-38B and RFC 4493, capable of streaming processing.
|
|
14
|
+
Also included is an implementation of AES-CMAC-PRF-128 as specified in RFC 4615 and of CMAC-96 as specified in RFC 4494.
|
|
15
|
+
email:
|
|
16
|
+
- martin.bosslet@gmail.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/krypt/cmac.rb
|
|
24
|
+
- lib/krypt/cmac/cmac_96.rb
|
|
25
|
+
- lib/krypt/cmac/cmac_prf_128.rb
|
|
26
|
+
- lib/krypt/cmac/errors.rb
|
|
27
|
+
- lib/krypt/cmac/version.rb
|
|
28
|
+
- spec/krypt/cmac/aes-cmac-prf-128_spec.rb
|
|
29
|
+
- spec/krypt/cmac/aes_128_spec.rb
|
|
30
|
+
- spec/krypt/cmac/aes_192_spec.rb
|
|
31
|
+
- spec/krypt/cmac/aes_256_spec.rb
|
|
32
|
+
- spec/krypt/cmac/aes_cmac_96_spec.rb
|
|
33
|
+
- spec/krypt/cmac/cmac_spec.rb
|
|
34
|
+
- spec/shared_contexts/reference_samples.rb
|
|
35
|
+
- spec/shared_contexts/string_helpers.rb
|
|
36
|
+
- spec/spec_helper.rb
|
|
37
|
+
- spec/support/reference_samples.rb
|
|
38
|
+
homepage: https://github.com/krypt/krypt-cmac
|
|
39
|
+
licenses:
|
|
40
|
+
- MIT
|
|
41
|
+
metadata:
|
|
42
|
+
homepage_uri: https://github.com/krypt/krypt-cmac
|
|
43
|
+
source_code_uri: https://github.com/krypt/krypt-cmac
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 3.0.0
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.6.2
|
|
59
|
+
specification_version: 4
|
|
60
|
+
summary: AES-CMAC as specified in NIST SP 800-38B, RFC 4493, RFC 4494 and RFC 4615.
|
|
61
|
+
test_files: []
|