233 lines
5.1 KiB
PHP
233 lines
5.1 KiB
PHP
<?php
|
|
// This is free and unencumbered software released into the public domain.
|
|
/**
|
|
* OpenPGP.php is a pure-PHP implementation of the OpenPGP Message Format
|
|
* (RFC 4880).
|
|
*
|
|
* @package OpenPGP
|
|
* @author Arto Bendiken <arto.bendiken@gmail.com>
|
|
* @author Stephen Paul Weber <singpolyma@singpolyma.net>
|
|
* @author Deon George <deon@leenooks.net>
|
|
* @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);
|
|
}
|
|
} |