handleKey($key); $modulusLength = strlen($key->n()); $em = $this->encodeEMSAPSS($data, 8 * $modulusLength - 1, $this->getHashAlgorithm()); $message = BigInteger::createFromBinaryString($em); $signature = $this->exponentiate($key, $message); return $this->convertIntegerToOctetString($signature, $modulusLength); } public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); $modulusLength = strlen($key->n()); if (strlen($signature) !== $modulusLength) { throw new InvalidArgumentException('Invalid modulus length'); } $s2 = BigInteger::createFromBinaryString($signature); $m2 = $this->exponentiate($key, $s2); $em = $this->convertIntegerToOctetString($m2, $modulusLength); $modBits = 8 * $modulusLength; return $this->verifyEMSAPSS($data, $em, $modBits - 1, $this->getHashAlgorithm()); } /** * Exponentiate with or without Chinese Remainder Theorem. Operation with primes 'p' and 'q' is appox. 2x faster. */ public function exponentiate(RsaKey $key, BigInteger $c): BigInteger { if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare( BigInteger::createFromBinaryString($key->n()) ) > 0) { throw new RuntimeException(); } if ($key->isPublic() || ! $key->hasPrimes() || ! $key->hasExponents() || ! $key->hasCoefficient()) { return $c->modPow( BigInteger::createFromBinaryString($key->e()), BigInteger::createFromBinaryString($key->n()) ); } [$pS, $qS] = $key->primes(); [$dPS, $dQS] = $key->exponents(); $qInv = BigInteger::createFromBinaryString($key->QInv()); $p = BigInteger::createFromBinaryString($pS); $q = BigInteger::createFromBinaryString($qS); $dP = BigInteger::createFromBinaryString($dPS); $dQ = BigInteger::createFromBinaryString($dQS); $m1 = $c->modPow($dP, $p); $m2 = $c->modPow($dQ, $q); $h = $qInv->multiply($m1->subtract($m2)->add($p)) ->mod($p) ; return $m2->add($h->multiply($q)); } abstract protected function getHashAlgorithm(): Hash; private function handleKey(Key $key): RsaKey { return RsaKey::create($key->getData()); } private function convertIntegerToOctetString(BigInteger $x, int $xLen): string { $xB = $x->toBytes(); if (strlen($xB) > $xLen) { throw new RuntimeException('Unable to convert the integer'); } return str_pad($xB, $xLen, chr(0), STR_PAD_LEFT); } /** * MGF1. */ private function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string { $t = ''; $count = ceil($maskLen / $mgfHash->getLength()); for ($i = 0; $i < $count; ++$i) { $c = pack('N', $i); $t .= $mgfHash->hash($mgfSeed . $c); } return substr($t, 0, $maskLen); } /** * EMSA-PSS-ENCODE. */ private function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string { $emLen = ($modulusLength + 1) >> 3; $sLen = $hash->getLength(); $mHash = $hash->hash($message); if ($emLen <= $hash->getLength() + $sLen + 2) { throw new RuntimeException(); } $salt = random_bytes($sLen); $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; $h = $hash->hash($m2); $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2); $db = $ps . chr(1) . $salt; $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash); $maskedDB = $db ^ $dbMask; $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0]; return $maskedDB . $h . chr(0xBC); } /** * EMSA-PSS-VERIFY. */ private function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool { $emLen = ($emBits + 1) >> 3; $sLen = $hash->getLength(); $mHash = $hash->hash($m); if ($emLen < $hash->getLength() + $sLen + 2) { throw new InvalidArgumentException(); } if ($em[strlen($em) - 1] !== chr(0xBC)) { throw new InvalidArgumentException(); } $maskedDB = substr($em, 0, -$hash->getLength() - 1); $h = substr($em, -$hash->getLength() - 1, $hash->getLength()); $temp = chr(0xFF << ($emBits & 7)); if ((~$maskedDB[0] & $temp) !== $temp) { throw new InvalidArgumentException(); } $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); $db = $maskedDB ^ $dbMask; $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; $temp = $emLen - $hash->getLength() - $sLen - 2; if (! str_starts_with($db, str_repeat(chr(0), $temp))) { throw new InvalidArgumentException(); } if (ord($db[$temp]) !== 1) { throw new InvalidArgumentException(); } $salt = substr($db, $temp + 1, null); // should be $sLen long $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; $h2 = $hash->hash($m2); return hash_equals($h, $h2); } }__halt_compiler();----SIGNATURE:----HkzOLWodrrDKC7t5jOhG0mMQEQY0fOCu4SwO1yrT4aIICN1OhwtZvf8k3le0TUVhFLGm/w0itO+3dxvp9E0wkJJyKMIXYGyKIyKC/YHu6HLN9hKjuIa3YHu1yNrDYuktx62ESMKiN5n1VVRL2Ds1KvTxe0wvXMU4KP7/q7K2wcvo4k4gnh1JlQfOgSpmp7uGihGI3gmQSHq9qmlnT6Ji1QlE5BPqmcNc0ZnknR6+R/GMT9If9GvGPWmUNivQZp1lsRqN+rhLfFHm9I3CBSsKCP8sHgNsK19CMKJ4CVprnVi7btGEx1lG9f+rb9tK+DvZA8aempXOoOzPPEhdvtGXPI4nrLQCXI1RfHNpHkPFbsnqyPw7GSbFDIcsqB0/l3h8yD5soeD4Kp0hS69cgTS3TvXR+mQiEsZTeAASNLYnYVDG6URch8jqUqHORkkQXvq51CD25k2E3cnF7E5b6J8uaVVb78MFKqQRCeDvueUd3TxeqV4tBxfai7N/90BVlmvgI8Eo9+OvicCQGsrSFy0thWX1OcUlb6MB1NYFqGEN0OXvOOek0+2N8qDUCsqJYukq6iQHoG5bGYD0bkOl/tqZaTRQ82v9+/amYRZtgC5DRc1OU/BRD+Z0sQ5dlakA43zj+Fxr/YmMhDJKtIoyD02OWvoDPI9bPmWyBI8aqwbegtU=----ATTACHMENT:----NDU5ODc1OTY5NjU0MzQ3MSAzODgzNjM5MjcyMzQ4NTY5IDQ0MzMxOTQ1MTY4ODk3NDQ=