key = $packet; } else { $this->message = $packet; } } function key($keyid=NULL) { if(!$this->key) return NULL; // No key if($this->key instanceof OpenPGP_Message) { foreach($this->key as $p) { if($p instanceof OpenPGP_PublicKeyPacket) { if(!$keyid || strtoupper(substr($p->fingerprint, strlen($keyid)*-1)) == strtoupper($keyid)) return $p; } } } return $this->key; } // Get Crypt_RSA for the public key function public_key($keyid=NULL) { return self::convert_public_key($this->key($keyid)); } // Get Crypt_RSA for the private key function private_key($keyid=NULL) { return self::convert_private_key($this->key($keyid)); } // Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with // Second optional parameter to specify which signature to verify (if there is more than one) function verify($packet) { $self = $this; // For old PHP if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); if(!$this->message) { $m = $packet; $verifier = function($m, $s) use($self) { $key = $self->public_key($s->issuer()); if(!$key) return false; $key = $key->withHash(strtolower($s->hash_algorithm_name())); return $key->verify($m, reset($s->data)); }; } else { if(!($packet instanceof Crypt_RSA)) { $packet = new self($packet); } $m = $this->message; $verifier = function($m, $s) use($self, $packet) { if(!($packet instanceof Crypt_RSA)) { $key = $packet->public_key($s->issuer()); } if(!$key) return false; $key = $key->withHash(strtolower($s->hash_algorithm_name())); return $key->verify($m, reset($s->data)); }; } return $m->verified_signatures(array('RSA' => array( 'MD5' => $verifier, 'SHA1' => $verifier, 'SHA224' => $verifier, 'SHA256' => $verifier, 'SHA384' => $verifier, 'SHA512' => $verifier ))); } // Pass a message to sign with this key, or a secret key to sign this message with // Second parameter is hash algorithm to use (default SHA256) // Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet function sign($packet, $hash='SHA256', $keyid=NULL) { if(!is_object($packet)) { if($this->key) { $packet = new OpenPGP_LiteralDataPacket($packet); } else { $packet = OpenPGP_Message::parse($packet); } } if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA || ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) { $key = $packet; $message = $this->message; } else { $key = $this->key; $message = $packet; } if(!$key || !$message) return NULL; // Missing some data if($message instanceof OpenPGP_Message) { $sign = $message->signatures(); $message = $sign[0][0]; } if(!($key instanceof Crypt_RSA)) { $key = new self($key); if(!$keyid) $keyid = substr($key->key()->fingerprint, -16, 16); $key = $key->private_key($keyid); } $key = $key->withHash(strtolower($hash)); $sig = new OpenPGP_SignaturePacket($message, 'RSA', strtoupper($hash)); $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); $sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));}))); return new OpenPGP_Message(array($sig, $message)); } /** Pass a message with a key and userid packet to sign */ // TODO: merge this with the normal sign function function sign_key_userid($packet, $hash='SHA256', $keyid=NULL) { if(is_array($packet)) { $packet = new OpenPGP_Message($packet); } else if(!is_object($packet)) { $packet = OpenPGP_Message::parse($packet); } $key = $this->private_key($keyid); if(!$key || !$packet) return NULL; // Missing some data if(!$keyid) $keyid = substr($this->key->fingerprint, -16); $key = $key->withHash(strtolower($hash)); $sig = NULL; foreach($packet as $p) { if($p instanceof OpenPGP_SignaturePacket) $sig = $p; } if(!$sig) { $sig = new OpenPGP_SignaturePacket($packet, 'RSA', strtoupper($hash)); $sig->signature_type = 0x13; $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02)); $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); $packet[] = $sig; } $sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));}))); return $packet; } function decrypt($packet) { if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA || ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) { $keys = $packet; $message = $this->message; } else { $keys = $this->key; $message = $packet; } if(!$keys || !$message) return NULL; // Missing some data if(!($keys instanceof Crypt_RSA)) { $keys = new self($keys); } $session_key = NULL; foreach($message as $p) { if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) { $session_key = $p; if($keys instanceof Crypt_RSA) { $sk = self::try_decrypt_session($keys, substr($p->encrypted_data, 2)); } else if(strlen(str_replace('0', '', $p->keyid)) < 1) { foreach($keys->key as $k) { $sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2)); if($sk) break; } } else { $key = $keys->private_key($p->keyid); $sk = self::try_decrypt_session($key, substr($p->encrypted_data, 2)); } if(!$sk) continue; $r = OpenPGP_Crypt_Symmetric::decryptPacket(OpenPGP_Crypt_Symmetric::getEncryptedData($message), $sk[0], $sk[1]); if($r) return $r; } } if (!$session_key) throw new Exception("Not an asymmetrically encrypted message"); return NULL; /* Failed */ } static function try_decrypt_session($key, $edata) { $key = $key->withPadding(CRYPT_RSA_ENCRYPTION_PKCS1 | CRYPT_RSA_SIGNATURE_PKCS1); try { $data = $key->decrypt($edata); } catch (\RuntimeException $e) { return NULL; } if(!$data) return NULL; $sk = substr($data, 1, strlen($data)-3); $chk = unpack('n', substr($data, -2)); $chk = reset($chk); $sk_chk = 0; for($i = 0; $i < strlen($sk); $i++) { $sk_chk = ($sk_chk + ord($sk[$i])) % 65536; } if($sk_chk != $chk) return NULL; return array(ord($data[0]), $sk); } static function crypt_rsa_key($mod, $exp, $hash='SHA256') { return Crypt_RSA::loadPublicKey([ 'e' => new Math_BigInteger($exp, 256), 'n' => new Math_BigInteger($mod, 256), ]) ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) ->withHash(strtolower($hash)); } static function convert_key($packet, $private=false) { if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); if($packet instanceof OpenPGP_Message) $packet = $packet[0]; $exp = $packet->key['e']; if($private) $exp = $packet->key['d']; if(!$exp) return NULL; // Packet doesn't have needed data /** * @see https://github.com/phpseclib/phpseclib/issues/1113 * Primes and coefficients now use BigIntegers. **/ if($private) { // Invert p and q to make u work out as q' $rawKey = [ 'e' => new Math_BigInteger($packet->key['e'], 256), 'n' => new Math_BigInteger($packet->key['n'], 256), 'd' => new Math_BigInteger($packet->key['d'], 256), 'q' => new Math_BigInteger($packet->key['p'], 256), 'p' => new Math_BigInteger($packet->key['q'], 256), ]; if (array_key_exists('u', $packet->key)) { // possible keys for 'u': https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Crypt/RSA/Formats/Keys/Raw.php#L108 $rawKey['inerseq'] = new Math_BigInteger($packet->key['u'], 256); } return publickeyloader::loadPrivateKey($rawKey) ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) ->withHash('sha256'); } else { return publickeyloader::loadPublicKey([ 'e' => new Math_BigInteger($packet->key['e'], 256), 'n' => new Math_BigInteger($packet->key['n'], 256), ]) ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) ->withHash('sha256'); } } static function convert_public_key($packet) { return self::convert_key($packet, false); } static function convert_private_key($packet) { return self::convert_key($packet, true); } } ?>