cryptal  latest
Cryptography Abstraction Layer
Umac.php
1 <?php
2 
4 
13 
18 class Umac extends AbstractMac
19 {
21  const PRIME_36 = '0x0000000FFFFFFFFB';
22 
24  const PRIME_64 = '0xFFFFFFFFFFFFFFC5';
25 
27  const PRIME_128 = '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61';
28 
30  protected $cipher;
31 
33  protected $blkSize;
34 
36  protected $taglen;
37 
39  private $key;
40 
42  private $nonce;
43 
45  static protected $twop32;
46 
48  static protected $twop64;
49 
50  public function __construct(MacEnum $macAlgorithm, SubAlgorithmAbstractEnum $innerAlgorithm, $key, $nonce = '')
51  {
52  $supported = array(
53  4 => MacEnum::MAC_UMAC_32(),
54  8 => MacEnum::MAC_UMAC_64(),
55  12 => MacEnum::MAC_UMAC_96(),
56  16 => MacEnum::MAC_UMAC_128(),
57  );
58 
59  $taglen = array_search($macAlgorithm, $supported);
60  if (false === $taglen || !extension_loaded('gmp')) {
61  throw new \InvalidArgumentException('Unsupported MAC algorithm');
62  }
63 
64  if (!($innerAlgorithm instanceof CipherEnum)) {
65  throw new \InvalidArgumentException('A cipher was expected as the inner algorithm');
66  }
67 
68  if (!is_string($nonce)) {
69  throw new \InvalidArgumentException('Invalid key');
70  }
71 
72  if (!is_string($nonce)) {
73  throw new \InvalidArgumentException('Invalid nonce');
74  }
75 
76  $cipher = Registry::buildCipher($innerAlgorithm, ModeEnum::MODE_ECB(), new None, $key, 0, true);
77  $blkSize = $cipher->getBlockSize();
78 
79  if (16 > $blkSize || '1' !== trim(base_convert($blkSize, 10, 2), '0')) {
80  throw new \InvalidArgumentException('Incompatible cipher');
81  }
82 
83  $this->cipher = $cipher;
84  $this->subAlgo = $innerAlgorithm;
85  $this->blkSize = $blkSize;
86  $this->taglen = $taglen;
87  $this->key = $key;
88  $this->nonce = $nonce;
89  $this->data = '';
90 
91  self::$twop32 = gmp_pow(2, 32);
92  self::$twop64 = gmp_pow(2, 64);
93  }
94 
95  protected function internalUpdate($data)
96  {
97  $this->data .= $data;
98  }
99 
100  protected function internalFinalize()
101  {
102  $hashed = $this->UHASH($this->data);
103  $pad = $this->PDF($this->nonce);
104  $tag = gmp_xor(gmp_init(bin2hex($pad), 16), gmp_init(bin2hex($hashed), 16));
105  $tag = gmp_strval($tag, 16);
106  $tag = pack('H*', str_pad($tag, $this->taglen << 1, '0', STR_PAD_LEFT));
107  return $tag;
108  }
109 
110  protected function KDF($index, $numbytes)
111  {
112  // Calculate number of block cipher iterations
113  $n = (int) ceil($numbytes / $this->blkSize);
114  $y = '';
115  $bhex = (($this->blkSize - 8) << 1);
116  $pad = "%0${bhex}X%016X";
117 
118  // Build Y using the block cipher in counter mode
119  for ($i = 1; $i <= $n; $i++) {
120  $t = pack('H*', sprintf($pad, $index, $i));
121  $t = $this->cipher->encrypt('', $t);
122  $y .= $t;
123  }
124  return substr($y, 0, $numbytes);
125  }
126 
127  protected function PDF($nonce)
128  {
129  $nlen = strlen($nonce);
130  $nonce = gmp_init(bin2hex($nonce), 16);
131 
132  // Extract and zero low bit(s) of Nonce if needed
133  if ($this->taglen <= 8) {
134  $index = gmp_intval(gmp_mod($nonce, gmp_init($this->blkSize / $this->taglen)));
135  $nonce = gmp_xor($nonce, $index);
136  }
137 
138  $nonce = gmp_strval($nonce, 16);
139  $nonce = pack('H*', str_pad($nonce, $nlen << 1, '0', STR_PAD_LEFT));
140 
141  // Make Nonce BLOCKLEN bytes by appending zeroes if needed
142  $nonce = str_pad($nonce, $this->blkSize, "\x00");
143 
144  $kprime = $this->KDF(0, strlen($this->key));
145  $cipher = Registry::buildCipher($this->subAlgo, ModeEnum::MODE_ECB(), new None, $kprime, 0, true);
146  $t = $cipher->encrypt('', $nonce);
147 
148  if ($this->taglen <= 8) {
149  return substr($t, $index * $this->taglen, $this->taglen);
150  } else {
151  return substr($t, 0, $this->taglen);
152  }
153  }
154 
155  protected function UHASH($m)
156  {
157  $iters = $this->taglen >> 2;
158  $l1key = $this->KDF(1, 1024 + ($iters - 1) << 4);
159  $l2key = $this->KDF(2, $iters * 24);
160  $l3key1 = $this->KDF(3, $iters * 64);
161  $l3key2 = $this->KDF(4, $iters * 4);
162 
163  $y = '';
164  for ($i = 0; $i < $iters; $i++) {
165  $l1key_i = substr($l1key, $i << 4, 1024);
166  $l2key_i = substr($l2key, $i * 24, 24);
167  $l3key1_i = substr($l3key1, $i << 6, 64);
168  $l3key2_i = substr($l3key2, $i << 2, 4);
169 
170  $a = $this->l1Hash($l1key_i, $m);
171  if (strlen($m) <= 1024) {
172  $b = "\x00\x00\x00\x00\x00\x00\x00\x00" . $a;
173  } else {
174  $b = $this->l2Hash($l2key_i, $a);
175  }
176  $c = $this->l3Hash($l3key1_i, $l3key2_i, $b);
177  $y .= $c;
178  }
179  return $y;
180  }
181 
182  protected function l1Hash($k, $m)
183  {
184  // Break M into 1024 byte chunks (final chunk may be shorter)
185  $ms = str_split($m, 1024);
186 
187  // For each chunk, except the last: endian-adjust, NH hash
188  // and add bit-length. Use results to build Y.
189  $len = gmp_init("0x2000", 16);
190  $y = '';
191  $last = array_pop($ms);
192  $k_i = str_split(substr($k, 0, 1024), 4);
193  foreach ($ms as $mp) {
194  $v = unpack('V*', $mp);
195  array_unshift($v, 'N*');
196  $m_i = call_user_func_array('pack', $v);
197  $nh = gmp_strval($this->NH($k_i, $m_i, $len), 16);
198  $y .= pack('H*', str_pad($nh, 16, '0', STR_PAD_LEFT));
199  }
200 
201  // For the last chunk: pad to 32-byte boundary, endian-adjust,
202  // NH hash and add bit-length. Concatenate the result to Y.
203  $len = gmp_init(strlen($last) * 8);
204  $last = str_pad($last, max(32, ((strlen($last) + 31) >> 5) << 5), "\x00");
205  $v = unpack('V*', $last);
206  array_unshift($v, 'N*');
207  $m_t = call_user_func_array('pack', $v);
208  $k_i = str_split(substr($k, 0, strlen($m_t)), 4);
209  $nh = gmp_strval($this->NH($k_i, $m_t, $len), 16);
210  $y .= pack('H*', str_pad($nh, 16, '0', STR_PAD_LEFT));
211  return $y;
212  }
213 
214  protected function NH($k_i, $m, $len)
215  {
216  // Break M and K into 4-byte chunks
217  $m_i = str_split($m, 4);
218 
219  // Perform NH hash on the chunks, pairing words for multiplication
220  // which are 4 apart to accommodate vector-parallelism.
221  $y = gmp_init(0);
222  for ($i = 0, $t = count($m_i) >> 3; $i < $t; $i++) {
223  for ($j = 0; $j < 4; $j++) {
224  $y = gmp_add(
225  $y,
226  gmp_mul(
227  gmp_mod(
228  gmp_add(
229  gmp_init(bin2hex($m_i[8 * $i + $j]), 16),
230  gmp_init(bin2hex($k_i[8 * $i + $j]), 16)
231  ),
232  self::$twop32
233  ),
234  gmp_mod(
235  gmp_add(
236  gmp_init(bin2hex($m_i[8 * $i + $j + 4]), 16),
237  gmp_init(bin2hex($k_i[8 * $i + $j + 4]), 16)
238  ),
239  self::$twop32
240  )
241  )
242  );
243  }
244  }
245  $y = gmp_mod(gmp_add($y, $len), self::$twop64);
246  return $y;
247  }
248 
249  protected function l2Hash($k, $m)
250  {
251  // Extract keys and restrict to special key-sets
252  $mask64 = gmp_init('0x01ffffff01ffffff', 16);
253  $mask128 = gmp_init('0x01ffffff01ffffff01ffffff01ffffff', 16);
254  $k64 = gmp_and(gmp_init(bin2hex(substr($k, 0, 8)), 16), $mask64);
255  $k128 = gmp_and(gmp_init(bin2hex(substr($k, 8, 16)), 16), $mask128);
256 
257  // If M is no more than 2^17 bytes, hash under 64-bit prime,
258  // otherwise, hash first 2^17 bytes under 64-bit prime and
259  // remainder under 128-bit prime.
260  if (strlen($m) <= (1 << 17)) {
261  // View M as an array of 64-bit words, and use POLY modulo
262  // prime(64) (and with bound 2^64 - 2^32) to hash it.
263  $y = $this->POLY(
264  64,
265  gmp_sub(
266  gmp_init('0x10000000000000000', 16),
267  gmp_init('0x100000000', 16)
268  ),
269  $k64,
270  $m
271  );
272  } else {
273  $m_1 = substr($m, 0, 1 << 17);
274  $m_2 = substr($m, 1 << 17) . "\x80";
275  $m_2 = str_pad($m_2, max(16, ((strlen($m_2) + 15) >> 4) << 4), "\x00");
276 
277  $y = $this->POLY(
278  64,
279  gmp_sub(gmp_pow(2, 64), gmp_pow(2, 32)),
280  $k64,
281  $m_1
282  );
283  $y = $this->POLY(
284  128,
285  gmp_sub(gmp_pow(2, 128), gmp_pow(2, 96)),
286  $k128,
287  pack('H*', substr(str_repeat('00', 16) . gmp_strval($y, 16), -32)) . $m_2
288  );
289  }
290 
291  $res = substr(str_repeat('00', 16) . gmp_strval($y, 16), -32);
292  return pack('H*', $res);
293  }
294 
295  protected function POLY($wordbits, $maxwordrange, $k, $m)
296  {
297  $wordbytes = $wordbits >> 3;
298  $p = gmp_init(constant(__CLASS__ . '::PRIME_' . $wordbits), 16);
299  $offset = gmp_sub(gmp_pow(2, $wordbits), $p);
300  $marker = gmp_sub($p, 1);
301 
302  // Break M into chunks of length wordbytes bytes
303  $m_i = str_split($m, $wordbytes);
304 
305  // Each input word m is compared with maxwordrange. If not smaller
306  // then 'marker' and (m - offset), both in range, are hashed.
307  $y = gmp_init(1);
308  for ($i = 0, $n = count($m_i); $i < $n; $i++) {
309  $m = gmp_init(bin2hex($m_i[$i]), 16);
310  if (gmp_cmp($m, $maxwordrange) >= 0) {
311  $y = gmp_mod(gmp_add(gmp_mul($k, $y), $marker), $p);
312  $y = gmp_mod(gmp_add(gmp_mul($k, $y), gmp_sub($m, $offset)), $p);
313  } else {
314  $y = gmp_mod(gmp_add(gmp_mul($k, $y), $m), $p);
315  }
316  }
317 
318  return $y;
319  }
320 
321  protected function l3Hash($k1, $k2, $m)
322  {
323  $y = gmp_init(0);
324  $prime36 = gmp_init(self::PRIME_36, 16);
325  for ($i = 0; $i < 8; $i++) {
326  $m_i = gmp_init(bin2hex(substr($m, $i << 1, 2)), 16);
327  $k_i = gmp_mod(gmp_init(bin2hex(substr($k1, $i << 3, 8)), 16), $prime36);
328  $y = gmp_add($y, gmp_mul($m_i, $k_i));
329  }
330  $y = gmp_and(gmp_mod($y, $prime36), '0xFFFFFFFF');
331  $y = gmp_xor($y, gmp_init(bin2hex($k2), 16));
332  $y = pack('H*', str_pad(gmp_strval($y, 16), 8, '0', STR_PAD_LEFT));
333  return $y;
334  }
335 }
const PRIME_64
64-bits prime number, in hexadecimal notation.
Definition: Umac.php:24
const PRIME_36
36-bits prime number, in hexadecimal notation.
Definition: Umac.php:21
$blkSize
Block size for the cipher.
Definition: Umac.php:33
$cipher
Cipher algorithm used to encrypt data.
Definition: Umac.php:30
const PRIME_128
128-bits prime number, in hexadecimal notation.
Definition: Umac.php:27
__construct(MacEnum $macAlgorithm, SubAlgorithmAbstractEnum $innerAlgorithm, $key, $nonce= '')
Definition: Umac.php:50
$taglen
Length of tags generated by this instance.
Definition: Umac.php:36