loadJWK($data->all()); $this->populateBigIntegers(); $this->private = \array_key_exists('d', $this->values); } /** * @return RSAKey */ public static function createFromJWK(JWK $jwk): self { return new self($jwk); } public function getModulus(): BigInteger { return $this->modulus; } public function getModulusLength(): int { return $this->modulus_length; } public function getExponent(): BigInteger { $d = $this->getPrivateExponent(); if (null !== $d) { return $d; } return $this->getPublicExponent(); } public function getPublicExponent(): BigInteger { return $this->public_exponent; } public function getPrivateExponent(): ?BigInteger { return $this->private_exponent; } /** * @return BigInteger[] */ public function getPrimes(): array { return $this->primes; } /** * @return BigInteger[] */ public function getExponents(): array { return $this->exponents; } public function getCoefficient(): ?BigInteger { return $this->coefficient; } public function isPublic(): bool { return !\array_key_exists('d', $this->values); } /** * @param RSAKey $private * * @return RSAKey */ public static function toPublic(self $private): self { $data = $private->toArray(); $keys = ['p', 'd', 'q', 'dp', 'dq', 'qi']; foreach ($keys as $key) { if (\array_key_exists($key, $data)) { unset($data[$key]); } } return new self(new JWK($data)); } public function toArray(): array { return $this->values; } private function loadJWK(array $jwk) { if (!\array_key_exists('kty', $jwk)) { throw new \InvalidArgumentException('The key parameter "kty" is missing.'); } if ('RSA' !== $jwk['kty']) { throw new \InvalidArgumentException('The JWK is not a RSA key.'); } $this->values = $jwk; } private function populateBigIntegers() { $this->modulus = $this->convertBase64StringToBigInteger($this->values['n']); $this->modulus_length = \mb_strlen($this->getModulus()->toBytes(), '8bit'); $this->public_exponent = $this->convertBase64StringToBigInteger($this->values['e']); if (!$this->isPublic()) { $this->private_exponent = $this->convertBase64StringToBigInteger($this->values['d']); if (\array_key_exists('p', $this->values) && \array_key_exists('q', $this->values)) { $this->primes = [ $this->convertBase64StringToBigInteger($this->values['p']), $this->convertBase64StringToBigInteger($this->values['q']), ]; if (\array_key_exists('dp', $this->values) && \array_key_exists('dq', $this->values) && \array_key_exists('qi', $this->values)) { $this->exponents = [ $this->convertBase64StringToBigInteger($this->values['dp']), $this->convertBase64StringToBigInteger($this->values['dq']), ]; $this->coefficient = $this->convertBase64StringToBigInteger($this->values['qi']); } } } } private function convertBase64StringToBigInteger(string $value): BigInteger { return BigInteger::createFromBinaryString(Base64Url::decode($value)); } /** * @throws \Exception */ public function toPEM(): string { if (null === $this->sequence) { $this->sequence = new Sequence(); if (\array_key_exists('d', $this->values)) { $this->initPrivateKey(); } else { $this->initPublicKey(); } } $result = '-----BEGIN '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL; $result .= \chunk_split(\base64_encode($this->sequence->getBinary()), 64, PHP_EOL); $result .= '-----END '.($this->private ? 'RSA PRIVATE' : 'PUBLIC').' KEY-----'.PHP_EOL; return $result; } /** * @throws \Exception */ private function initPublicKey() { $oid_sequence = new Sequence(); $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1')); $oid_sequence->addChild(new NullObject()); $this->sequence->addChild($oid_sequence); $n = new Integer($this->fromBase64ToInteger($this->values['n'])); $e = new Integer($this->fromBase64ToInteger($this->values['e'])); $key_sequence = new Sequence(); $key_sequence->addChild($n); $key_sequence->addChild($e); $key_bit_string = new BitString(\bin2hex($key_sequence->getBinary())); $this->sequence->addChild($key_bit_string); } private function initPrivateKey() { $this->sequence->addChild(new Integer(0)); $oid_sequence = new Sequence(); $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1')); $oid_sequence->addChild(new NullObject()); $this->sequence->addChild($oid_sequence); $v = new Integer(0); $n = new Integer($this->fromBase64ToInteger($this->values['n'])); $e = new Integer($this->fromBase64ToInteger($this->values['e'])); $d = new Integer($this->fromBase64ToInteger($this->values['d'])); $p = new Integer($this->fromBase64ToInteger($this->values['p'])); $q = new Integer($this->fromBase64ToInteger($this->values['q'])); $dp = \array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dp'])) : new Integer(0); $dq = \array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['dq'])) : new Integer(0); $qi = \array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger($this->values['qi'])) : new Integer(0); $key_sequence = new Sequence(); $key_sequence->addChild($v); $key_sequence->addChild($n); $key_sequence->addChild($e); $key_sequence->addChild($d); $key_sequence->addChild($p); $key_sequence->addChild($q); $key_sequence->addChild($dp); $key_sequence->addChild($dq); $key_sequence->addChild($qi); $key_octet_string = new OctetString(\bin2hex($key_sequence->getBinary())); $this->sequence->addChild($key_octet_string); } /** * @param string $value * * @return string */ private function fromBase64ToInteger($value) { return \gmp_strval(\gmp_init(\current(\unpack('H*', Base64Url::decode($value))), 16), 10); } /** * Exponentiate with or without Chinese Remainder Theorem. * Operation with primes 'p' and 'q' is appox. 2x faster. * * @param RSAKey $key */ public static function exponentiate(self $key, BigInteger $c): BigInteger { if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) { throw new \RuntimeException(); } if ($key->isPublic() || empty($key->getPrimes()) || empty($key->getExponents()) || null === $key->getCoefficient()) { return $c->modPow($key->getExponent(), $key->getModulus()); } $p = $key->getPrimes()[0]; $q = $key->getPrimes()[1]; $dP = $key->getExponents()[0]; $dQ = $key->getExponents()[1]; $qInv = $key->getCoefficient(); $m1 = $c->modPow($dP, $p); $m2 = $c->modPow($dQ, $q); $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p); $m = $m2->add($h->multiply($q)); return $m; } }