* @author Stephen Paul Weber * @author Deon George * @see http://github.com/bendiken/openpgp-php */ namespace Leenooks; use Illuminate\Support\Arr; use phpseclib\Crypt\RSA as Crypt_RSA; use Leenooks\OpenPGP\Exceptions\PacketTagException; /** * @see http://tools.ietf.org/html/rfc4880 */ class OpenPGP { const VERSION = [0,5,0]; private $key = NULL; // Functions /** * @see http://tools.ietf.org/html/rfc4880#section-12.2 */ static function bitlength($data) { return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1; } /** * Create a PGP Key * * @todo Incomplete and untested. * * @param string $name * @param string $email * @param string $comment * @param int $keysize * @return OpenPGP */ static function create(string $name,string $email,string $comment,int $keysize=512): self { $result = new self; $rsa = new Crypt_RSA; $rsa->loadKey(Arr::get($rsa->createKey($keysize),'privatekey')); $nkey = new OpenPGP\SecretKeyPacket(array( 'n' => $rsa->modulus->toBytes(), 'e' => $rsa->publicExponent->toBytes(), 'd' => $rsa->exponent->toBytes(), 'p' => $rsa->primes[2]->toBytes(), 'q' => $rsa->primes[1]->toBytes(), 'u' => $rsa->coefficients[2]->toBytes() )); $wkey = new OpenPGP\Crypt\RSA($nkey); $uid = new OpenPGP\UserIDPacket($name,$comment,$email); $result->key = $wkey->sign_key_userid([$nkey,$uid]); return $result; } /** * @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6.1 */ static function crc24($data): int { $crc = 0x00b704ce; for ($i = 0; $i < strlen($data); $i++) { $crc ^= (ord($data[$i]) & 255) << 16; for ($j = 0; $j < 8; $j++) { $crc <<= 1; if ($crc & 0x01000000) { $crc ^= 0x01864cfb; } } } return $crc & 0x00ffffff; } static function decode_s2k_count($c) { return ((int)16 + ($c & 15)) << (($c >> 4) + 6); } public function decrypt($data) { $decryptor = new OpenPGP\Crypt\RSA($this->key); return $decryptor->decrypt($data); } /** * @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6.2 * @see http://tools.ietf.org/html/rfc2045 */ static function enarmor1($data,$marker='MESSAGE',array $headers=[]) { $text = self::header($marker)."\n"; foreach ($headers as $key => $value) { $text .= $key.': '.(string)$value."\n"; } $text .= "\n".wordwrap(base64_encode($data),76,"\n",true); $text .= "\n".'='.base64_encode(substr(pack('N',self::crc24($data)),1))."\n"; $text .= self::footer($marker)."\n"; return $text; } protected function enarmor(string $data,$marker='MESSAGE',array $headers=[]): string { return static::enarmor1($data,$marker,$headers); } public function encrypt(OpenPGP\LiteralDataPacket $data) { return OpenPGP\Crypt\Symmetric::encrypt($this->key,new OpenPGP\Message([$data])); } static function encode_s2k_count($iterations) { if($iterations >= 65011712) return 255; $count = $iterations >> 6; $c = 0; while($count >= 32) { $count = $count >> 1; $c++; } $result = ($c << 4) | ($count - 16); if (OpenPGP::decode_s2k_count($result) < $iterations) { return $result + 1; } return $result; } /** * @see http://tools.ietf.org/html/rfc4880#section-6.2 */ static protected function footer($marker): string { return'-----END '.strtoupper((string)$marker).'-----'; } /** * @see http://tools.ietf.org/html/rfc4880#section-6.2 */ static protected function header($marker): string { return '-----BEGIN '.strtoupper((string)$marker).'-----'; } public function key() { return $this->key; } static function load(string $data,$marker='MESSAGE',bool $binary=TRUE): self { $result = new self; $result->key = OpenPGP\Message::parse( $binary ? $data : OpenPGP::unarmor($data,$marker) ); return $result; } public function private(): string { return $this->enarmor((string)$this->key,'PRIVATE KEY'); } protected function publicKey(): OpenPGP\PublicKeyPacket { return $this->key[0]; } public function public(): string { return $this->enarmor((string)$this->publicKey(),'PUBLIC KEY'); } public function signatures() { return $this->key->signatures(); } /** * @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc2045 */ static function unarmor($text,$header='PGP PUBLIC KEY BLOCK') { $header = self::header($header); $text = str_replace(["\r\n","\r"],["\n",''],$text); if (($pos1=strpos($text,$header)) !== FALSE && ($pos1=strpos($text,"\n\n",$pos1+=strlen($header))) !== FALSE && ($pos2=strpos($text,"\n=",$pos1+=2)) !== FALSE) { return base64_decode($text=substr($text,$pos1,$pos2-$pos1)); } } public function verify(OpenPGP\Message $data) { $verify = new OpenPGP\Crypt\RSA($this->key); return $verify->verify($data); } }