3 * The CCM (Counter CBC-MAC) mode implementation
7 * @category PHPCryptLib
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
17 namespace CryptLib\Cipher\Block\Mode
;
20 * The CCM (Counter CBC-MAC) mode implementation
22 * @category PHPCryptLib
25 * @author Anthony Ferrara <ircmaxell@ircmaxell.com>
26 * @see http://tools.ietf.org/html/rfc3610
28 class CCM
extends \CryptLib\Cipher\Block\AbstractMode
{
31 * Indicates which mode the cipher mode is in (encryption/decryption)
33 const MODE_DECRYPT
= 1;
36 * Indicates which mode the cipher mode is in (encryption/decryption)
38 const MODE_ENCRYPT
= 2;
41 * @var int The number of octets in the Authentication field
43 protected $authFieldSize = 8;
46 * @var string The data buffer for this encryption instance
51 * @var int The number of octets in the length field
56 * @var string The mode name for the current instance
58 protected $mode = 'ccm';
61 * @var int The current encryption mode (enc/dec)
63 protected $encryptionMode = 0;
66 * @var array Mode specific options
68 protected $options = array(
75 * @var string The initialization vector to use for this instance
77 protected $usedIV = '';
80 * Build the instance of the cipher mode
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
86 public function __construct(
87 \CryptLib\Cipher\Block\Cipher
$cipher,
89 array $options = array()
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']);
101 * Finish the mode and append any additional data necessary
103 * @return string Any additional data
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');
110 if ($this->encryptionMode
& static::MODE_DECRYPT
) {
111 return $this->decryptBlockFinal();
113 return $this->encryptBlockFinal();
118 * Set the auth field size to a different value.
120 * Valid values: 4, 6, 8, 10, 12, 14, 16
122 * Note that increasing this size will make it harder for an attacker to
123 * modify the message payload
125 * @param int $new The new size of auth field to append
128 * @throws InvalidArgumentException If the number is outside of the range
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'
136 $this->authFieldSize
= (int) $new;
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
144 * Valid values are 2, 3, 4, 5, 6, 7, 8
146 * @param int $new The new LSize to use
149 * @throws InvalidArgumentException If the number is outside of the range
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'
157 $this->lSize
= (int) $new;
162 * Reset the mode to start over (destroying any intermediate state)
166 public function reset() {
167 $this->usedIV
= $this->extractInitv(
169 $this->cipher
->getBlockSize()
171 $this->encryptionMode
= 0;
176 * Decrypt the data using the supplied key, cipher
178 * @param string $data The data to decrypt
180 * @return string The decrypted data
182 protected function decryptBlock($data) {
183 $this->data
.= $data;
184 $this->encryptionMode |
= static::MODE_DECRYPT
;
188 * Perform the decryption of the block data
190 * @return string The final data
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) {
202 return rtrim($data, chr(0));
206 * Encrypt the data using the supplied key, cipher
208 * @param string $data The data to encrypt
210 * @return string The encrypted data
212 protected function encryptBlock($data) {
213 $this->data
.= $data;
214 $this->encryptionMode |
= static::MODE_ENCRYPT
;
218 * Perform the encryption of the block data
220 * @return string The final data
222 protected function encryptBlockFinal() {
223 $authFieldT = $this->computeAuthField($this->data
);
224 $data = $this->encryptMessage($this->data
, $authFieldT);
229 * Compute the authentication field
231 * @param string $data The data to compute with
233 * @return string The computed MAC Authentication Code
235 protected function computeAuthField($data) {
236 $blockSize = $this->cipher
->getBlockSize();
239 64 * (empty($this->adata
) ?
0 : 1)
240 +
8 * (($this->authFieldSize
- 2) / 2)
244 $flags . $this->usedIV
. pack($this->getLPackString(), strlen($data))
246 if (strlen($data) %
$blockSize != 0) {
247 $data .= str_repeat(chr(0), $blockSize - (strlen($data) %
$blockSize));
250 $blocks = array_merge(
252 $this->processAData($this->adata
, $blockSize)
255 $blocks = array_merge($blocks, str_split($data, $blockSize));
258 1 => $this->cipher
->encryptBlock($blocks[0])
261 $blockLen = count($blocks);
262 for ($i = 1; $i < $blockLen; $i++
) {
263 $crypted[$i +
1] = $this->cipher
->encryptBlock(
264 $crypted[$i] ^
$blocks[$i]
267 return substr(end($crypted), 0, $this->authFieldSize
);
271 * Encrypt the data using the supplied method
273 * @param string $data The data to encrypt
274 * @param string $authValue The auth value field
276 * @return string The encrypted data with authfield payload
278 protected function encryptMessage($data, $authValue) {
279 $blockSize = $this->cipher
->getBlockSize();
280 $flags = pack('C', ($this->lSize
- 1));
281 $blocks = str_split($data, $blockSize);
283 $blockLen = count($blocks);
284 for ($i = 0; $i <= $blockLen; $i++
) {
285 $sblocks[] = $this->cipher
->encryptBlock(
286 $flags . $this->usedIV
. pack($this->getLPackString(), $i)
290 foreach ($blocks as $key => $value) {
291 if (strlen($value) < $blockSize) {
292 $sblocks[$key +
1] = substr($sblocks[$key +
1], 0, strlen($value));
294 $encrypted .= $sblocks[$key +
1] ^
$value;
296 $sValue = substr($sblocks[0], 0, $this->authFieldSize
);
297 $uValue = $authValue ^
$sValue;
298 return $encrypted . $uValue;
302 * Enforce the data block is the correct size for the cipher
304 * @param string $data The data to check
307 * @throws InvalidArgumentException if the block size is not correct
309 protected function enforceBlockSize($data) {
314 * Extract the nonce from the initialization vector
316 * @param string $initv The initialization Vector to trim
317 * @param int $blockSize The size of the final nonce
319 * @return string The sized nonce
320 * @throws InvalidArgumentException if the IV is too short
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',
330 return substr($initv, 0, $initSize);
334 * Get a packing string related to the instance lSize variable
336 * @return string The pack() string to use to pack the length variables
339 protected function getLPackString() {
340 if ($this->lSize
<= 3) {
341 return str_repeat('x', $this->lSize
- 2) . 'n';
343 return str_repeat('x', $this->lSize
- 4) . 'N';
347 * Process the Authentication data for authenticating
349 * @param string $adata The data to authenticate with
350 * @param int $blockSize The block size for the cipher
352 * @return array An array of strings bound by the supplied blocksize
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
));
359 $len = chr(0xff) . chr(0xfe) . pack('N', strlen($this->adata
));
361 $temp = $len . $this->adata
;
362 if (strlen($temp) %
$blockSize != 0) {
363 //Pad the string to exactly mod16
366 $blockSize - (strlen($temp) %
$blockSize)
369 return str_split($temp, $blockSize);