| 1 | <?php |
| 2 | /************************************************************* |
| 3 | # 076 License |
| 4 | |
| 5 | Copyright (c) テクニカル諏訪子 |
| 6 | |
| 7 | Permission is hereby granted to any person obtaining a copy of the software |
| 8 | Little Beast (the "Software") to use, modify, merge, copy, publish, distribute, |
| 9 | sublicense, and/or sell copies of the Software, subject to the following conditions: |
| 10 | |
| 11 | 1. **Origin Attribution**: |
| 12 | - You must not misrepresent the origin of the Software; you must not claim |
| 13 | you created the original Software. |
| 14 | - If the Software is used in a product, you must either: |
| 15 | a. Provide clear attribution in the product's documentation, user interface, |
| 16 | or other visible areas, **OR** |
| 17 | b. Pay the original developers a fee they specify in writing. |
| 18 | 2. **Usage Restriction**: |
| 19 | - The Software, or any derivative works, dependencies, or libraries |
| 20 | incorporating it, must not be used for censorship or to suppress freedom of |
| 21 | speech, expression, or creativity. Prohibited uses include, but are not |
| 22 | limited to: |
| 23 | - Censorship of so-called "hate speech", visuals, non-mainstream opinions, |
| 24 | ideas, or objective reality. |
| 25 | - Tools or systems designed to restrict access to information or |
| 26 | artistic works. |
| 27 | 3. **Notice Preservation**: |
| 28 | - This license and the above copyright notice must remain intact in all copies |
| 29 | of the source code. |
| 30 | |
| 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| 32 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
| 33 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 34 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| 35 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |
| 36 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 37 | */ |
| 38 | namespace Std\Lib\Crypto; |
| 39 | |
| 40 | /** |
| 41 | * ChaCha20-Poly1305(RFC 8439のAEAD) |
| 42 | * |
| 43 | * 例: |
| 44 | * $c = new ChaCha20($key32); |
| 45 | * $sealed = $c->seal($nonce12, $plain, $aad); // 暗号文||タグ(16バイト) |
| 46 | * $plain = $c->open($nonce12, $sealed, $aad); // 改ざんされていればnull |
| 47 | */ |
| 48 | class ChaCha20 { |
| 49 | private array $k; |
| 50 | |
| 51 | /** |
| 52 | * @param string $key 32バイト |
| 53 | */ |
| 54 | public function __construct(string $key) { |
| 55 | if (strlen($key) !== 32) throw new \InvalidArgumentException('ChaCha20:鍵は32バイト'); |
| 56 | $this->k = array_values(unpack('V8', $key)); |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * 暗号化する(AEAD) |
| 61 | * |
| 62 | * @param string $nonce 12バイト |
| 63 | * @param string $plain 平文 |
| 64 | * @param string $aad 追加の認証データ |
| 65 | * @return string 暗号文||タグ(16バイト) |
| 66 | */ |
| 67 | public function seal(string $nonce, string $plain, string $aad = ''): string { |
| 68 | if (strlen($nonce) !== 12) throw new \InvalidArgumentException('ChaCha20:ノンスは12バイト'); |
| 69 | $ct = $this->xor($nonce, $plain, 1); |
| 70 | return $ct.$this->mac($nonce, $ct, $aad); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * 復号する(AEAD) |
| 75 | * |
| 76 | * @return string|null 平文、改ざんされていればnull |
| 77 | */ |
| 78 | public function open(string $nonce, string $sealed, string $aad = ''): ?string { |
| 79 | if (strlen($nonce) !== 12 || strlen($sealed) < 16) return null; |
| 80 | $ct = substr($sealed, 0, -16); |
| 81 | if (!hash_equals($this->mac($nonce, $ct, $aad), substr($sealed, -16))) return null; |
| 82 | return $this->xor($nonce, $ct, 1); |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * 鍵ストリームとXORする(ChaCha20だけ) |
| 87 | * |
| 88 | * @param string $nonce 12バイト |
| 89 | * @param string $data データ |
| 90 | * @param int $counter 最初のブロックのカウンタ |
| 91 | * @return string |
| 92 | */ |
| 93 | public function xor(string $nonce, string $data, int $counter = 0): string { |
| 94 | $len = strlen($data); |
| 95 | if ($len === 0) return ''; |
| 96 | $n = unpack('V3', $nonce); |
| 97 | $ks = ''; |
| 98 | for ($i = 0, $blocks = intdiv($len + 63, 64); $i < $blocks; $i++) { |
| 99 | $ks .= $this->block(($counter + $i) & 0xFFFFFFFF, $n[1], $n[2], $n[3]); |
| 100 | } |
| 101 | return $data ^ substr($ks, 0, $len); |
| 102 | } |
| 103 | |
| 104 | /** Poly1305のタグ(鍵はカウンタ0のブロック) */ |
| 105 | private function mac(string $nonce, string $ct, string $aad): string { |
| 106 | $n = unpack('V3', $nonce); |
| 107 | $key = substr($this->block(0, $n[1], $n[2], $n[3]), 0, 32); |
| 108 | $pad = fn(string $s): string => strlen($s) % 16 ? str_repeat("\0", 16 - strlen($s) % 16) : ''; |
| 109 | return self::poly1305($key, $aad.$pad($aad).$ct.$pad($ct).pack('P2', strlen($aad), strlen($ct))); |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Poly1305(RFC 8439 2.5、26ビットの5つの整数で計算する) |
| 114 | * |
| 115 | * @param string $key 32バイト(r||s) |
| 116 | * @param string $msg メッセージ |
| 117 | * @return string 16バイト |
| 118 | */ |
| 119 | public static function poly1305(string $key, string $msg): string { |
| 120 | $m = 0x3FFFFFF; |
| 121 | [, $t0, $t1, $t2, $t3] = unpack('V4', $key); |
| 122 | $r0 = $t0 & 0x3FFFFFF; |
| 123 | $r1 = (($t0 >> 26) | ($t1 << 6)) & 0x3FFFF03; |
| 124 | $r2 = (($t1 >> 20) | ($t2 << 12)) & 0x3FFC0FF; |
| 125 | $r3 = (($t2 >> 14) | ($t3 << 18)) & 0x3F03FFF; |
| 126 | $r4 = ($t3 >> 8) & 0x00FFFFF; |
| 127 | $s1 = $r1 * 5; |
| 128 | $s2 = $r2 * 5; |
| 129 | $s3 = $r3 * 5; |
| 130 | $s4 = $r4 * 5; |
| 131 | $h0 = $h1 = $h2 = $h3 = $h4 = 0; |
| 132 | |
| 133 | $len = strlen($msg); |
| 134 | $full = $len - $len % 16; |
| 135 | for ($i = 0; $i < $len; $i += 16) { |
| 136 | if ($i < $full) { |
| 137 | [, $t0, $t1, $t2, $t3] = unpack('V4', $msg, $i); |
| 138 | $hibit = 1 << 24; |
| 139 | } else { |
| 140 | // 最後の短いブロック:1バイトの1を足して、0で埋める |
| 141 | [, $t0, $t1, $t2, $t3] = unpack('V4', str_pad(substr($msg, $i)."\x01", 16, "\0")); |
| 142 | $hibit = 0; |
| 143 | } |
| 144 | $h0 += $t0 & $m; |
| 145 | $h1 += (($t0 >> 26) | ($t1 << 6)) & $m; |
| 146 | $h2 += (($t1 >> 20) | ($t2 << 12)) & $m; |
| 147 | $h3 += (($t2 >> 14) | ($t3 << 18)) & $m; |
| 148 | $h4 += ($t3 >> 8) | $hibit; |
| 149 | |
| 150 | $d0 = $h0 * $r0 + $h1 * $s4 + $h2 * $s3 + $h3 * $s2 + $h4 * $s1; |
| 151 | $d1 = $h0 * $r1 + $h1 * $r0 + $h2 * $s4 + $h3 * $s3 + $h4 * $s2; |
| 152 | $d2 = $h0 * $r2 + $h1 * $r1 + $h2 * $r0 + $h3 * $s4 + $h4 * $s3; |
| 153 | $d3 = $h0 * $r3 + $h1 * $r2 + $h2 * $r1 + $h3 * $r0 + $h4 * $s4; |
| 154 | $d4 = $h0 * $r4 + $h1 * $r3 + $h2 * $r2 + $h3 * $r1 + $h4 * $r0; |
| 155 | |
| 156 | $c = $d0 >> 26; $h0 = $d0 & $m; |
| 157 | $d1 += $c; $c = $d1 >> 26; $h1 = $d1 & $m; |
| 158 | $d2 += $c; $c = $d2 >> 26; $h2 = $d2 & $m; |
| 159 | $d3 += $c; $c = $d3 >> 26; $h3 = $d3 & $m; |
| 160 | $d4 += $c; $c = $d4 >> 26; $h4 = $d4 & $m; |
| 161 | $h0 += $c * 5; $c = $h0 >> 26; $h0 &= $m; |
| 162 | $h1 += $c; |
| 163 | } |
| 164 | |
| 165 | // 完全に繰り上げる |
| 166 | $c = $h1 >> 26; $h1 &= $m; |
| 167 | $h2 += $c; $c = $h2 >> 26; $h2 &= $m; |
| 168 | $h3 += $c; $c = $h3 >> 26; $h3 &= $m; |
| 169 | $h4 += $c; $c = $h4 >> 26; $h4 &= $m; |
| 170 | $h0 += $c * 5; $c = $h0 >> 26; $h0 &= $m; |
| 171 | $h1 += $c; |
| 172 | |
| 173 | // h - p(h >= pならこちら) |
| 174 | $g0 = $h0 + 5; $c = $g0 >> 26; $g0 &= $m; |
| 175 | $g1 = $h1 + $c; $c = $g1 >> 26; $g1 &= $m; |
| 176 | $g2 = $h2 + $c; $c = $g2 >> 26; $g2 &= $m; |
| 177 | $g3 = $h3 + $c; $c = $g3 >> 26; $g3 &= $m; |
| 178 | $g4 = $h4 + $c - (1 << 26); |
| 179 | if ($g4 >= 0) { |
| 180 | [$h0, $h1, $h2, $h3, $h4] = [$g0, $g1, $g2, $g3, $g4]; |
| 181 | } |
| 182 | |
| 183 | // h + s(mod 2^128) |
| 184 | $w0 = ($h0 | ($h1 << 26)) & 0xFFFFFFFF; |
| 185 | $w1 = (($h1 >> 6) | ($h2 << 20)) & 0xFFFFFFFF; |
| 186 | $w2 = (($h2 >> 12) | ($h3 << 14)) & 0xFFFFFFFF; |
| 187 | $w3 = (($h3 >> 18) | ($h4 << 8)) & 0xFFFFFFFF; |
| 188 | [, $p0, $p1, $p2, $p3] = unpack('V4', $key, 16); |
| 189 | $f = $w0 + $p0; $w0 = $f & 0xFFFFFFFF; |
| 190 | $f = $w1 + $p1 + ($f >> 32); $w1 = $f & 0xFFFFFFFF; |
| 191 | $f = $w2 + $p2 + ($f >> 32); $w2 = $f & 0xFFFFFFFF; |
| 192 | $f = $w3 + $p3 + ($f >> 32); $w3 = $f & 0xFFFFFFFF; |
| 193 | return pack('V4', $w0, $w1, $w2, $w3); |
| 194 | } |
| 195 | |
| 196 | /** ChaCha20のブロック関数(RFC 8439 2.3) */ |
| 197 | private function block(int $counter, int $n0, int $n1, int $n2): string { |
| 198 | [$k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7] = $this->k; |
| 199 | $x0 = 0x61707865; $x1 = 0x3320646e; $x2 = 0x79622d32; $x3 = 0x6b206574; |
| 200 | $x4 = $k0; $x5 = $k1; $x6 = $k2; $x7 = $k3; $x8 = $k4; $x9 = $k5; $x10 = $k6; $x11 = $k7; |
| 201 | $x12 = $counter; $x13 = $n0; $x14 = $n1; $x15 = $n2; |
| 202 | $m = 0xFFFFFFFF; |
| 203 | |
| 204 | for ($i = 0; $i < 10; $i++) { |
| 205 | // 列 |
| 206 | $x0 = ($x0 + $x4) & $m; $x12 ^= $x0; $x12 = (($x12 << 16) | ($x12 >> 16)) & $m; |
| 207 | $x8 = ($x8 + $x12) & $m; $x4 ^= $x8; $x4 = (($x4 << 12) | ($x4 >> 20)) & $m; |
| 208 | $x0 = ($x0 + $x4) & $m; $x12 ^= $x0; $x12 = (($x12 << 8) | ($x12 >> 24)) & $m; |
| 209 | $x8 = ($x8 + $x12) & $m; $x4 ^= $x8; $x4 = (($x4 << 7) | ($x4 >> 25)) & $m; |
| 210 | $x1 = ($x1 + $x5) & $m; $x13 ^= $x1; $x13 = (($x13 << 16) | ($x13 >> 16)) & $m; |
| 211 | $x9 = ($x9 + $x13) & $m; $x5 ^= $x9; $x5 = (($x5 << 12) | ($x5 >> 20)) & $m; |
| 212 | $x1 = ($x1 + $x5) & $m; $x13 ^= $x1; $x13 = (($x13 << 8) | ($x13 >> 24)) & $m; |
| 213 | $x9 = ($x9 + $x13) & $m; $x5 ^= $x9; $x5 = (($x5 << 7) | ($x5 >> 25)) & $m; |
| 214 | $x2 = ($x2 + $x6) & $m; $x14 ^= $x2; $x14 = (($x14 << 16) | ($x14 >> 16)) & $m; |
| 215 | $x10 = ($x10 + $x14) & $m; $x6 ^= $x10; $x6 = (($x6 << 12) | ($x6 >> 20)) & $m; |
| 216 | $x2 = ($x2 + $x6) & $m; $x14 ^= $x2; $x14 = (($x14 << 8) | ($x14 >> 24)) & $m; |
| 217 | $x10 = ($x10 + $x14) & $m; $x6 ^= $x10; $x6 = (($x6 << 7) | ($x6 >> 25)) & $m; |
| 218 | $x3 = ($x3 + $x7) & $m; $x15 ^= $x3; $x15 = (($x15 << 16) | ($x15 >> 16)) & $m; |
| 219 | $x11 = ($x11 + $x15) & $m; $x7 ^= $x11; $x7 = (($x7 << 12) | ($x7 >> 20)) & $m; |
| 220 | $x3 = ($x3 + $x7) & $m; $x15 ^= $x3; $x15 = (($x15 << 8) | ($x15 >> 24)) & $m; |
| 221 | $x11 = ($x11 + $x15) & $m; $x7 ^= $x11; $x7 = (($x7 << 7) | ($x7 >> 25)) & $m; |
| 222 | // 対角線 |
| 223 | $x0 = ($x0 + $x5) & $m; $x15 ^= $x0; $x15 = (($x15 << 16) | ($x15 >> 16)) & $m; |
| 224 | $x10 = ($x10 + $x15) & $m; $x5 ^= $x10; $x5 = (($x5 << 12) | ($x5 >> 20)) & $m; |
| 225 | $x0 = ($x0 + $x5) & $m; $x15 ^= $x0; $x15 = (($x15 << 8) | ($x15 >> 24)) & $m; |
| 226 | $x10 = ($x10 + $x15) & $m; $x5 ^= $x10; $x5 = (($x5 << 7) | ($x5 >> 25)) & $m; |
| 227 | $x1 = ($x1 + $x6) & $m; $x12 ^= $x1; $x12 = (($x12 << 16) | ($x12 >> 16)) & $m; |
| 228 | $x11 = ($x11 + $x12) & $m; $x6 ^= $x11; $x6 = (($x6 << 12) | ($x6 >> 20)) & $m; |
| 229 | $x1 = ($x1 + $x6) & $m; $x12 ^= $x1; $x12 = (($x12 << 8) | ($x12 >> 24)) & $m; |
| 230 | $x11 = ($x11 + $x12) & $m; $x6 ^= $x11; $x6 = (($x6 << 7) | ($x6 >> 25)) & $m; |
| 231 | $x2 = ($x2 + $x7) & $m; $x13 ^= $x2; $x13 = (($x13 << 16) | ($x13 >> 16)) & $m; |
| 232 | $x8 = ($x8 + $x13) & $m; $x7 ^= $x8; $x7 = (($x7 << 12) | ($x7 >> 20)) & $m; |
| 233 | $x2 = ($x2 + $x7) & $m; $x13 ^= $x2; $x13 = (($x13 << 8) | ($x13 >> 24)) & $m; |
| 234 | $x8 = ($x8 + $x13) & $m; $x7 ^= $x8; $x7 = (($x7 << 7) | ($x7 >> 25)) & $m; |
| 235 | $x3 = ($x3 + $x4) & $m; $x14 ^= $x3; $x14 = (($x14 << 16) | ($x14 >> 16)) & $m; |
| 236 | $x9 = ($x9 + $x14) & $m; $x4 ^= $x9; $x4 = (($x4 << 12) | ($x4 >> 20)) & $m; |
| 237 | $x3 = ($x3 + $x4) & $m; $x14 ^= $x3; $x14 = (($x14 << 8) | ($x14 >> 24)) & $m; |
| 238 | $x9 = ($x9 + $x14) & $m; $x4 ^= $x9; $x4 = (($x4 << 7) | ($x4 >> 25)) & $m; |
| 239 | } |
| 240 | |
| 241 | return pack('V16', |
| 242 | ($x0 + 0x61707865) & $m, ($x1 + 0x3320646e) & $m, ($x2 + 0x79622d32) & $m, ($x3 + 0x6b206574) & $m, |
| 243 | ($x4 + $k0) & $m, ($x5 + $k1) & $m, ($x6 + $k2) & $m, ($x7 + $k3) & $m, |
| 244 | ($x8 + $k4) & $m, ($x9 + $k5) & $m, ($x10 + $k6) & $m, ($x11 + $k7) & $m, |
| 245 | ($x12 + $counter) & $m, ($x13 + $n0) & $m, ($x14 + $n1) & $m, ($x15 + $n2) & $m); |
| 246 | } |
| 247 | } |