2 // This is free and unencumbered software released into the public domain.
4 * OpenPGP.php is a pure-PHP implementation of the OpenPGP Message Format
9 * @author Arto Bendiken <arto.bendiken@gmail.com>
10 * @author Stephen Paul Weber <singpolyma@singpolyma.net>
11 * @see http://github.com/bendiken/openpgp-php
14 //////////////////////////////////////////////////////////////////////////////
18 * @see http://tools.ietf.org/html/rfc4880
22 * @see http://tools.ietf.org/html/rfc4880#section-6
23 * @see http://tools.ietf.org/html/rfc4880#section-6.2
24 * @see http://tools.ietf.org/html/rfc2045
26 static function enarmor($data, $marker = 'MESSAGE', array $headers = array()) {
27 $text = self::header($marker) . "\n";
28 foreach ($headers as $key => $value) {
29 $text .= $key . ': ' . (string)$value . "\n";
31 $text .= "\n" . base64_encode($data);
32 $text .= "\n".'=' . base64_encode(substr(pack('N', self::crc24($data)), 1)) . "\n";
33 $text .= self::footer($marker) . "\n";
38 * @see http://tools.ietf.org/html/rfc4880#section-6
39 * @see http://tools.ietf.org/html/rfc2045
41 static function unarmor($text, $header = 'PGP PUBLIC KEY BLOCK') {
42 $header = self::header($header);
43 $text = str_replace(array("\r\n", "\r"), array("\n", ''), $text);
44 if (($pos1 = strpos($text, $header)) !== FALSE &&
45 ($pos1 = strpos($text, "\n\n", $pos1 += strlen($header))) !== FALSE &&
46 ($pos2 = strpos($text, "\n=", $pos1 += 2)) !== FALSE) {
47 return base64_decode($text = substr($text, $pos1, $pos2 - $pos1));
52 * @see http://tools.ietf.org/html/rfc4880#section-6.2
54 static function header($marker) {
55 return '-----BEGIN ' . strtoupper((string)$marker) . '-----';
59 * @see http://tools.ietf.org/html/rfc4880#section-6.2
61 static function footer($marker) {
62 return '-----END ' . strtoupper((string)$marker) . '-----';
66 * @see http://tools.ietf.org/html/rfc4880#section-6
67 * @see http://tools.ietf.org/html/rfc4880#section-6.1
69 static function crc24($data) {
71 for ($i = 0; $i < strlen($data); $i++) {
72 $crc ^= (ord($data[$i]) & 255) << 16;
73 for ($j = 0; $j < 8; $j++) {
75 if ($crc & 0x01000000) {
80 return $crc & 0x00ffffff;
84 * @see http://tools.ietf.org/html/rfc4880#section-12.2
86 static function bitlength($data) {
87 return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1;
90 static function decode_s2k_count($c) {
91 return ((int)16 + ($c & 15)) << (($c >> 4) + 6);
94 static function encode_s2k_count($iterations) {
95 if($iterations >= 65011712) return 255;
97 $count = $iterations >> 6;
100 $count = $count >> 1;
103 $result = ($c << 4) | ($count - 16);
105 if(OpenPGP::decode_s2k_count($result) < $iterations) {
114 public $type, $hash_algorithm, $salt, $count;
116 function __construct($salt='BADSALT', $hash_algorithm=10, $count=65536, $type=3) {
118 $this->hash_algorithm = $hash_algorithm;
120 $this->count = $count;
123 static function parse(&$input) {
124 $s2k = new OpenPGP_S2k();
125 switch($s2k->type = ord($input{0})) {
127 $s2k->hash_algorithm = ord($input{1});
128 $input = substr($input, 2);
131 $s2k->hash_algorithm = ord($input{1});
132 $s2k->salt = substr($input, 2, 8);
133 $input = substr($input, 10);
136 $s2k->hash_algorithm = ord($input{1});
137 $s2k->salt = substr($input, 2, 8);
138 $s2k->count = OpenPGP::decode_s2k_count(ord($input{10}));
139 $input = substr($input, 11);
146 function to_bytes() {
147 $bytes = chr($this->type);
148 switch($this->type) {
150 $bytes .= chr($this->hash_algorithm);
153 $bytes .= chr($this->hash_algorithm);
154 $bytes .= $this->salt;
157 $bytes .= chr($this->hash_algorithm);
158 $bytes .= $this->salt;
159 $bytes .= chr(OpenPGP::encode_s2k_count($this->count));
165 function raw_hash($s) {
166 return hash(strtolower(OpenPGP_SignaturePacket::$hash_algorithms[$this->hash_algorithm]), $s, true);
169 function sized_hash($s, $size) {
170 $hash = $this->raw_hash($s);
171 while(strlen($hash) < $size) {
173 $hash .= $this->raw_hash($s);
176 return substr($hash, 0, $size);
179 function iterate($s) {
180 if(strlen($s) >= $this->count) return $s;
181 $s = str_repeat($s, ceil($this->count / strlen($s)));
182 return substr($s, 0, $this->count);
185 function make_key($pass, $size) {
186 switch($this->type) {
188 return $this->sized_hash($pass, $size);
190 return $this->sized_hash($this->salt . $pass, $size);
192 return $this->sized_hash($this->iterate($this->salt . $pass), $size);
197 //////////////////////////////////////////////////////////////////////////////
201 * @see http://tools.ietf.org/html/rfc4880#section-4.1
202 * @see http://tools.ietf.org/html/rfc4880#section-11
203 * @see http://tools.ietf.org/html/rfc4880#section-11.3
205 class OpenPGP_Message implements IteratorAggregate, ArrayAccess {
207 public $packets = array();
209 static function parse_file($path) {
210 if (($msg = self::parse(file_get_contents($path)))) {
211 $msg->uri = preg_match('!^[\w\d]+://!', $path) ? $path : 'file://' . realpath($path);
217 * @see http://tools.ietf.org/html/rfc4880#section-4.1
218 * @see http://tools.ietf.org/html/rfc4880#section-4.2
220 static function parse($input) {
221 if (is_resource($input)) {
222 return self::parse_stream($input);
224 if (is_string($input)) {
225 return self::parse_string($input);
229 static function parse_stream($input) {
230 return self::parse_string(stream_get_contents($input));
233 static function parse_string($input) {
235 while (($length = strlen($input)) > 0) {
236 if (($packet = OpenPGP_Packet::parse($input))) {
239 if ($length == strlen($input)) { // is parsing stuck?
246 function __construct(array $packets = array()) {
247 $this->packets = $packets;
250 function to_bytes() {
252 foreach($this as $p) {
253 $bytes .= $p->to_bytes();
259 * Extract signed objects from a well-formatted message
261 * Recurses into CompressedDataPacket
263 * @see http://tools.ietf.org/html/rfc4880#section-11
265 function signatures() {
267 while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]->data;
273 $final_sigs = array();
275 foreach($msg as $idx => $p) {
276 if($p instanceof OpenPGP_LiteralDataPacket) {
277 return array(array($p, array_values(array_filter($msg->packets, function($p) {
278 return $p instanceof OpenPGP_SignaturePacket;
280 } else if($p instanceof OpenPGP_PublicSubkeyPacket || $p instanceof OpenPGP_SecretSubkeyPacket) {
282 array_push($final_sigs, array($key, $userid, $sigs));
285 array_push($final_sigs, array($key, $subkey, $sigs));
290 } else if($p instanceof OpenPGP_PublicKeyPacket) {
292 array_push($final_sigs, array($key, $userid, $sigs));
295 array_push($final_sigs, array($key, $subkey, $sigs));
298 array_push($final_sigs, array($key, $sigs));
303 } else if($p instanceof OpenPGP_UserIDPacket) {
305 array_push($final_sigs, array($key, $userid, $sigs));
308 array_push($final_sigs, array($key, $sigs));
312 } else if($p instanceof OpenPGP_SignaturePacket) {
318 array_push($final_sigs, array($key, $userid, $sigs));
320 array_push($final_sigs, array($key, $subkey, $sigs));
322 array_push($final_sigs, array($key, $sigs));
329 * Function to extract verified signatures
330 * $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet
332 function verified_signatures($verifiers) {
333 $signed = $this->signatures();
336 foreach($signed as $sign) {
337 $signatures = array_pop($sign);
340 foreach($signatures as $sig) {
341 $verifier = $verifiers[$sig->key_algorithm_name()][$sig->hash_algorithm_name()];
342 if($verifier && $this->verify_one($verifier, $sign, $sig)) {
346 array_push($sign, $vsigs);
353 function verify_one($verifier, $sign, $sig) {
354 if($sign[0] instanceof OpenPGP_LiteralDataPacket) {
355 $sign[0]->normalize();
356 $raw = $sign[0]->data;
357 } else if(isset($sign[1]) && $sign[1] instanceof OpenPGP_UserIDPacket) {
358 $raw = implode('', array_merge($sign[0]->fingerprint_material(), array(chr(0xB4),
359 pack('N', strlen($sign[1]->body())), $sign[1]->body())));
360 } else if(isset($sign[1]) && ($sign[1] instanceof OpenPGP_PublicSubkeyPacket || $sign[1] instanceof OpenPGP_SecretSubkeyPacket)) {
361 $raw = implode('', array_merge($sign[0]->fingerprint_material(), $sign[1]->fingerprint_material()));
362 } else if($sign[0] instanceof OpenPGP_PublicKeyPacket) {
363 $raw = implode('', $sign[0]->fingerprint_material());
367 return call_user_func($verifier, $raw.$sig->trailer, $sig);
370 // IteratorAggregate interface
372 function getIterator() {
373 return new ArrayIterator($this->packets);
376 // ArrayAccess interface
378 function offsetExists($offset) {
379 return isset($this->packets[$offset]);
382 function offsetGet($offset) {
383 return $this->packets[$offset];
386 function offsetSet($offset, $value) {
387 return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value;
390 function offsetUnset($offset) {
391 unset($this->packets[$offset]);
395 //////////////////////////////////////////////////////////////////////////////
401 * @see http://tools.ietf.org/html/rfc4880#section-4.1
402 * @see http://tools.ietf.org/html/rfc4880#section-4.3
404 class OpenPGP_Packet {
405 public $tag, $size, $data;
407 static function class_for($tag) {
408 return isset(self::$tags[$tag]) && class_exists(
409 $class = 'OpenPGP_' . self::$tags[$tag] . 'Packet') ? $class : __CLASS__;
413 * Parses an OpenPGP packet.
415 * Partial body lengths based on https://github.com/toofishes/python-pgpdump/blob/master/pgpdump/packet.py
417 * @see http://tools.ietf.org/html/rfc4880#section-4.2
419 static function parse(&$input) {
421 if (strlen($input) > 0) {
422 $parser = ord($input[0]) & 64 ? 'parse_new_format' : 'parse_old_format';
428 list($tag, $data_offset, $data_length, $partial) = self::$parser($input, $header_start0);
430 $data_start0 = $header_start0 + $data_offset;
431 $header_start0 = $data_start0 + $data_length - 1;
432 $packet_data .= substr($input, $data_start0, $data_length);
434 $consumed += $data_offset + $data_length;
438 } while ($partial === true && $parser === 'parse_new_format');
440 if ($tag && ($class = self::class_for($tag))) {
441 $packet = new $class();
443 $packet->input = $packet_data;
444 $packet->length = strlen($packet_data);
446 unset($packet->input);
447 unset($packet->length);
449 $input = substr($input, $consumed);
455 * Parses a new-format (RFC 4880) OpenPGP packet.
457 * @see http://tools.ietf.org/html/rfc4880#section-4.2.2
459 static function parse_new_format($input, $header_start = 0) {
460 $tag = ord($input[0]) & 63;
461 $len = ord($input[$header_start + 1]);
462 if($len < 192) { // One octet length
463 return array($tag, 2, $len, false);
465 if($len > 191 && $len < 224) { // Two octet length
466 return array($tag, 3, (($len - 192) << 8) + ord($input[$header_start + 2]) + 192, false);
468 if($len == 255) { // Five octet length
469 $unpacked = unpack('N', substr($input, $header_start + 2, 4));
470 return array($tag, 6, reset($unpacked), false);
472 // Partial body lengths
473 return array($tag, 2, 1 << ($len & 0x1f), true);
477 * Parses an old-format (PGP 2.6.x) OpenPGP packet.
479 * @see http://tools.ietf.org/html/rfc4880#section-4.2.1
481 static function parse_old_format($input) {
482 $len = ($tag = ord($input[0])) & 3;
483 $tag = ($tag >> 2) & 15;
485 case 0: // The packet has a one-octet length. The header is 2 octets long.
487 $data_length = ord($input[1]);
489 case 1: // The packet has a two-octet length. The header is 3 octets long.
491 $data_length = unpack('n', substr($input, 1, 2));
492 $data_length = $data_length[1];
494 case 2: // The packet has a four-octet length. The header is 5 octets long.
496 $data_length = unpack('N', substr($input, 1, 4));
497 $data_length = $data_length[1];
499 case 3: // The packet is of indeterminate length. The header is 1 octet long.
501 $data_length = strlen($input) - $head_length;
504 return array($tag, $head_length, $data_length, false);
507 function __construct($data=NULL) {
508 $this->tag = array_search(substr(substr(get_class($this), 8), 0, -6), self::$tags);
516 return $this->data; // Will normally be overridden by subclasses
519 function header_and_body() {
520 $body = $this->body(); // Get body first, we will need it's length
521 $tag = chr($this->tag | 0xC0); // First two bits are 1 for new packet format
522 $size = chr(255).pack('N', strlen($body)); // Use 5-octet lengths
523 return array('header' => $tag.$size, 'body' => $body);
526 function to_bytes() {
527 $data = $this->header_and_body();
528 return $data['header'].$data['body'];
532 * @see http://tools.ietf.org/html/rfc4880#section-3.5
534 function read_timestamp() {
535 return $this->read_unpacked(4, 'N');
539 * @see http://tools.ietf.org/html/rfc4880#section-3.2
541 function read_mpi() {
542 $length = $this->read_unpacked(2, 'n'); // length in bits
543 $length = (int)floor(($length + 7) / 8); // length in bytes
544 return $this->read_bytes($length);
548 * @see http://php.net/manual/en/function.unpack.php
550 function read_unpacked($count, $format) {
551 $unpacked = unpack($format, $this->read_bytes($count));
552 return reset($unpacked);
555 function read_byte() {
556 return ($bytes = $this->read_bytes()) ? $bytes[0] : NULL;
559 function read_bytes($count = 1) {
560 $bytes = substr($this->input, 0, $count);
561 $this->input = substr($this->input, $count);
565 static $tags = array(
566 1 => 'AsymmetricSessionKey', // Public-Key Encrypted Session Key
567 2 => 'Signature', // Signature Packet
568 3 => 'SymmetricSessionKey', // Symmetric-Key Encrypted Session Key Packet
569 4 => 'OnePassSignature', // One-Pass Signature Packet
570 5 => 'SecretKey', // Secret-Key Packet
571 6 => 'PublicKey', // Public-Key Packet
572 7 => 'SecretSubkey', // Secret-Subkey Packet
573 8 => 'CompressedData', // Compressed Data Packet
574 9 => 'EncryptedData', // Symmetrically Encrypted Data Packet
575 10 => 'Marker', // Marker Packet
576 11 => 'LiteralData', // Literal Data Packet
577 12 => 'Trust', // Trust Packet
578 13 => 'UserID', // User ID Packet
579 14 => 'PublicSubkey', // Public-Subkey Packet
580 17 => 'UserAttribute', // User Attribute Packet
581 18 => 'IntegrityProtectedData', // Sym. Encrypted and Integrity Protected Data Packet
582 19 => 'ModificationDetectionCode', // Modification Detection Code Packet
583 60 => 'Experimental', // Private or Experimental Values
584 61 => 'Experimental', // Private or Experimental Values
585 62 => 'Experimental', // Private or Experimental Values
586 63 => 'Experimental', // Private or Experimental Values
591 * OpenPGP Public-Key Encrypted Session Key packet (tag 1).
593 * @see http://tools.ietf.org/html/rfc4880#section-5.1
595 class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet {
596 public $version, $keyid, $key_algorithm, $encrypted_data;
598 function __construct($key_algorithm='', $keyid='', $encrypted_data='', $version=3) {
599 parent::__construct();
600 $this->version = $version;
601 $this->keyid = substr($keyid, -16);
602 $this->key_algorithm = $key_algorithm;
603 $this->encrypted_data = $encrypted_data;
607 switch($this->version = ord($this->read_byte())) {
609 $rawkeyid = $this->read_bytes(8);
611 for($i = 0; $i < strlen($rawkeyid); $i++) { // Store KeyID in Hex
612 $this->keyid .= sprintf('%02X',ord($rawkeyid{$i}));
615 $this->key_algorithm = ord($this->read_byte());
617 $this->encrypted_data = $this->input;
620 throw new Exception("Unsupported AsymmetricSessionKeyPacket version: " . $this->version);
625 $bytes = chr($this->version);
627 for($i = 0; $i < strlen($this->keyid); $i += 2) {
628 $bytes .= chr(hexdec($this->keyid{$i}.$this->keyid{$i+1}));
631 $bytes .= chr($this->key_algorithm);
632 $bytes .= $this->encrypted_data;
638 * OpenPGP Signature packet (tag 2).
639 * Be sure to NULL the trailer if you update a signature packet!
641 * @see http://tools.ietf.org/html/rfc4880#section-5.2
643 class OpenPGP_SignaturePacket extends OpenPGP_Packet {
644 public $version, $signature_type, $hash_algorithm, $key_algorithm, $hashed_subpackets, $unhashed_subpackets, $hash_head;
645 public $trailer; // This is the literal bytes that get tacked on the end of the message when verifying the signature
647 function __construct($data=NULL, $key_algorithm=NULL, $hash_algorithm=NULL) {
648 parent::__construct();
649 $this->version = 4; // Default to version 4 sigs
650 if(is_string($this->hash_algorithm = $hash_algorithm)) {
651 $this->hash_algorithm = array_search($this->hash_algorithm, self::$hash_algorithms);
653 if(is_string($this->key_algorithm = $key_algorithm)) {
654 $this->key_algorithm = array_search($this->key_algorithm, OpenPGP_PublicKeyPacket::$algorithms);
656 if($data) { // If we have any data, set up the creation time
657 $this->hashed_subpackets = array(new OpenPGP_SignaturePacket_SignatureCreationTimePacket(time()));
659 if($data instanceof OpenPGP_LiteralDataPacket) {
660 $this->signature_type = ($data->format == 'b') ? 0x00 : 0x01;
663 } else if($data instanceof OpenPGP_Message && $data[0] instanceof OpenPGP_PublicKeyPacket) {
664 // $data is a message with PublicKey first, UserID second
665 $key = implode('', $data[0]->fingerprint_material());
666 $user_id = $data[1]->body();
667 $data = $key . chr(0xB4) . pack('N', strlen($user_id)) . $user_id;
669 $this->data = $data; // Store to-be-signed data in here until the signing happens
673 * $this->data must be set to the data to sign (done by constructor)
674 * $signers in the same format as $verifiers for OpenPGP_Message.
676 function sign_data($signers) {
677 $this->trailer = $this->calculate_trailer();
678 $signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()];
679 $this->data = call_user_func($signer, $this->data.$this->trailer);
680 $unpacked = unpack('n', substr(implode('',$this->data), 0, 2));
681 $this->hash_head = reset($unpacked);
685 switch($this->version = ord($this->read_byte())) {
688 assert(ord($this->read_byte()) == 5);
689 $this->signature_type = ord($this->read_byte());
690 $creation_time = $this->read_timestamp();
691 $keyid = $this->read_bytes(8);
693 for($i = 0; $i < strlen($keyid); $i++) { // Store KeyID in Hex
694 $keyidHex .= sprintf('%02X',ord($keyid{$i}));
697 $this->hashed_subpackets = array();
698 $this->unhashed_subpackets = array(
699 new OpenPGP_SignaturePacket_SignatureCreationTimePacket($creation_time),
700 new OpenPGP_SignaturePacket_IssuerPacket($keyidHex)
703 $this->key_algorithm = ord($this->read_byte());
704 $this->hash_algorithm = ord($this->read_byte());
705 $this->hash_head = $this->read_unpacked(2, 'n');
706 $this->data = array();
707 while(strlen($this->input) > 0) {
708 $this->data[] = $this->read_mpi();
712 $this->signature_type = ord($this->read_byte());
713 $this->key_algorithm = ord($this->read_byte());
714 $this->hash_algorithm = ord($this->read_byte());
715 $this->trailer = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm);
717 $hashed_size = $this->read_unpacked(2, 'n');
718 $hashed_subpackets = $this->read_bytes($hashed_size);
719 $this->trailer .= pack('n', $hashed_size).$hashed_subpackets;
720 $this->hashed_subpackets = self::get_subpackets($hashed_subpackets);
722 $this->trailer .= chr(4).chr(0xff).pack('N', 6 + $hashed_size);
724 $unhashed_size = $this->read_unpacked(2, 'n');
725 $this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size));
727 $this->hash_head = $this->read_unpacked(2, 'n');
729 $this->data = array();
730 while(strlen($this->input) > 0) {
731 $this->data[] = $this->read_mpi();
737 function calculate_trailer() {
738 // The trailer is just the top of the body plus some crap
739 $body = $this->body_start();
740 return $body.chr(4).chr(0xff).pack('N', strlen($body));
743 function body_start() {
744 $body = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm);
746 $hashed_subpackets = '';
747 foreach((array)$this->hashed_subpackets as $p) {
748 $hashed_subpackets .= $p->to_bytes();
750 $body .= pack('n', strlen($hashed_subpackets)).$hashed_subpackets;
756 switch($this->version) {
759 $body = chr($this->version) . chr(5) . chr($this->signature_type);
761 foreach((array)$this->unhashed_subpackets as $p) {
762 if($p instanceof OpenPGP_SignaturePacket_SignatureCreationTimePacket) {
763 $body .= pack('N', $p->data);
768 foreach((array)$this->unhashed_subpackets as $p) {
769 if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) {
770 for($i = 0; $i < strlen($p->data); $i += 2) {
771 $body .= chr(hexdec($p->data{$i}.$p->data{$i+1}));
777 $body .= chr($this->key_algorithm);
778 $body .= chr($this->hash_algorithm);
779 $body .= pack('n', $this->hash_head);
781 foreach($this->data as $mpi) {
782 $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi;
787 if(!$this->trailer) $this->trailer = $this->calculate_trailer();
788 $body = substr($this->trailer, 0, -6);
790 $unhashed_subpackets = '';
791 foreach((array)$this->unhashed_subpackets as $p) {
792 $unhashed_subpackets .= $p->to_bytes();
794 $body .= pack('n', strlen($unhashed_subpackets)).$unhashed_subpackets;
796 $body .= pack('n', $this->hash_head);
798 foreach((array)$this->data as $mpi) {
799 $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi;
806 function key_algorithm_name() {
807 return OpenPGP_PublicKeyPacket::$algorithms[$this->key_algorithm];
810 function hash_algorithm_name() {
811 return self::$hash_algorithms[$this->hash_algorithm];
815 foreach($this->hashed_subpackets as $p) {
816 if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data;
818 foreach($this->unhashed_subpackets as $p) {
819 if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data;
825 * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.1
827 static function get_subpackets($input) {
828 $subpackets = array();
829 while(($length = strlen($input)) > 0) {
830 $subpackets[] = self::get_subpacket($input);
831 if($length == strlen($input)) { // Parsing stuck?
838 static function get_subpacket(&$input) {
839 $len = ord($input[0]);
840 $length_of_length = 1;
841 // if($len < 192) One octet length, no furthur processing
842 if($len > 190 && $len < 255) { // Two octet length
843 $length_of_length = 2;
844 $len = (($len - 192) << 8) + ord($input[1]) + 192;
846 if($len == 255) { // Five octet length
847 $length_of_length = 5;
848 $unpacked = unpack('N', substr($input, 1, 4));
849 $len = reset($unpacked);
851 $input = substr($input, $length_of_length); // Chop off length header
852 $tag = ord($input[0]);
853 $class = self::class_for($tag);
855 $packet = new $class();
857 $packet->input = substr($input, 1, $len-1);
858 $packet->length = $len-1;
860 unset($packet->input);
861 unset($packet->length);
863 $input = substr($input, $len); // Chop off the data from this packet
867 static $hash_algorithms = array(
877 static $subpacket_types = array(
880 2 => 'SignatureCreationTime',
881 3 => 'SignatureExpirationTime',
882 4 => 'ExportableCertification',
883 5 => 'TrustSignature',
884 6 => 'RegularExpression',
887 9 => 'KeyExpirationTime',
888 //10 => 'Placeholder for backward compatibility',
889 11 => 'PreferredSymmetricAlgorithms',
890 12 => 'RevocationKey',
898 20 => 'NotationData',
899 21 => 'PreferredHashAlgorithms',
900 22 => 'PreferredCompressionAlgorithms',
901 23 => 'KeyServerPreferences',
902 24 => 'PreferredKeyServer',
903 25 => 'PrimaryUserID',
906 28 => 'SignersUserID',
907 29 => 'ReasonforRevocation',
909 31 => 'SignatureTarget',
910 32 => 'EmbeddedSignature',
913 static function class_for($tag) {
914 if(!isset(self::$subpacket_types[$tag])) return 'OpenPGP_SignaturePacket_Subpacket';
915 return 'OpenPGP_SignaturePacket_'.self::$subpacket_types[$tag].'Packet';
920 class OpenPGP_SignaturePacket_Subpacket extends OpenPGP_Packet {
921 function __construct($data=NULL) {
922 parent::__construct($data);
923 $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types);
926 function header_and_body() {
927 $body = $this->body(); // Get body first, we will need it's length
928 $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet
929 $tag = chr($this->tag);
930 return array('header' => $size.$tag, 'body' => $body);
933 /* Defaults for unsupported packets */
935 $this->data = $this->input;
944 * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.4
946 class OpenPGP_SignaturePacket_SignatureCreationTimePacket extends OpenPGP_SignaturePacket_Subpacket {
948 $this->data = $this->read_timestamp();
952 return pack('N', $this->data);
956 class OpenPGP_SignaturePacket_SignatureExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket {
958 $this->data = $this->read_timestamp();
962 return pack('N', $this->data);
966 class OpenPGP_SignaturePacket_ExportableCertificationPacket extends OpenPGP_SignaturePacket_Subpacket {
968 $this->data = (ord($this->input) != 0);
972 return chr($this->data ? 1 : 0);
976 class OpenPGP_SignaturePacket_TrustSignaturePacket extends OpenPGP_SignaturePacket_Subpacket {
978 $this->depth = ord($this->input{0});
979 $this->trust = ord($this->input{1});
983 return chr($this->depth) . chr($this->trust);
987 class OpenPGP_SignaturePacket_RegularExpressionPacket extends OpenPGP_SignaturePacket_Subpacket {
989 $this->data = substr($this->input, 0, -1);
993 return $this->data . chr(0);
997 class OpenPGP_SignaturePacket_RevocablePacket extends OpenPGP_SignaturePacket_Subpacket {
999 $this->data = (ord($this->input) != 0);
1003 return chr($this->data ? 1 : 0);
1007 class OpenPGP_SignaturePacket_KeyExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket {
1009 $this->data = $this->read_timestamp();
1013 return pack('N', $this->data);
1017 class OpenPGP_SignaturePacket_PreferredSymmetricAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket {
1019 $this->data = array();
1020 while(strlen($this->input) > 0) {
1021 $this->data[] = ord($this->read_byte());
1027 foreach($this->data as $algo) {
1028 $bytes .= chr($algo);
1034 class OpenPGP_SignaturePacket_RevocationKeyPacket extends OpenPGP_SignaturePacket_Subpacket {
1035 public $key_algorithm, $fingerprint, $sensitive;
1038 // bitfield must have bit 0x80 set, says the spec
1039 $bitfield = ord($this->read_byte());
1040 $this->sensitive = $bitfield & 0x40 == 0x40;
1041 $this->key_algorithm = ord($this->read_byte());
1043 $this->fingerprint = '';
1044 while(strlen($this->input) > 0) {
1045 $this->fingerprint .= sprintf('%02X',ord($this->read_byte()));
1051 $bytes .= chr(0x80 | ($this->sensitive ? 0x40 : 0x00));
1052 $bytes .= chr($this->key_algorithm);
1054 for($i = 0; $i < strlen($this->fingerprint); $i += 2) {
1055 $bytes .= chr(hexdec($this->fingerprint{$i}.$this->fingerprint{$i+1}));
1063 * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.5
1065 class OpenPGP_SignaturePacket_IssuerPacket extends OpenPGP_SignaturePacket_Subpacket {
1067 for($i = 0; $i < 8; $i++) { // Store KeyID in Hex
1068 $this->data .= sprintf('%02X',ord($this->read_byte()));
1074 for($i = 0; $i < strlen($this->data); $i += 2) {
1075 $bytes .= chr(hexdec($this->data{$i}.$this->data{$i+1}));
1081 class OpenPGP_SignaturePacket_NotationDataPacket extends OpenPGP_SignaturePacket_Subpacket {
1082 public $human_readable, $name;
1085 $flags = $this->read_bytes(4);
1086 $namelen = $this->read_unpacked(2, 'n');
1087 $datalen = $this->read_unpacked(2, 'n');
1088 $this->human_readable = ord($flags[0]) & 0x80 == 0x80;
1089 $this->name = $this->read_bytes($namelen);
1090 $this->data = $this->read_bytes($datalen);
1094 return chr($this->human_readable ? 0x80 : 0x00) . "\0\0\0" .
1095 pack('n', strlen($this->name)) . pack('n', strlen($this->data)) .
1096 $this->name . $this->data;
1100 class OpenPGP_SignaturePacket_PreferredHashAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket {
1102 $this->data = array();
1103 while(strlen($this->input) > 0) {
1104 $this->data[] = ord($this->read_byte());
1110 foreach($this->data as $algo) {
1111 $bytes .= chr($algo);
1117 class OpenPGP_SignaturePacket_PreferredCompressionAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket {
1119 $this->data = array();
1120 while(strlen($this->input) > 0) {
1121 $this->data[] = ord($this->read_byte());
1127 foreach($this->data as $algo) {
1128 $bytes .= chr($algo);
1134 class OpenPGP_SignaturePacket_KeyServerPreferencesPacket extends OpenPGP_SignaturePacket_Subpacket {
1138 $flags = ord($this->input);
1139 $this->no_modify = $flags & 0x80 == 0x80;
1143 return chr($this->no_modify ? 0x80 : 0x00);
1147 class OpenPGP_SignaturePacket_PreferredKeyServerPacket extends OpenPGP_SignaturePacket_Subpacket {
1149 $this->data = $this->input;
1157 class OpenPGP_SignaturePacket_PrimaryUserIDPacket extends OpenPGP_SignaturePacket_Subpacket {
1159 $this->data = (ord($this->input) != 0);
1163 return chr($this->data ? 1 : 0);
1168 class OpenPGP_SignaturePacket_PolicyURIPacket extends OpenPGP_SignaturePacket_Subpacket {
1170 $this->data = $this->input;
1178 class OpenPGP_SignaturePacket_KeyFlagsPacket extends OpenPGP_SignaturePacket_Subpacket {
1179 function __construct($flags=array()) {
1180 parent::__construct();
1181 $this->flags = $flags;
1185 $this->flags = array();
1186 while($this->input) {
1187 $this->flags[] = ord($this->read_byte());
1193 foreach($this->flags as $f) {
1200 class OpenPGP_SignaturePacket_SignersUserIDPacket extends OpenPGP_SignaturePacket_Subpacket {
1202 $this->data = $this->input;
1210 class OpenPGP_SignaturePacket_ReasonforRevocationPacket extends OpenPGP_SignaturePacket_Subpacket {
1214 $this->code = ord($this->read_byte());
1215 $this->data = $this->input;
1219 return chr($this->code) . $this->data;
1224 class OpenPGP_SignaturePacket_FeaturesPacket extends OpenPGP_SignaturePacket_KeyFlagsPacket {
1225 // Identical functionality to parent
1228 class OpenPGP_SignaturePacket_SignatureTargetPacket extends OpenPGP_SignaturePacket_Subpacket {
1229 public $key_algorithm, $hash_algorithm;
1232 $this->key_algorithm = ord($this->read_byte());
1233 $this->hash_algorithm = ord($this->read_byte());
1234 $this->data = $this->input;
1238 return chr($this->key_algorithm) . chr($this->hash_algorithm) . $this->data;
1243 class OpenPGP_SignaturePacket_EmbeddedSignaturePacket extends OpenPGP_SignaturePacket {
1244 // TODO: This is duplicated from subpacket... improve?
1245 function __construct($data=NULL) {
1246 parent::__construct($data);
1247 $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types);
1250 function header_and_body() {
1251 $body = $this->body(); // Get body first, we will need it's length
1252 $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet
1253 $tag = chr($this->tag);
1254 return array('header' => $size.$tag, 'body' => $body);
1259 * OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3).
1261 * @see http://tools.ietf.org/html/rfc4880#section-5.3
1263 class OpenPGP_SymmetricSessionKeyPacket extends OpenPGP_Packet {
1264 public $version, $symmetric_algorithm, $s2k, $encrypted_data;
1266 function __construct($s2k=NULL, $encrypted_data='', $symmetric_algorithm=9, $version=3) {
1267 parent::__construct();
1268 $this->version = $version;
1269 $this->symmetric_algorithm = $symmetric_algorithm;
1271 $this->encrypted_data = $encrypted_data;
1275 $this->version = ord($this->read_byte());
1276 $this->symmetric_algorithm = ord($this->read_byte());
1277 $this->s2k = OpenPGP_S2k::parse($this->input);
1278 $this->encrypted_data = $this->input;
1282 return chr($this->version) . chr($this->symmetric_algorithm) .
1283 $this->s2k->to_bytes() . $this->encrypted_data;
1288 * OpenPGP One-Pass Signature packet (tag 4).
1290 * @see http://tools.ietf.org/html/rfc4880#section-5.4
1292 class OpenPGP_OnePassSignaturePacket extends OpenPGP_Packet {
1293 public $version, $signature_type, $hash_algorithm, $key_algorithm, $key_id, $nested;
1295 $this->version = ord($this->read_byte());
1296 $this->signature_type = ord($this->read_byte());
1297 $this->hash_algorithm = ord($this->read_byte());
1298 $this->key_algorithm = ord($this->read_byte());
1299 for($i = 0; $i < 8; $i++) { // Store KeyID in Hex
1300 $this->key_id .= sprintf('%02X',ord($this->read_byte()));
1302 $this->nested = ord($this->read_byte());
1306 $body = chr($this->version).chr($this->signature_type).chr($this->hash_algorithm).chr($this->key_algorithm);
1307 for($i = 0; $i < strlen($this->key_id); $i += 2) {
1308 $body .= chr(hexdec($this->key_id{$i}.$this->key_id{$i+1}));
1310 $body .= chr((int)$this->nested);
1316 * OpenPGP Public-Key packet (tag 6).
1318 * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1
1319 * @see http://tools.ietf.org/html/rfc4880#section-5.5.2
1320 * @see http://tools.ietf.org/html/rfc4880#section-11.1
1321 * @see http://tools.ietf.org/html/rfc4880#section-12
1323 class OpenPGP_PublicKeyPacket extends OpenPGP_Packet {
1324 public $version, $timestamp, $algorithm;
1325 public $key, $key_id, $fingerprint;
1326 public $v3_days_of_validity;
1328 function __construct($key=array(), $algorithm='RSA', $timestamp=NULL, $version=4) {
1329 parent::__construct();
1331 if(is_string($this->algorithm = $algorithm)) {
1332 $this->algorithm = array_search($this->algorithm, self::$algorithms);
1334 $this->timestamp = $timestamp ? $timestamp : time();
1335 $this->version = $version;
1337 if(count($this->key) > 0) {
1338 $this->key_id = substr($this->fingerprint(), -8);
1342 // Find self signatures in a message, these often contain metadata about the key
1343 function self_signatures($message) {
1345 $keyid16 = strtoupper(substr($this->fingerprint, -16));
1346 foreach($message as $p) {
1347 if($p instanceof OpenPGP_SignaturePacket) {
1348 if(strtoupper($p->issuer()) == $keyid16) {
1351 foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
1352 if($s instanceof OpenPGP_SignaturePacket_EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) {
1358 } else if(count($sigs)) break; // After we've seen a self sig, the next non-sig stop all self-sigs
1363 // Find expiry time of this key based on the self signatures in a message
1364 function expires($message) {
1365 foreach($this->self_signatures($message) as $p) {
1366 foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
1367 if($s instanceof OpenPGP_SignaturePacket_KeyExpirationTimePacket) {
1368 return $this->timestamp + $s->data;
1372 return NULL; // Never expires
1376 * @see http://tools.ietf.org/html/rfc4880#section-5.5.2
1379 switch ($this->version = ord($this->read_byte())) {
1381 $this->timestamp = $this->read_timestamp();
1382 $this->v3_days_of_validity = $this->read_unpacked(2, 'n');
1383 $this->algorithm = ord($this->read_byte());
1384 $this->read_key_material();
1387 $this->timestamp = $this->read_timestamp();
1388 $this->algorithm = ord($this->read_byte());
1389 $this->read_key_material();
1394 * @see http://tools.ietf.org/html/rfc4880#section-5.5.2
1396 function read_key_material() {
1397 foreach (self::$key_fields[$this->algorithm] as $field) {
1398 $this->key[$field] = $this->read_mpi();
1400 $this->key_id = substr($this->fingerprint(), -8);
1403 function fingerprint_material() {
1404 switch ($this->version) {
1406 $material = array();
1407 foreach (self::$key_fields[$this->algorithm] as $i) {
1408 $material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
1409 $material[] = $this->key[$i];
1415 chr($this->version), pack('N', $this->timestamp),
1416 chr($this->algorithm),
1418 $material = array();
1419 foreach (self::$key_fields[$this->algorithm] as $i) {
1420 $material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
1421 $material[] = $this->key[$i];
1423 $material = implode('', $material);
1424 $head[1] = pack('n', 6 + strlen($material));
1425 $head[] = $material;
1431 * @see http://tools.ietf.org/html/rfc4880#section-12.2
1432 * @see http://tools.ietf.org/html/rfc4880#section-3.3
1434 function fingerprint() {
1435 switch ($this->version) {
1438 return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material())));
1440 return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material())));
1445 switch ($this->version) {
1448 return implode('', array_merge(array(
1449 chr($this->version) . pack('N', $this->timestamp) .
1450 pack('n', $this->v3_days_of_validity) . chr($this->algorithm)
1451 ), $this->fingerprint_material())
1454 return implode('', array_slice($this->fingerprint_material(), 2));
1458 static $key_fields = array(
1459 1 => array('n', 'e'), // RSA
1460 16 => array('p', 'g', 'y'), // ELG-E
1461 17 => array('p', 'q', 'g', 'y'), // DSA
1464 static $algorithms = array(
1478 * OpenPGP Public-Subkey packet (tag 14).
1480 * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2
1481 * @see http://tools.ietf.org/html/rfc4880#section-5.5.2
1482 * @see http://tools.ietf.org/html/rfc4880#section-11.1
1483 * @see http://tools.ietf.org/html/rfc4880#section-12
1485 class OpenPGP_PublicSubkeyPacket extends OpenPGP_PublicKeyPacket {
1490 * OpenPGP Secret-Key packet (tag 5).
1492 * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3
1493 * @see http://tools.ietf.org/html/rfc4880#section-5.5.3
1494 * @see http://tools.ietf.org/html/rfc4880#section-11.2
1495 * @see http://tools.ietf.org/html/rfc4880#section-12
1497 class OpenPGP_SecretKeyPacket extends OpenPGP_PublicKeyPacket {
1498 public $s2k_useage, $s2k, $symmetric_algorithm, $private_hash, $encrypted_data;
1500 parent::read(); // All the fields from PublicKey
1501 $this->s2k_useage = ord($this->read_byte());
1502 if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
1503 $this->symmetric_algorithm = ord($this->read_byte());
1504 $this->s2k = OpenPGP_S2k::parse($this->input);
1505 } else if($this->s2k_useage > 0) {
1506 $this->symmetric_algorithm = $this->s2k_useage;
1508 if($this->s2k_useage > 0) {
1509 $this->encrypted_data = $this->input; // Rest of input is MPIs and checksum (encrypted)
1511 $this->key_from_input();
1512 $this->private_hash = $this->read_bytes(2); // TODO: Validate checksum?
1516 static $secret_key_fields = array(
1517 1 => array('d', 'p', 'q', 'u'), // RSA
1518 2 => array('d', 'p', 'q', 'u'), // RSA-E
1519 3 => array('d', 'p', 'q', 'u'), // RSA-S
1520 16 => array('x'), // ELG-E
1521 17 => array('x'), // DSA
1524 function key_from_input() {
1525 foreach(self::$secret_key_fields[$this->algorithm] as $field) {
1526 $this->key[$field] = $this->read_mpi();
1531 $bytes = parent::body() . chr($this->s2k_useage);
1532 $secret_material = NULL;
1533 if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
1534 $bytes .= chr($this->symmetric_algorithm);
1535 $bytes .= $this->s2k->to_bytes();
1537 if($this->s2k_useage > 0) {
1538 $bytes .= $this->encrypted_data;
1540 $secret_material = '';
1541 foreach(self::$secret_key_fields[$this->algorithm] as $f) {
1542 $f = $this->key[$f];
1543 $secret_material .= pack('n', OpenPGP::bitlength($f));
1544 $secret_material .= $f;
1546 $bytes .= $secret_material;
1550 for($i = 0; $i < strlen($secret_material); $i++) {
1551 $chk = ($chk + ord($secret_material[$i])) % 65536;
1553 $bytes .= pack('n', $chk);
1560 * OpenPGP Secret-Subkey packet (tag 7).
1562 * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4
1563 * @see http://tools.ietf.org/html/rfc4880#section-5.5.3
1564 * @see http://tools.ietf.org/html/rfc4880#section-11.2
1565 * @see http://tools.ietf.org/html/rfc4880#section-12
1567 class OpenPGP_SecretSubkeyPacket extends OpenPGP_SecretKeyPacket {
1572 * OpenPGP Compressed Data packet (tag 8).
1574 * @see http://tools.ietf.org/html/rfc4880#section-5.6
1576 class OpenPGP_CompressedDataPacket extends OpenPGP_Packet implements IteratorAggregate, ArrayAccess {
1578 /* see http://tools.ietf.org/html/rfc4880#section-9.3 */
1579 static $algorithms = array(0 => 'Uncompressed', 1 => 'ZIP', 2 => 'ZLIB', 3 => 'BZip2');
1581 $this->algorithm = ord($this->read_byte());
1582 $this->data = $this->read_bytes($this->length);
1583 switch($this->algorithm) {
1585 $this->data = OpenPGP_Message::parse($this->data);
1588 $this->data = OpenPGP_Message::parse(gzinflate($this->data));
1591 $this->data = OpenPGP_Message::parse(gzuncompress($this->data));
1594 $this->data = OpenPGP_Message::parse(bzdecompress($this->data));
1602 $body = chr($this->algorithm);
1603 switch($this->algorithm) {
1605 $body .= $this->data->to_bytes();
1608 $body .= gzdeflate($this->data->to_bytes());
1611 $body .= gzcompress($this->data->to_bytes());
1614 $body .= bzcompress($this->data->to_bytes());
1622 // IteratorAggregate interface
1624 function getIterator() {
1625 return new ArrayIterator($this->data->packets);
1628 // ArrayAccess interface
1630 function offsetExists($offset) {
1631 return isset($this->data[$offset]);
1634 function offsetGet($offset) {
1635 return $this->data[$offset];
1638 function offsetSet($offset, $value) {
1639 return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value;
1642 function offsetUnset($offset) {
1643 unset($this->data[$offset]);
1649 * OpenPGP Symmetrically Encrypted Data packet (tag 9).
1651 * @see http://tools.ietf.org/html/rfc4880#section-5.7
1653 class OpenPGP_EncryptedDataPacket extends OpenPGP_Packet {
1655 $this->data = $this->input;
1664 * OpenPGP Marker packet (tag 10).
1666 * @see http://tools.ietf.org/html/rfc4880#section-5.8
1668 class OpenPGP_MarkerPacket extends OpenPGP_Packet {
1673 * OpenPGP Literal Data packet (tag 11).
1675 * @see http://tools.ietf.org/html/rfc4880#section-5.9
1677 class OpenPGP_LiteralDataPacket extends OpenPGP_Packet {
1678 public $format, $filename, $timestamp;
1680 function __construct($data=NULL, $opt=array()) {
1681 parent::__construct();
1682 $this->data = $data;
1683 $this->format = isset($opt['format']) ? $opt['format'] : 'b';
1684 $this->filename = isset($opt['filename']) ? $opt['filename'] : 'data';
1685 $this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time();
1688 function normalize() {
1689 if($this->format == 'u' || $this->format == 't') { // Normalize line endings
1690 $this->data = str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $this->data)));
1695 $this->size = $this->length - 1 - 4;
1696 $this->format = $this->read_byte();
1697 $filename_length = ord($this->read_byte());
1698 $this->size -= $filename_length;
1699 $this->filename = $this->read_bytes($filename_length);
1700 $this->timestamp = $this->read_timestamp();
1701 $this->data = $this->read_bytes($this->size);
1705 return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data;
1710 * OpenPGP Trust packet (tag 12).
1712 * @see http://tools.ietf.org/html/rfc4880#section-5.10
1714 class OpenPGP_TrustPacket extends OpenPGP_Packet {
1716 $this->data = $this->input;
1725 * OpenPGP User ID packet (tag 13).
1727 * @see http://tools.ietf.org/html/rfc4880#section-5.11
1728 * @see http://tools.ietf.org/html/rfc2822
1730 class OpenPGP_UserIDPacket extends OpenPGP_Packet {
1731 public $name, $comment, $email;
1733 function __construct($name='', $comment='', $email='') {
1734 parent::__construct();
1735 if(!$comment && !$email) {
1736 $this->input = $name;
1739 $this->name = $name;
1740 $this->comment = $comment;
1741 $this->email = $email;
1746 $this->data = $this->input;
1747 // User IDs of the form: "name (comment) <email>"
1748 if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) {
1749 $this->name = trim($matches[1]);
1750 $this->comment = trim($matches[2]);
1751 $this->email = trim($matches[3]);
1753 // User IDs of the form: "name <email>"
1754 else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) {
1755 $this->name = trim($matches[1]);
1756 $this->comment = NULL;
1757 $this->email = trim($matches[2]);
1759 // User IDs of the form: "name"
1760 else if (preg_match('/^([^<]+)$/', $this->data, $matches)) {
1761 $this->name = trim($matches[1]);
1762 $this->comment = NULL;
1763 $this->email = NULL;
1765 // User IDs of the form: "<email>"
1766 else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) {
1768 $this->comment = NULL;
1769 $this->email = trim($matches[2]);
1773 function __toString() {
1775 if ($this->name) { $text[] = $this->name; }
1776 if ($this->comment) { $text[] = "({$this->comment})"; }
1777 if ($this->email) { $text[] = "<{$this->email}>"; }
1778 return implode(' ', $text);
1782 return ''.$this; // Convert to string is the body
1787 * OpenPGP User Attribute packet (tag 17).
1789 * @see http://tools.ietf.org/html/rfc4880#section-5.12
1790 * @see http://tools.ietf.org/html/rfc4880#section-11.1
1792 class OpenPGP_UserAttributePacket extends OpenPGP_Packet {
1799 * OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18).
1801 * @see http://tools.ietf.org/html/rfc4880#section-5.13
1803 class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_EncryptedDataPacket {
1806 function __construct($data='', $version=1) {
1807 parent::__construct();
1808 $this->version = $version;
1809 $this->data = $data;
1813 $this->version = ord($this->read_byte());
1814 $this->data = $this->input;
1818 return chr($this->version) . $this->data;
1823 * OpenPGP Modification Detection Code packet (tag 19).
1825 * @see http://tools.ietf.org/html/rfc4880#section-5.14
1827 class OpenPGP_ModificationDetectionCodePacket extends OpenPGP_Packet {
1828 function __construct($sha1='') {
1829 parent::__construct();
1830 $this->data = $sha1;
1834 $this->data = $this->input;
1835 if(strlen($this->input) != 20) throw new Exception("Bad ModificationDetectionCodePacket");
1838 function header_and_body() {
1839 $body = $this->body(); // Get body first, we will need it's length
1840 if(strlen($body) != 20) throw new Exception("Bad ModificationDetectionCodePacket");
1841 return array('header' => "\xD3\x14", 'body' => $body);
1850 * OpenPGP Private or Experimental packet (tags 60..63).
1852 * @see http://tools.ietf.org/html/rfc4880#section-4.3
1854 class OpenPGP_ExperimentalPacket extends OpenPGP_Packet {}