cryptal  latest
Cryptography Abstraction Layer
GCM.php
1 <?php
2 
4 
7 
11 class GCM implements AsymmetricModeInterface
12 {
14  protected $cipher;
15 
17  protected $key;
18 
20  protected $iv;
21 
23  protected $taglen;
24 
26  protected $table;
27 
28  public function __construct(CryptoInterface $cipher, $iv, $tagLength)
29  {
30  if (16 !== $cipher->getBlockSize()) {
31  throw new \InvalidArgumentException('Incompatible cipher (block size != 16)');
32  }
33 
34  $this->taglen = $tagLength;
35  $this->cipher = $cipher;
36  $this->iv = $iv;
37 
38  $H = gmp_init(bin2hex($cipher->encrypt('', str_repeat("\x00", 16))), 16);
39  $H = str_pad(gmp_strval($H, 2), 128, '0', STR_PAD_LEFT);
40  $R = gmp_init('E1000000000000000000000000000000', 16);
41 
42  $this->table = array();
43  for ($i = 0; $i < 16; $i++) {
44  $this->table[$i] = array();
45  for ($j = 0; $j < 256; $j++) {
46  $V = gmp_init(dechex($j) . str_repeat("00", $i), 16);
47  $Z = gmp_init(0);
48  for ($k = 0; $k < 128; $k++) {
49  // Compute Z_n+1
50  if ($H[$k]) {
51  $Z = gmp_xor($Z, $V);
52  }
53 
54  // Compute V_n+1
55  $odd = gmp_testbit($V, 0);
56  $V = gmp_div_q($V, 2);
57  if ($odd) {
58  $V = gmp_xor($V, $R);
59  }
60  }
61  $this->table[$i][$j] = pack('H*', str_pad(gmp_strval($Z, 16), 32, 0, STR_PAD_LEFT));
62  }
63  }
64  }
65 
66  protected static function inc($X, $n)
67  {
68  $s = gmp_strval($X, 2);
69  $s1 = (string) substr($s, 0, -$n);
70  $s = gmp_add(gmp_init(substr($s, -$n), 2), 1);
71  $s = gmp_mod($s, gmp_pow(2, $n));
72  $s2 = str_pad(gmp_strval($s, 2), $n, '0', STR_PAD_LEFT);
73  return gmp_init($s1 . $s2, 2);
74  }
75 
76  protected function ghash($X)
77  {
78  $Xn = str_split($X, 16);
79  $m = count($Xn);
80  if (strlen($Xn[$m - 1]) != 16) {
81  throw new \InvalidArgumentException();
82  }
83 
84  // Inline lookup.
85  $Y = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
86  $Y2 = $Y;
87  for ($i = 0; $i < $m; $i++) {
88  $res = $Y2;
89  $val = $Y ^ $Xn[$i];
90  for ($j = 0; $j < 16; $j++) {
91  $res = $res ^ $this->table[$j][ord($val[15 - $j])];
92  }
93  $Y = $res;
94  }
95  return $Y;
96  }
97 
98  protected function gctr($ICB, $X)
99  {
100  if ($X === '') {
101  return '';
102  }
103 
104  $Xn = str_split($X, 16);
105  $n = count($Xn);
106  $CB = array(1 => $ICB);
107  $Yn = array();
108  for ($i = 1; $i < $n; $i++) {
109  $CB[$i + 1] = static::inc($CB[$i], 32);
110 
111  $t = $this->cipher->encrypt(
112  '',
113  // Pad CB[i] to the block size (128 bits)
114  pack('H*', str_pad(gmp_strval($CB[$i], 16), 32, '0', STR_PAD_LEFT))
115  );
116  $t = gmp_xor(
117  gmp_init(bin2hex($Xn[$i - 1]), 16),
118  gmp_init(bin2hex($t), 16)
119  );
120  $Yn[$i] = pack('H*', str_pad(gmp_strval($t, 16), 32, '0', STR_PAD_LEFT));
121  }
122 
123  // Cipher
124  $t = $this->cipher->encrypt(
125  '',
126  // Pad CB[i] to the block size (128 bits)
127  pack('H*', str_pad(gmp_strval($CB[$n], 16), 32, '0', STR_PAD_LEFT))
128  );
129  // MSB Xn*
130  $t = str_pad(gmp_strval(gmp_init(bin2hex($t), 16), 16), 32, '0', STR_PAD_LEFT);
131  $nn = strlen($Xn[$n - 1]) << 1;
132  $t = substr($t, 0, $nn);
133  // Yn*
134  $t = gmp_xor(gmp_init(bin2hex($Xn[$n - 1]), 16), gmp_init($t, 16));
135  $Yn[$n] = pack('H*', str_pad(gmp_strval($t, 16), $nn, '0', STR_PAD_LEFT));
136  return implode('', $Yn);
137  }
138 
139  protected function padIv()
140  {
142  $ivlen = strlen($this->iv);
143  if ($ivlen === 12) {
144  $J0 = $this->iv . "\x00\x00\x00\x01";
145  } else {
146  $s = (16 - ($ivlen % 16)) % 16;
147  $t = gmp_strval(gmp_init($ivlen << 3, 10), 16);
148  $J0 = $this->ghash(
149  $this->iv .
150  str_repeat("\x00", $s) .
151  pack('H*', str_pad($t, 32, '0', STR_PAD_LEFT))
152  );
153  }
154  return $J0;
155  }
156 
157  public function encrypt($data, $context)
158  {
159  $options = stream_context_get_options($context);
160  $A = isset($options['cryptal']['data']) ? (string) $options['cryptal']['data'] : '';
161 
162  $J0 = gmp_init(bin2hex($this->padIv()), 16);
163  $C = $this->gctr(static::inc($J0, 32), $data);
164  $Cl = strlen($C);
165  $u = (16 - ($Cl % 16)) % 16;
166  $Al = strlen($A);
167  $v = (16 - ($Al % 16)) % 16;
168  $S = $this->ghash(
169  $A .
170  str_repeat("\x00", $v) .
171  $C .
172  str_repeat("\x00", $u) .
173  pack('H*', str_pad(gmp_strval(gmp_init($Al << 3, 10), 16), 16, '0', STR_PAD_LEFT)) .
174  pack('H*', str_pad(gmp_strval(gmp_init($Cl << 3, 10), 16), 16, '0', STR_PAD_LEFT))
175  );
176  $T = substr($this->gctr($J0, $S), 0, $this->taglen);
177  stream_context_set_option($context, 'cryptal', 'tag', $T);
178  return $C;
179  }
180 
181  public function decrypt($data, $context)
182  {
183  $options = stream_context_get_options($context);
184  $A = isset($options['cryptal']['data']) ? (string) $options['cryptal']['data'] : '';
185  $T = isset($options['cryptal']['tag']) ? (string) $options['cryptal']['tag'] : '';
186 
187  $J0 = gmp_init(bin2hex($this->padIv()), 16);
188  $P = $this->gctr(static::inc($J0, 32), $data);
189  $Cl = strlen($data);
190  $u = (16 - ($Cl % 16)) % 16;
191  $Al = strlen($A);
192  $v = (16 - ($Al % 16)) % 16;
193  $S = $this->ghash(
194  $A .
195  str_repeat("\x00", $v) .
196  $data .
197  str_repeat("\x00", $u) .
198  pack('H*', str_pad(gmp_strval(gmp_init($Al << 3, 10), 16), 16, '0', STR_PAD_LEFT)) .
199  pack('H*', str_pad(gmp_strval(gmp_init($Cl << 3, 10), 16), 16, '0', STR_PAD_LEFT))
200  );
201  $T2 = substr($this->gctr($J0, $S), 0, $this->taglen);
202  if ($T2 !== $T) {
203  throw new \InvalidArgumentException('Tag does not match expected value');
204  }
205  return $P;
206  }
207 }
$key
Secret key.
Definition: GCM.php:17
encrypt($iv, $data, &$tag=null, $aad= '')
$table
Pre-computation table for GF(2**128)
Definition: GCM.php:26
encrypt($data, $context)
Definition: GCM.php:157
__construct(CryptoInterface $cipher, $iv, $tagLength)
Definition: GCM.php:28
$taglen
Output tag length (in bytes)
Definition: GCM.php:23
$cipher
Approved block cipher with a 128-bit block size.
Definition: GCM.php:14
$iv
Initialization Vector.
Definition: GCM.php:20
decrypt($data, $context)
Definition: GCM.php:181