cryptal  latest
Cryptography Abstraction Layer
ChaCha20.php
1 <?php
2 
4 
13 
25 class ChaCha20 implements CryptoInterface
26 {
28  protected $key;
29 
31  protected $tagLength;
32 
33  protected $cipher;
34 
35  public function __construct(
36  CipherEnum $cipher,
37  ModeEnum $mode,
38  PaddingInterface $padding,
39  $key,
40  $tagLength = self::DEFAULT_TAG_LENGTH
41  ) {
42  if (CipherEnum::CIPHER_CHACHA20() !== $cipher) {
43  throw new \InvalidArgumentException('Unsupported cipher');
44  }
45 
46  if (!($padding instanceof None)) {
47  throw new \InvalidArgumentException(
48  'ChaCha20 does not need any padding ' .
49  '(hint: use fpoirotte\Cryptal\Padding\None)'
50  );
51  }
52 
53  if (0 !== $tagLength && 16 !== $tagLength) {
54  throw new \InvalidArgumentException('Invalid tag length: must be 16 to enable AEAD, 0 to disable');
55  }
56 
57  if (32 !== strlen($key)) {
58  throw new \InvalidArgumentException('Invalid key length');
59  }
60 
61  $this->tagLength = $tagLength;
62  $this->key = $key;
63  $this->cipher = $cipher;
64  }
65 
66  protected static function quarterRound(&$a, &$b, &$c, &$d)
67  {
68  $a += $b;
69  $a &= 0xFFFFFFFF;
70  $d ^= $a;
71  $d = (($d & 0xFFFF) << 16) | (($d >> 16) & 0xFFFF);
72 
73  $c += $d;
74  $c &= 0xFFFFFFFF;
75  $b ^= $c;
76  $b = (($b & 0xFFFFF) << 12) | (($b >> 20) & 0xFFF);
77 
78  $a += $b;
79  $a &= 0xFFFFFFFF;
80  $d ^= $a;
81  $d = (($d & 0xFFFFFF) << 8) | (($d >> 24) & 0xFF);
82 
83  $c += $d;
84  $c &= 0xFFFFFFFF;
85  $b ^= $c;
86  $b = (($b & 0x1FFFFFF) << 7) | (($b >> 25) & 0x7F);
87  }
88 
89  protected function block($iv, $counter)
90  {
91 
92  $block = array_values(
93  unpack('V*', 'expand 32-byte k' . $this->key . $counter . $iv)
94  );
95  $init = $block;
96 
97  for ($i = 0; $i < 10; $i++) {
98  static::quarterRound($block[ 0], $block[ 4], $block[ 8], $block[12]);
99  static::quarterRound($block[ 1], $block[ 5], $block[ 9], $block[13]);
100  static::quarterRound($block[ 2], $block[ 6], $block[10], $block[14]);
101  static::quarterRound($block[ 3], $block[ 7], $block[11], $block[15]);
102 
103  static::quarterRound($block[ 0], $block[ 5], $block[10], $block[15]);
104  static::quarterRound($block[ 1], $block[ 6], $block[11], $block[12]);
105  static::quarterRound($block[ 2], $block[ 7], $block[ 8], $block[13]);
106  static::quarterRound($block[ 3], $block[ 4], $block[ 9], $block[14]);
107  }
108 
109  $res = '';
110  for ($i = 0; $i < 16; $i++) {
111  $res .= pack('V', ($block[$i] + $init[$i]) & 0xFFFFFFFF);
112  }
113  return $res;
114  }
115 
116  protected function basicXcrypt($plain, $iv, $counter = 0)
117  {
118  $ivSize = $this->getIVSize();
119  if (strlen($iv) !== $ivSize) {
120  throw new \InvalidArgumentException("Invalid Initialization Vector (should be $ivSize bytes long)");
121  }
122 
123  $len = strlen($plain);
124  $m = ($len >> 6) + (($len % 64) > 0);
125  $keyStream = '';
126  for ($i = 0; $i < $m; $i++) {
127  $c = gmp_strval(gmp_add($counter, $i), 16);
128  $c = pack('H*', str_pad($c, (16 - $ivSize) << 1, '0', STR_PAD_LEFT));
129  $keyStream .= $this->block($iv, strrev($c));
130  }
131  return $plain ^ $keyStream;
132  }
133 
134  public function encrypt($iv, $data, &$tag = null, $aad = '')
135  {
136  if (!$this->tagLength) {
137  return $this->basicXcrypt($data, $iv, 0);
138  }
139 
140  $polyKey = substr($this->block($iv, str_repeat("\x00", 4)), 0, 32);
141  $ciphertext = $this->basicXcrypt($data, $iv, 1);
142  $pad1 = str_repeat("\x00", (16 - (strlen($aad) % 16)) % 16);
143  $pad2 = str_repeat("\x00", (16 - (strlen($ciphertext) % 16)) % 16);
144  $aadLen = pack('V*', strlen($aad), 0);
145  $ctLen = pack('V*', strlen($ciphertext), 0);
146  $tag = Poly1305::mac(
147  MacEnum::MAC_POLY1305(),
148  CipherEnum::CIPHER_CHACHA20(),
149  $polyKey,
150  $aad . $pad1 . $ciphertext . $pad2 . $aadLen . $ctLen,
151  '',
152  true
153  );
154  return $ciphertext;
155  }
156 
157  public function decrypt($iv, $data, $tag = null, $aad = '')
158  {
159  if (!$this->tagLength) {
160  return $this->basicXcrypt($data, $iv, 0);
161  }
162 
163  $polyKey = substr($this->block($iv, str_repeat("\x00", 4)), 0, 32);
164  $pad1 = str_repeat("\x00", (16 - (strlen($aad) % 16)) % 16);
165  $pad2 = str_repeat("\x00", (16 - (strlen($data) % 16)) % 16);
166  $aadLen = pack('V*', strlen($aad), 0);
167  $ctLen = pack('V*', strlen($data), 0);
168  $outTag = Poly1305::mac(
169  MacEnum::MAC_POLY1305(),
170  CipherEnum::CIPHER_CHACHA20(),
171  $polyKey,
172  $aad . $pad1 . $data . $pad2 . $aadLen . $ctLen,
173  '',
174  true
175  );
176 
177  if ($outTag !== $tag) {
178  throw new \InvalidArgumentException('Invalid tag');
179  }
180 
181  return $this->basicXcrypt($data, $iv, 1);
182  }
183 
184  public function getIVSize()
185  {
186  return 12;
187  }
188 
189  public function getBlockSize()
190  {
191  // ChaCha20 does not use blocks, which is the same
192  // as saying each byte in the input is a separate block.
193  return 1;
194  }
195 
196  public function getCipher()
197  {
198  return $this->cipher;
199  }
200 
201  public function getKey()
202  {
203  return $this->key;
204  }
205 }
encrypt($iv, $data, &$tag=null, $aad= '')
Definition: ChaCha20.php:134
decrypt($iv, $data, $tag=null, $aad= '')
Definition: ChaCha20.php:157
__construct(CipherEnum $cipher, ModeEnum $mode, PaddingInterface $padding, $key, $tagLength=self::DEFAULT_TAG_LENGTH)
Definition: ChaCha20.php:35
$key
Secret key used to encrypt/decrypt data.
Definition: ChaCha20.php:28
static mac(MacEnum $macAlgorithm, SubAlgorithmAbstractEnum $innerAlgorithm, $key, $data, $nonce= '', $raw=false)
Definition: AbstractMac.php:15
$tagLength
Tag length in bytes; 16 when AEAD is enabled, 0 otherwise.
Definition: ChaCha20.php:31