Commit | Line | Data |
---|---|---|
14d4f286 S |
1 | <?php |
2 | /** | |
3 | * A Cipher based MAC generator (Based upon the CMAC specification) | |
4 | * | |
5 | * PHP version 5.3 | |
6 | * | |
7 | * @see http://csrc.nist.gov/publications/nistpubs/800-38B/SP_800-38B.pdf | |
8 | * @category PHPCryptLib | |
9 | * @package MAC | |
10 | * @subpackage Implementation | |
11 | * @author Anthony Ferrara <ircmaxell@ircmaxell.com> | |
12 | * @copyright 2011 The Authors | |
13 | * @license http://www.opensource.org/licenses/mit-license.html MIT License | |
14 | * @version Build @@version@@ | |
15 | */ | |
16 | namespace CryptLib\MAC\Implementation; | |
17 | ||
18 | use \CryptLib\Cipher\Factory; | |
19 | ||
20 | /** | |
21 | * A Cipher based MAC generator (Based upon the CMAC specification) | |
22 | * | |
23 | * @see http://csrc.nist.gov/publications/nistpubs/800-38B/SP_800-38B.pdf | |
24 | * @category PHPCryptLib | |
25 | * @package MAC | |
26 | * @subpackage Implementation | |
27 | */ | |
28 | class CMAC extends \CryptLib\MAC\AbstractMAC { | |
29 | ||
30 | protected $cipher = null; | |
31 | ||
32 | /** | |
33 | * @var array The stored options for this instance | |
34 | */ | |
35 | protected $options = array( | |
36 | 'cipher' => 'aes-128', | |
37 | 'cipherFactory' => null, | |
38 | ); | |
39 | ||
40 | /** | |
41 | * Build the instance of the MAC generator | |
42 | * | |
43 | * @param array $options The options for the instance | |
44 | * | |
45 | * @return void | |
46 | */ | |
47 | public function __construct(array $options = array()) { | |
48 | parent::__construct($options); | |
49 | if (is_null($this->options['cipherFactory'])) { | |
50 | $this->options['cipherFactory'] = new Factory; | |
51 | } | |
52 | $this->cipher = $this->options['cipherFactory']->getBlockCipher( | |
53 | $this->options['cipher'] | |
54 | ); | |
55 | } | |
56 | ||
57 | /** | |
58 | * Generate the MAC using the supplied data | |
59 | * | |
60 | * @param string $data The data to use to generate the MAC with | |
61 | * @param string $key The key to generate the MAC | |
62 | * @param int $size The size of the output to return | |
63 | * | |
64 | * @return string The generated MAC of the appropriate size | |
65 | */ | |
66 | public function generate($data, $key, $size = 0) { | |
67 | $blockSize = $this->cipher->getBlockSize(); | |
68 | if ($size == 0) { | |
69 | $size = $blockSize; | |
70 | } | |
71 | if ($size > $blockSize) { | |
72 | throw new \OutOfRangeException( | |
73 | sprintf( | |
74 | 'The size is too big for the cipher primitive [%d:%d]', | |
75 | $size, | |
76 | $blockSize | |
77 | ) | |
78 | ); | |
79 | } | |
80 | $this->cipher->setKey($key); | |
81 | $keys = $this->generateKeys(); | |
82 | $mBlocks = $this->splitDataIntoMBlocks($data, $keys); | |
83 | $cBlock = str_repeat(chr(0), $blockSize); | |
84 | foreach ($mBlocks as $key => $block) { | |
85 | $cBlock = $this->cipher->encryptBlock($cBlock ^ $block); | |
86 | } | |
87 | return substr($cBlock, 0, $size); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Generate a pair of keys by encrypting a block of all 0's, and then | |
92 | * maniuplating the result | |
93 | * | |
94 | * @return array The generated keys | |
95 | */ | |
96 | protected function generateKeys() { | |
97 | $keys = array(); | |
98 | $blockSize = $this->cipher->getBlockSize(); | |
99 | $rVal = $this->getRValue($blockSize); | |
100 | $text = str_repeat(chr(0), $blockSize); | |
101 | $lVal = $this->cipher->encryptBlock($text); | |
102 | $keys[0] = $this->leftShift($lVal, 1); | |
103 | if (ord(substr($lVal, 0, 1)) > 127) { | |
104 | $keys[0] = $keys[0] ^ $rVal; | |
105 | } | |
106 | $keys[1] = $this->leftShift($keys[0], 1); | |
107 | if (ord(substr($keys[0], 0, 1)) > 127) { | |
108 | $keys[1] = $keys[1] ^ $rVal; | |
109 | } | |
110 | return $keys; | |
111 | } | |
112 | ||
113 | /** | |
114 | * Get an RValue based upon the block size | |
115 | * | |
116 | * @param int $size The size of the block in bytes | |
117 | * | |
118 | * @see http://csrc.nist.gov/publications/nistpubs/800-38B/SP_800-38B.pdf | |
119 | * @return string A RValue of the appropriate block size | |
120 | */ | |
121 | protected function getRValue($size) { | |
122 | switch ($size * 8) { | |
123 | case 64: | |
124 | return str_repeat(chr(0), 7) . chr(0x1B); | |
125 | case 128: | |
126 | return str_repeat(chr(0), 15) . chr(0x87); | |
127 | default: | |
128 | } | |
129 | throw new \RuntimeException('Unsupported Block Size For The Cipher'); | |
130 | } | |
131 | ||
132 | protected function leftShift($data, $bits) { | |
133 | $mask = (0xff << (8 - $bits)) & 0xff; | |
134 | $state = 0; | |
135 | $result = ''; | |
136 | $length = strlen($data); | |
137 | for ($i = $length - 1; $i >= 0; $i--) { | |
138 | $tmp = ord($data[$i]); | |
139 | $result .= chr(($tmp << $bits) | $state); | |
140 | $state = ($tmp & $mask) >> (8 - $bits); | |
141 | } | |
142 | return strrev($result); | |
143 | } | |
144 | ||
145 | /** | |
146 | * Split the data into appropriate block chunks, encoding with the kyes | |
147 | * | |
148 | * @param string $data The data to split | |
149 | * @param array $keys The keys to use for encoding | |
150 | * | |
151 | * @return array The array of chunked and encoded data | |
152 | */ | |
153 | protected function splitDataIntoMBlocks($data, array $keys) { | |
154 | $blockSize = $this->cipher->getBlockSize(); | |
155 | $data = str_split($data, $blockSize); | |
156 | $last = end($data); | |
157 | if (strlen($last) != $blockSize) { | |
158 | //Pad the last element | |
159 | $last .= chr(0x80) . str_repeat(chr(0), $blockSize - 1 - strlen($last)); | |
160 | $last = $last ^ $keys[1]; | |
161 | } else { | |
162 | $last = $last ^ $keys[0]; | |
163 | } | |
164 | $data[count($data) - 1] = $last; | |
165 | return $data; | |
166 | } | |
167 | ||
168 | } |