Commit | Line | Data |
---|---|---|
14d4f286 S |
1 | <?php |
2 | /** | |
3 | * The CCM (Counter CBC-MAC) mode implementation | |
4 | * | |
5 | * PHP version 5.3 | |
6 | * | |
7 | * @category PHPCryptLib | |
8 | * @package Cipher | |
9 | * @subpackage Block | |
10 | * @author Anthony Ferrara <ircmaxell@ircmaxell.com> | |
11 | * @copyright 2011 The Authors | |
12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License | |
13 | * @version Build @@version@@ | |
14 | * @see http://tools.ietf.org/html/rfc3610 | |
15 | */ | |
16 | ||
17 | namespace CryptLib\Cipher\Block\Mode; | |
18 | ||
19 | /** | |
20 | * The CCM (Counter CBC-MAC) mode implementation | |
21 | * | |
22 | * @category PHPCryptLib | |
23 | * @package Cipher | |
24 | * @subpackage Block | |
25 | * @author Anthony Ferrara <ircmaxell@ircmaxell.com> | |
26 | * @see http://tools.ietf.org/html/rfc3610 | |
27 | */ | |
28 | class CCM extends \CryptLib\Cipher\Block\AbstractMode { | |
29 | ||
30 | /** | |
31 | * Indicates which mode the cipher mode is in (encryption/decryption) | |
32 | */ | |
33 | const MODE_DECRYPT = 1; | |
34 | ||
35 | /** | |
36 | * Indicates which mode the cipher mode is in (encryption/decryption) | |
37 | */ | |
38 | const MODE_ENCRYPT = 2; | |
39 | ||
40 | /** | |
41 | * @var int The number of octets in the Authentication field | |
42 | */ | |
43 | protected $authFieldSize = 8; | |
44 | ||
45 | /** | |
46 | * @var string The data buffer for this encryption instance | |
47 | */ | |
48 | protected $data = ''; | |
49 | ||
50 | /** | |
51 | * @var int The number of octets in the length field | |
52 | */ | |
53 | protected $lSize = 4; | |
54 | ||
55 | /** | |
56 | * @var string The mode name for the current instance | |
57 | */ | |
58 | protected $mode = 'ccm'; | |
59 | ||
60 | /** | |
61 | * @var int The current encryption mode (enc/dec) | |
62 | */ | |
63 | protected $encryptionMode = 0; | |
64 | ||
65 | /** | |
66 | * @var array Mode specific options | |
67 | */ | |
68 | protected $options = array( | |
69 | 'adata' => '', | |
70 | 'lSize' => 4, | |
71 | 'aSize' => 8, | |
72 | ); | |
73 | ||
74 | /** | |
75 | * @var string The initialization vector to use for this instance | |
76 | */ | |
77 | protected $usedIV = ''; | |
78 | ||
79 | /** | |
80 | * Build the instance of the cipher mode | |
81 | * | |
82 | * @param Cipher $cipher The cipher to use for encryption/decryption | |
83 | * @param string $initv The initialization vector (empty if not needed) | |
84 | * @param array $options An array of mode-specific options | |
85 | */ | |
86 | public function __construct( | |
87 | \CryptLib\Cipher\Block\Cipher $cipher, | |
88 | $initv, | |
89 | array $options = array() | |
90 | ) { | |
91 | $this->options = $options + $this->options; | |
92 | $this->cipher = $cipher; | |
93 | $this->initv = $initv; | |
94 | $this->adata = $this->options['adata']; | |
95 | $this->setLSize($this->options['lSize']); | |
96 | $this->setAuthFieldSize($this->options['aSize']); | |
97 | $this->reset(); | |
98 | } | |
99 | ||
100 | /** | |
101 | * Finish the mode and append any additional data necessary | |
102 | * | |
103 | * @return string Any additional data | |
104 | */ | |
105 | public function finish() { | |
106 | $mask = (static::MODE_DECRYPT | static::MODE_ENCRYPT); | |
107 | if (!($this->encryptionMode ^ $mask)) { | |
108 | throw new \LogicException('Cannot encrypt and decrypt in same state'); | |
109 | } | |
110 | if ($this->encryptionMode & static::MODE_DECRYPT) { | |
111 | return $this->decryptBlockFinal(); | |
112 | } else { | |
113 | return $this->encryptBlockFinal(); | |
114 | } | |
115 | } | |
116 | ||
117 | /** | |
118 | * Set the auth field size to a different value. | |
119 | * | |
120 | * Valid values: 4, 6, 8, 10, 12, 14, 16 | |
121 | * | |
122 | * Note that increasing this size will make it harder for an attacker to | |
123 | * modify the message payload | |
124 | * | |
125 | * @param int $new The new size of auth field to append | |
126 | * | |
127 | * @return void | |
128 | * @throws InvalidArgumentException If the number is outside of the range | |
129 | */ | |
130 | public function setAuthFieldSize($new) { | |
131 | if (!in_array($new, array(4, 6, 8, 10, 12, 14, 16))) { | |
132 | throw new \InvalidArgumentException( | |
133 | 'The Auth Field must be one of: 4, 6, 8, 10, 12, 14, 16' | |
134 | ); | |
135 | } | |
136 | $this->authFieldSize = (int) $new; | |
137 | $this->reset(); | |
138 | } | |
139 | ||
140 | /** | |
141 | * Set the size of the length field. This is a tradeoff between the maximum | |
142 | * message size and the size of the initialization vector | |
143 | * | |
144 | * Valid values are 2, 3, 4, 5, 6, 7, 8 | |
145 | * | |
146 | * @param int $new The new LSize to use | |
147 | * | |
148 | * @return void | |
149 | * @throws InvalidArgumentException If the number is outside of the range | |
150 | */ | |
151 | public function setLSize($new) { | |
152 | if ($new < 2 || $new > 8) { | |
153 | throw new \InvalidArgumentException( | |
154 | 'The LSize must be between 2 and 8 inclusive' | |
155 | ); | |
156 | } | |
157 | $this->lSize = (int) $new; | |
158 | $this->reset(); | |
159 | } | |
160 | ||
161 | /** | |
162 | * Reset the mode to start over (destroying any intermediate state) | |
163 | * | |
164 | * @return void | |
165 | */ | |
166 | public function reset() { | |
167 | $this->usedIV = $this->extractInitv( | |
168 | $this->initv, | |
169 | $this->cipher->getBlockSize() | |
170 | ); | |
171 | $this->encryptionMode = 0; | |
172 | $this->data = ''; | |
173 | } | |
174 | ||
175 | /** | |
176 | * Decrypt the data using the supplied key, cipher | |
177 | * | |
178 | * @param string $data The data to decrypt | |
179 | * | |
180 | * @return string The decrypted data | |
181 | */ | |
182 | protected function decryptBlock($data) { | |
183 | $this->data .= $data; | |
184 | $this->encryptionMode |= static::MODE_DECRYPT; | |
185 | } | |
186 | ||
187 | /** | |
188 | * Perform the decryption of the block data | |
189 | * | |
190 | * @return string The final data | |
191 | */ | |
192 | protected function decryptBlockFinal() { | |
193 | $message = substr($this->data, 0, -1 * $this->authFieldSize); | |
194 | $uValue = substr($this->data, -1 * $this->authFieldSize); | |
195 | $data = $this->encryptMessage($message, $uValue); | |
196 | $computedT = substr($data, -1 * $this->authFieldSize); | |
197 | $data = substr($data, 0, -1 * $this->authFieldSize); | |
198 | $authFieldT = $this->computeAuthField($data); | |
199 | if ($authFieldT != $computedT) { | |
200 | return false; | |
201 | } | |
202 | return rtrim($data, chr(0)); | |
203 | } | |
204 | ||
205 | /** | |
206 | * Encrypt the data using the supplied key, cipher | |
207 | * | |
208 | * @param string $data The data to encrypt | |
209 | * | |
210 | * @return string The encrypted data | |
211 | */ | |
212 | protected function encryptBlock($data) { | |
213 | $this->data .= $data; | |
214 | $this->encryptionMode |= static::MODE_ENCRYPT; | |
215 | } | |
216 | ||
217 | /** | |
218 | * Perform the encryption of the block data | |
219 | * | |
220 | * @return string The final data | |
221 | */ | |
222 | protected function encryptBlockFinal() { | |
223 | $authFieldT = $this->computeAuthField($this->data); | |
224 | $data = $this->encryptMessage($this->data, $authFieldT); | |
225 | return $data; | |
226 | } | |
227 | ||
228 | /** | |
229 | * Compute the authentication field | |
230 | * | |
231 | * @param string $data The data to compute with | |
232 | * | |
233 | * @return string The computed MAC Authentication Code | |
234 | */ | |
235 | protected function computeAuthField($data) { | |
236 | $blockSize = $this->cipher->getBlockSize(); | |
237 | $flags = pack( | |
238 | 'C', | |
239 | 64 * (empty($this->adata) ? 0 : 1) | |
240 | + 8 * (($this->authFieldSize - 2) / 2) | |
241 | + ($this->lSize - 1) | |
242 | ); | |
243 | $blocks = array( | |
244 | $flags . $this->usedIV . pack($this->getLPackString(), strlen($data)) | |
245 | ); | |
246 | if (strlen($data) % $blockSize != 0) { | |
247 | $data .= str_repeat(chr(0), $blockSize - (strlen($data) % $blockSize)); | |
248 | } | |
249 | ||
250 | $blocks = array_merge( | |
251 | $blocks, | |
252 | $this->processAData($this->adata, $blockSize) | |
253 | ); | |
254 | if (!empty($data)) { | |
255 | $blocks = array_merge($blocks, str_split($data, $blockSize)); | |
256 | } | |
257 | $crypted = array( | |
258 | 1 => $this->cipher->encryptBlock($blocks[0]) | |
259 | ); | |
260 | ||
261 | $blockLen = count($blocks); | |
262 | for ($i = 1; $i < $blockLen; $i++) { | |
263 | $crypted[$i + 1] = $this->cipher->encryptBlock( | |
264 | $crypted[$i] ^ $blocks[$i] | |
265 | ); | |
266 | } | |
267 | return substr(end($crypted), 0, $this->authFieldSize); | |
268 | } | |
269 | ||
270 | /** | |
271 | * Encrypt the data using the supplied method | |
272 | * | |
273 | * @param string $data The data to encrypt | |
274 | * @param string $authValue The auth value field | |
275 | * | |
276 | * @return string The encrypted data with authfield payload | |
277 | */ | |
278 | protected function encryptMessage($data, $authValue) { | |
279 | $blockSize = $this->cipher->getBlockSize(); | |
280 | $flags = pack('C', ($this->lSize - 1)); | |
281 | $blocks = str_split($data, $blockSize); | |
282 | $sblocks = array(); | |
283 | $blockLen = count($blocks); | |
284 | for ($i = 0; $i <= $blockLen; $i++) { | |
285 | $sblocks[] = $this->cipher->encryptBlock( | |
286 | $flags . $this->usedIV . pack($this->getLPackString(), $i) | |
287 | ); | |
288 | } | |
289 | $encrypted = ''; | |
290 | foreach ($blocks as $key => $value) { | |
291 | if (strlen($value) < $blockSize) { | |
292 | $sblocks[$key + 1] = substr($sblocks[$key + 1], 0, strlen($value)); | |
293 | } | |
294 | $encrypted .= $sblocks[$key + 1] ^ $value; | |
295 | } | |
296 | $sValue = substr($sblocks[0], 0, $this->authFieldSize); | |
297 | $uValue = $authValue ^ $sValue; | |
298 | return $encrypted . $uValue; | |
299 | } | |
300 | ||
301 | /** | |
302 | * Enforce the data block is the correct size for the cipher | |
303 | * | |
304 | * @param string $data The data to check | |
305 | * | |
306 | * @return void | |
307 | * @throws InvalidArgumentException if the block size is not correct | |
308 | */ | |
309 | protected function enforceBlockSize($data) { | |
310 | return true; | |
311 | } | |
312 | ||
313 | /** | |
314 | * Extract the nonce from the initialization vector | |
315 | * | |
316 | * @param string $initv The initialization Vector to trim | |
317 | * @param int $blockSize The size of the final nonce | |
318 | * | |
319 | * @return string The sized nonce | |
320 | * @throws InvalidArgumentException if the IV is too short | |
321 | */ | |
322 | protected function extractInitv($initv, $blockSize) { | |
323 | $initSize = $blockSize - 1 - $this->lSize; | |
324 | if (strlen($initv) < $initSize) { | |
325 | throw new \InvalidArgumentException(sprintf( | |
326 | 'Supplied Initialization Vector is too short, should be %d bytes', | |
327 | $initSize | |
328 | )); | |
329 | } | |
330 | return substr($initv, 0, $initSize); | |
331 | } | |
332 | ||
333 | /** | |
334 | * Get a packing string related to the instance lSize variable | |
335 | * | |
336 | * @return string The pack() string to use to pack the length variables | |
337 | * @see pack() | |
338 | */ | |
339 | protected function getLPackString() { | |
340 | if ($this->lSize <= 3) { | |
341 | return str_repeat('x', $this->lSize - 2) . 'n'; | |
342 | } | |
343 | return str_repeat('x', $this->lSize - 4) . 'N'; | |
344 | } | |
345 | ||
346 | /** | |
347 | * Process the Authentication data for authenticating | |
348 | * | |
349 | * @param string $adata The data to authenticate with | |
350 | * @param int $blockSize The block size for the cipher | |
351 | * | |
352 | * @return array An array of strings bound by the supplied blocksize | |
353 | */ | |
354 | protected function processAData($adata, $blockSize) { | |
355 | if (!empty($this->adata)) { | |
356 | if (strlen($this->adata) < ((1 << 16) - (1 << 8))) { | |
357 | $len = pack('n', strlen($this->adata)); | |
358 | } else { | |
359 | $len = chr(0xff) . chr(0xfe) . pack('N', strlen($this->adata)); | |
360 | } | |
361 | $temp = $len . $this->adata; | |
362 | if (strlen($temp) % $blockSize != 0) { | |
363 | //Pad the string to exactly mod16 | |
364 | $temp .= str_repeat( | |
365 | chr(0), | |
366 | $blockSize - (strlen($temp) % $blockSize) | |
367 | ); | |
368 | } | |
369 | return str_split($temp, $blockSize); | |
370 | } | |
371 | return array(); | |
372 | } | |
373 | ||
374 | } |