seal($nonce12, $plain, $aad); // 暗号文||タグ(16バイト) * $plain = $c->open($nonce12, $sealed, $aad); // 改ざんされていればnull */ class ChaCha20 { private array $k; /** * @param string $key 32バイト */ public function __construct(string $key) { if (strlen($key) !== 32) throw new \InvalidArgumentException('ChaCha20:鍵は32バイト'); $this->k = array_values(unpack('V8', $key)); } /** * 暗号化する(AEAD) * * @param string $nonce 12バイト * @param string $plain 平文 * @param string $aad 追加の認証データ * @return string 暗号文||タグ(16バイト) */ public function seal(string $nonce, string $plain, string $aad = ''): string { if (strlen($nonce) !== 12) throw new \InvalidArgumentException('ChaCha20:ノンスは12バイト'); $ct = $this->xor($nonce, $plain, 1); return $ct.$this->mac($nonce, $ct, $aad); } /** * 復号する(AEAD) * * @return string|null 平文、改ざんされていればnull */ public function open(string $nonce, string $sealed, string $aad = ''): ?string { if (strlen($nonce) !== 12 || strlen($sealed) < 16) return null; $ct = substr($sealed, 0, -16); if (!hash_equals($this->mac($nonce, $ct, $aad), substr($sealed, -16))) return null; return $this->xor($nonce, $ct, 1); } /** * 鍵ストリームとXORする(ChaCha20だけ) * * @param string $nonce 12バイト * @param string $data データ * @param int $counter 最初のブロックのカウンタ * @return string */ public function xor(string $nonce, string $data, int $counter = 0): string { $len = strlen($data); if ($len === 0) return ''; $n = unpack('V3', $nonce); $ks = ''; for ($i = 0, $blocks = intdiv($len + 63, 64); $i < $blocks; $i++) { $ks .= $this->block(($counter + $i) & 0xFFFFFFFF, $n[1], $n[2], $n[3]); } return $data ^ substr($ks, 0, $len); } /** Poly1305のタグ(鍵はカウンタ0のブロック) */ private function mac(string $nonce, string $ct, string $aad): string { $n = unpack('V3', $nonce); $key = substr($this->block(0, $n[1], $n[2], $n[3]), 0, 32); $pad = fn(string $s): string => strlen($s) % 16 ? str_repeat("\0", 16 - strlen($s) % 16) : ''; return self::poly1305($key, $aad.$pad($aad).$ct.$pad($ct).pack('P2', strlen($aad), strlen($ct))); } /** * Poly1305(RFC 8439 2.5、26ビットの5つの整数で計算する) * * @param string $key 32バイト(r||s) * @param string $msg メッセージ * @return string 16バイト */ public static function poly1305(string $key, string $msg): string { $m = 0x3FFFFFF; [, $t0, $t1, $t2, $t3] = unpack('V4', $key); $r0 = $t0 & 0x3FFFFFF; $r1 = (($t0 >> 26) | ($t1 << 6)) & 0x3FFFF03; $r2 = (($t1 >> 20) | ($t2 << 12)) & 0x3FFC0FF; $r3 = (($t2 >> 14) | ($t3 << 18)) & 0x3F03FFF; $r4 = ($t3 >> 8) & 0x00FFFFF; $s1 = $r1 * 5; $s2 = $r2 * 5; $s3 = $r3 * 5; $s4 = $r4 * 5; $h0 = $h1 = $h2 = $h3 = $h4 = 0; $len = strlen($msg); $full = $len - $len % 16; for ($i = 0; $i < $len; $i += 16) { if ($i < $full) { [, $t0, $t1, $t2, $t3] = unpack('V4', $msg, $i); $hibit = 1 << 24; } else { // 最後の短いブロック:1バイトの1を足して、0で埋める [, $t0, $t1, $t2, $t3] = unpack('V4', str_pad(substr($msg, $i)."\x01", 16, "\0")); $hibit = 0; } $h0 += $t0 & $m; $h1 += (($t0 >> 26) | ($t1 << 6)) & $m; $h2 += (($t1 >> 20) | ($t2 << 12)) & $m; $h3 += (($t2 >> 14) | ($t3 << 18)) & $m; $h4 += ($t3 >> 8) | $hibit; $d0 = $h0 * $r0 + $h1 * $s4 + $h2 * $s3 + $h3 * $s2 + $h4 * $s1; $d1 = $h0 * $r1 + $h1 * $r0 + $h2 * $s4 + $h3 * $s3 + $h4 * $s2; $d2 = $h0 * $r2 + $h1 * $r1 + $h2 * $r0 + $h3 * $s4 + $h4 * $s3; $d3 = $h0 * $r3 + $h1 * $r2 + $h2 * $r1 + $h3 * $r0 + $h4 * $s4; $d4 = $h0 * $r4 + $h1 * $r3 + $h2 * $r2 + $h3 * $r1 + $h4 * $r0; $c = $d0 >> 26; $h0 = $d0 & $m; $d1 += $c; $c = $d1 >> 26; $h1 = $d1 & $m; $d2 += $c; $c = $d2 >> 26; $h2 = $d2 & $m; $d3 += $c; $c = $d3 >> 26; $h3 = $d3 & $m; $d4 += $c; $c = $d4 >> 26; $h4 = $d4 & $m; $h0 += $c * 5; $c = $h0 >> 26; $h0 &= $m; $h1 += $c; } // 完全に繰り上げる $c = $h1 >> 26; $h1 &= $m; $h2 += $c; $c = $h2 >> 26; $h2 &= $m; $h3 += $c; $c = $h3 >> 26; $h3 &= $m; $h4 += $c; $c = $h4 >> 26; $h4 &= $m; $h0 += $c * 5; $c = $h0 >> 26; $h0 &= $m; $h1 += $c; // h - p(h >= pならこちら) $g0 = $h0 + 5; $c = $g0 >> 26; $g0 &= $m; $g1 = $h1 + $c; $c = $g1 >> 26; $g1 &= $m; $g2 = $h2 + $c; $c = $g2 >> 26; $g2 &= $m; $g3 = $h3 + $c; $c = $g3 >> 26; $g3 &= $m; $g4 = $h4 + $c - (1 << 26); if ($g4 >= 0) { [$h0, $h1, $h2, $h3, $h4] = [$g0, $g1, $g2, $g3, $g4]; } // h + s(mod 2^128) $w0 = ($h0 | ($h1 << 26)) & 0xFFFFFFFF; $w1 = (($h1 >> 6) | ($h2 << 20)) & 0xFFFFFFFF; $w2 = (($h2 >> 12) | ($h3 << 14)) & 0xFFFFFFFF; $w3 = (($h3 >> 18) | ($h4 << 8)) & 0xFFFFFFFF; [, $p0, $p1, $p2, $p3] = unpack('V4', $key, 16); $f = $w0 + $p0; $w0 = $f & 0xFFFFFFFF; $f = $w1 + $p1 + ($f >> 32); $w1 = $f & 0xFFFFFFFF; $f = $w2 + $p2 + ($f >> 32); $w2 = $f & 0xFFFFFFFF; $f = $w3 + $p3 + ($f >> 32); $w3 = $f & 0xFFFFFFFF; return pack('V4', $w0, $w1, $w2, $w3); } /** ChaCha20のブロック関数(RFC 8439 2.3) */ private function block(int $counter, int $n0, int $n1, int $n2): string { [$k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7] = $this->k; $x0 = 0x61707865; $x1 = 0x3320646e; $x2 = 0x79622d32; $x3 = 0x6b206574; $x4 = $k0; $x5 = $k1; $x6 = $k2; $x7 = $k3; $x8 = $k4; $x9 = $k5; $x10 = $k6; $x11 = $k7; $x12 = $counter; $x13 = $n0; $x14 = $n1; $x15 = $n2; $m = 0xFFFFFFFF; for ($i = 0; $i < 10; $i++) { // 列 $x0 = ($x0 + $x4) & $m; $x12 ^= $x0; $x12 = (($x12 << 16) | ($x12 >> 16)) & $m; $x8 = ($x8 + $x12) & $m; $x4 ^= $x8; $x4 = (($x4 << 12) | ($x4 >> 20)) & $m; $x0 = ($x0 + $x4) & $m; $x12 ^= $x0; $x12 = (($x12 << 8) | ($x12 >> 24)) & $m; $x8 = ($x8 + $x12) & $m; $x4 ^= $x8; $x4 = (($x4 << 7) | ($x4 >> 25)) & $m; $x1 = ($x1 + $x5) & $m; $x13 ^= $x1; $x13 = (($x13 << 16) | ($x13 >> 16)) & $m; $x9 = ($x9 + $x13) & $m; $x5 ^= $x9; $x5 = (($x5 << 12) | ($x5 >> 20)) & $m; $x1 = ($x1 + $x5) & $m; $x13 ^= $x1; $x13 = (($x13 << 8) | ($x13 >> 24)) & $m; $x9 = ($x9 + $x13) & $m; $x5 ^= $x9; $x5 = (($x5 << 7) | ($x5 >> 25)) & $m; $x2 = ($x2 + $x6) & $m; $x14 ^= $x2; $x14 = (($x14 << 16) | ($x14 >> 16)) & $m; $x10 = ($x10 + $x14) & $m; $x6 ^= $x10; $x6 = (($x6 << 12) | ($x6 >> 20)) & $m; $x2 = ($x2 + $x6) & $m; $x14 ^= $x2; $x14 = (($x14 << 8) | ($x14 >> 24)) & $m; $x10 = ($x10 + $x14) & $m; $x6 ^= $x10; $x6 = (($x6 << 7) | ($x6 >> 25)) & $m; $x3 = ($x3 + $x7) & $m; $x15 ^= $x3; $x15 = (($x15 << 16) | ($x15 >> 16)) & $m; $x11 = ($x11 + $x15) & $m; $x7 ^= $x11; $x7 = (($x7 << 12) | ($x7 >> 20)) & $m; $x3 = ($x3 + $x7) & $m; $x15 ^= $x3; $x15 = (($x15 << 8) | ($x15 >> 24)) & $m; $x11 = ($x11 + $x15) & $m; $x7 ^= $x11; $x7 = (($x7 << 7) | ($x7 >> 25)) & $m; // 対角線 $x0 = ($x0 + $x5) & $m; $x15 ^= $x0; $x15 = (($x15 << 16) | ($x15 >> 16)) & $m; $x10 = ($x10 + $x15) & $m; $x5 ^= $x10; $x5 = (($x5 << 12) | ($x5 >> 20)) & $m; $x0 = ($x0 + $x5) & $m; $x15 ^= $x0; $x15 = (($x15 << 8) | ($x15 >> 24)) & $m; $x10 = ($x10 + $x15) & $m; $x5 ^= $x10; $x5 = (($x5 << 7) | ($x5 >> 25)) & $m; $x1 = ($x1 + $x6) & $m; $x12 ^= $x1; $x12 = (($x12 << 16) | ($x12 >> 16)) & $m; $x11 = ($x11 + $x12) & $m; $x6 ^= $x11; $x6 = (($x6 << 12) | ($x6 >> 20)) & $m; $x1 = ($x1 + $x6) & $m; $x12 ^= $x1; $x12 = (($x12 << 8) | ($x12 >> 24)) & $m; $x11 = ($x11 + $x12) & $m; $x6 ^= $x11; $x6 = (($x6 << 7) | ($x6 >> 25)) & $m; $x2 = ($x2 + $x7) & $m; $x13 ^= $x2; $x13 = (($x13 << 16) | ($x13 >> 16)) & $m; $x8 = ($x8 + $x13) & $m; $x7 ^= $x8; $x7 = (($x7 << 12) | ($x7 >> 20)) & $m; $x2 = ($x2 + $x7) & $m; $x13 ^= $x2; $x13 = (($x13 << 8) | ($x13 >> 24)) & $m; $x8 = ($x8 + $x13) & $m; $x7 ^= $x8; $x7 = (($x7 << 7) | ($x7 >> 25)) & $m; $x3 = ($x3 + $x4) & $m; $x14 ^= $x3; $x14 = (($x14 << 16) | ($x14 >> 16)) & $m; $x9 = ($x9 + $x14) & $m; $x4 ^= $x9; $x4 = (($x4 << 12) | ($x4 >> 20)) & $m; $x3 = ($x3 + $x4) & $m; $x14 ^= $x3; $x14 = (($x14 << 8) | ($x14 >> 24)) & $m; $x9 = ($x9 + $x14) & $m; $x4 ^= $x9; $x4 = (($x4 << 7) | ($x4 >> 25)) & $m; } return pack('V16', ($x0 + 0x61707865) & $m, ($x1 + 0x3320646e) & $m, ($x2 + 0x79622d32) & $m, ($x3 + 0x6b206574) & $m, ($x4 + $k0) & $m, ($x5 + $k1) & $m, ($x6 + $k2) & $m, ($x7 + $k3) & $m, ($x8 + $k4) & $m, ($x9 + $k5) & $m, ($x10 + $k6) & $m, ($x11 + $k7) & $m, ($x12 + $counter) & $m, ($x13 + $n0) & $m, ($x14 + $n1) & $m, ($x15 + $n2) & $m); } }