Asymmetric decryption

This commit is contained in:
Stephen Paul Weber 2013-01-26 14:00:00 -05:00
parent 641c07835b
commit cd15aec6f9
5 changed files with 152 additions and 37 deletions

View File

@ -568,7 +568,37 @@ class OpenPGP_Packet {
* @see http://tools.ietf.org/html/rfc4880#section-5.1 * @see http://tools.ietf.org/html/rfc4880#section-5.1
*/ */
class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet { class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet {
// TODO public $version, $keyid, $key_algorithm, $encrypted_data;
function read() {
switch($this->version = ord($this->read_byte())) {
case 3:
$rawkeyid = $this->read_bytes(8);
$this->keyid = '';
for($i = 0; $i < strlen($rawkeyid); $i++) { // Store KeyID in Hex
$this->keyid .= sprintf('%02X',ord($rawkeyid{$i}));
}
$this->key_algorithm = ord($this->read_byte());
$this->encrypted_data = $this->input;
break;
default:
throw new Exception("Unsupported AsymmetricSessionKeyPacket version: " . $this->version);
}
}
function body() {
$bytes = ord($this->version);
for($i = 0; $i < strlen($this->keyid); $i += 2) {
$bytes .= chr(hexdec($this->keyid{$i}.$this->keyid{$i+1}));
}
$bytes .= chr($this->key_algorithm);
$bytes .= $this->encrypted_data;
return $bytes;
}
} }
/** /**

View File

@ -6,55 +6,67 @@ require_once 'Crypt/TripleDES.php';
class OpenPGP_Crypt_AES_TripleDES { class OpenPGP_Crypt_AES_TripleDES {
public static function decryptSymmetric($pass, $m) { public static function decryptSymmetric($pass, $m) {
$epacket = self::getEncryptedData($m);
foreach($m as $p) { foreach($m as $p) {
if($p instanceof OpenPGP_SymmetricSessionKeyPacket) { if($p instanceof OpenPGP_SymmetricSessionKeyPacket) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
if(!$cipher) continue;
$cipher->setKey($p->s2k->make_key($pass, $key_bytes));
if(strlen($p->encrypted_data) > 0) { if(strlen($p->encrypted_data) > 0) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
if(!$cipher) continue;
$cipher->setKey($p->s2k->make_key($pass, $key_bytes));
$padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes); $padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes);
$data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data)); $data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data));
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher(ord($data{0})); $decrypted = self::decryptPacket($epacket, ord($data{0}), substr($data, 1));
if(!$cipher) continue;
$cipher->setKey(substr($data, 1));
}
$epacket = self::getEncryptedData($m);
$padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes);
if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) {
$data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data));
$prefix = substr($data, 0, $key_block_bytes + 2);
$mdc = substr(substr($data, -22, 22), 2);
$data = substr($data, $key_block_bytes + 2, -22);
$mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true);
if($mkMDC !== $mdc) return false;
try {
$msg = OpenPGP_Message::parse($data);
} catch (Exception $ex) { $msg = NULL; }
if($msg) return $msg; /* Otherwise keep trying */
} else { } else {
// No MDC mean decrypt with resync list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
$iv = substr($epacket->data, 2, $key_block_bytes); $decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes));
$edata = substr($epacket->data, $key_block_bytes + 2);
$cipher->setIV($iv);
$data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata));
try {
$msg = OpenPGP_Message::parse($data);
} catch (Exception $ex) { $msg = NULL; }
if($msg) return $msg; /* Otherwise keep trying */
} }
if($decrypted) return $decrypted;
} }
} }
return NULL; /* If we get here, we failed */ return NULL; /* If we get here, we failed */
} }
public static function decryptPacket($epacket, $symmetric_algorithm, $key) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
if(!$cipher) return NULL;
$cipher->setKey($key);
$padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes);
if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) {
$data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data));
$prefix = substr($data, 0, $key_block_bytes + 2);
$mdc = substr(substr($data, -22, 22), 2);
$data = substr($data, $key_block_bytes + 2, -22);
$mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true);
if($mkMDC !== $mdc) return false;
try {
$msg = OpenPGP_Message::parse($data);
} catch (Exception $ex) { $msg = NULL; }
if($msg) return $msg; /* Otherwise keep trying */
} else {
// No MDC mean decrypt with resync
$iv = substr($epacket->data, 2, $key_block_bytes);
$edata = substr($epacket->data, $key_block_bytes + 2);
$cipher->setIV($iv);
$data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata));
try {
$msg = OpenPGP_Message::parse($data);
} catch (Exception $ex) { $msg = NULL; }
if($msg) return $msg; /* Otherwise keep trying */
}
return NULL; /* Failed */
}
public static function getCipher($algo) { public static function getCipher($algo) {
switch($algo) { switch($algo) {
case 2: case 2:

View File

@ -12,6 +12,9 @@
// From http://phpseclib.sourceforge.net/ // From http://phpseclib.sourceforge.net/
require 'Crypt/RSA.php'; require 'Crypt/RSA.php';
require_once dirname(__FILE__).'/openpgp.php';
@include_once dirname(__FILE__).'/openpgp_cryph_aes_tripledes.php'; /* For encrypt/decrypt */
class OpenPGP_Crypt_RSA { class OpenPGP_Crypt_RSA {
protected $key, $message; protected $key, $message;
@ -157,6 +160,64 @@ class OpenPGP_Crypt_RSA {
return $packet; 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);
}
foreach($message as $p) {
if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) {
if($keys instanceof Crypt_RSA) {
$sk = self::try_decrypt_session($keys, $p->encyrpted_data);
} else if(strlen(str_replace('0', '', $p->keyid)) < 1) {
foreach($keys->key as $k) {
$sk = self::try_decrypt_session(self::convert_private_key($k), $p->encyrpted_data);
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_AES_TripleDES::decryptPacket(OpenPGP_Crypt_AES_TripleDES::getEncryptedData($message), $sk[0], $sk[1]);
if($r) return $r;
}
}
return NULL; /* Failed */
}
static function try_decrypt_session($key, $edata) {
$key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$data = $key->decrypt($edata);
$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') { static function crypt_rsa_key($mod, $exp, $hash='SHA256') {
$rsa = new Crypt_RSA(); $rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);

BIN
tests/data/hello.gpg Normal file

Binary file not shown.

View File

@ -91,4 +91,16 @@ class Decryption extends PHPUnit_Framework_TestCase {
public function testDecryptNoMDC() { public function testDecryptNoMDC() {
$this->oneSymmetric("hello", "PGP\n", "symmetric-no-mdc.gpg"); $this->oneSymmetric("hello", "PGP\n", "symmetric-no-mdc.gpg");
} }
public function testDecryptAsymmetric() {
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/hello.gpg'));
$key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$m2 = (new OpenPGP_Crypt_RSA($key))->decrypt($m);
while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data;
foreach($m2 as $p) {
if($p instanceof OpenPGP_LiteralDataPacket) {
$this->assertEquals($p->data, "hello\n");
}
}
}
} }