OpenPGP PHP
 All Classes Namespaces Functions Variables
openpgp_crypt_symmetric.php
1 <?php
2 
3 require_once dirname(__FILE__).'/openpgp.php';
4 @include_once dirname(__FILE__).'/openpgp_crypt_rsa.php';
5 @include_once dirname(__FILE__).'/openpgp_mcrypt_wrapper.php';
6 @include_once 'Crypt/AES.php';
7 @include_once 'Crypt/TripleDES.php';
8 require_once 'Crypt/Random.php'; // part of phpseclib is absolutely required
9 
11  public static function encrypt($passphrases_and_keys, $message, $symmetric_algorithm=9) {
12  list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
13  if(!$cipher) throw new Exception("Unsupported cipher");
14  $prefix = crypt_random_string($key_block_bytes);
15  $prefix .= substr($prefix, -2);
16 
17  $key = crypt_random_string($key_bytes);
18  $cipher->setKey($key);
19 
20  $to_encrypt = $prefix . $message->to_bytes();
21  $mdc = new OpenPGP_ModificationDetectionCodePacket(hash('sha1', $to_encrypt . "\xD3\x14", true));
22  $to_encrypt .= $mdc->to_bytes();
23  $encrypted = array(new OpenPGP_IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt)));
24 
25  if(!is_array($passphrases_and_keys) && !($passphrases_and_keys instanceof IteratorAggregate)) {
26  $passphrases_and_keys = (array)$passphrases_and_keys;
27  }
28 
29  foreach($passphrases_and_keys as $pass) {
30  if($pass instanceof OpenPGP_PublicKeyPacket) {
31  if(!in_array($pass->algorithm, array(1,2,3))) throw new Exception("Only RSA keys are supported.");
32  $crypt_rsa = new OpenPGP_Crypt_RSA($pass);
33  $rsa = $crypt_rsa->public_key();
34  $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
35  $esk = $rsa->encrypt(chr($symmetric_algorithm) . $key . pack('n', self::checksum($key)));
36  $esk = pack('n', OpenPGP::bitlength($esk)) . $esk;
37  array_unshift($encrypted, new OpenPGP_AsymmetricSessionKeyPacket($pass->algorithm, $pass->fingerprint(), $esk));
38  } else if(is_string($pass)) {
39  $s2k = new OpenPGP_S2K(crypt_random_string(10));
40  $cipher->setKey($s2k->make_key($pass, $key_bytes));
41  $esk = $cipher->encrypt(chr($symmetric_algorithm) . $key);
42  array_unshift($encrypted, new OpenPGP_SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm));
43  }
44  }
45 
46  return new OpenPGP_Message($encrypted);
47  }
48 
49  public static function decryptSymmetric($pass, $m) {
50  $epacket = self::getEncryptedData($m);
51 
52  foreach($m as $p) {
53  if($p instanceof OpenPGP_SymmetricSessionKeyPacket) {
54  if(strlen($p->encrypted_data) > 0) {
55  list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
56  if(!$cipher) continue;
57  $cipher->setKey($p->s2k->make_key($pass, $key_bytes));
58 
59  $padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes);
60  $data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data));
61  $decrypted = self::decryptPacket($epacket, ord($data{0}), substr($data, 1));
62  } else {
63  list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
64  $decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes));
65  }
66 
67  if($decrypted) return $decrypted;
68  }
69  }
70 
71  return NULL; /* If we get here, we failed */
72  }
73 
74  public static function decryptSecretKey($pass, $packet) {
75  $packet = clone $packet; // Do not mutate orinigal
76 
77  list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm);
78  if(!$cipher) throw new Exception("Unsupported cipher");
79  $cipher->setKey($packet->s2k->make_key($pass, $key_bytes));
80  $cipher->setIV(substr($packet->encrypted_data, 0, $key_block_bytes));
81  $material = $cipher->decrypt(substr($packet->encrypted_data, $key_block_bytes));
82 
83  if($packet->s2k_useage == 254) {
84  $chk = substr($material, -20);
85  $material = substr($material, 0, -20);
86  if($chk != hash('sha1', $material, true)) return NULL;
87  } else {
88  $chk = unpack('n', substr($material, -2));
89  $chk = reset($chk);
90  $material = substr($material, 0, -2);
91 
92  $mkChk = self::checksum($material);
93  if($chk != $mkChk) return NULL;
94  }
95 
96  $packet->s2k_useage = 0;
97  $packet->symmetric_algorithm = 0;
98  $packet->encrypted_data = NULL;
99  $packet->input = $material;
100  $packet->key_from_input();
101  unset($packet->input);
102  return $packet;
103  }
104 
105  public static function decryptPacket($epacket, $symmetric_algorithm, $key) {
106  list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
107  if(!$cipher) return NULL;
108  $cipher->setKey($key);
109 
110  if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) {
111  $padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes);
112  $data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data));
113  $prefix = substr($data, 0, $key_block_bytes + 2);
114  $mdc = substr(substr($data, -22, 22), 2);
115  $data = substr($data, $key_block_bytes + 2, -22);
116 
117  $mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true);
118  if($mkMDC !== $mdc) return false;
119 
120  try {
121  $msg = OpenPGP_Message::parse($data);
122  } catch (Exception $ex) { $msg = NULL; }
123  if($msg) return $msg; /* Otherwise keep trying */
124  } else {
125  // No MDC mean decrypt with resync
126  $iv = substr($epacket->data, 2, $key_block_bytes);
127  $edata = substr($epacket->data, $key_block_bytes + 2);
128  $padAmount = $key_block_bytes - (strlen($edata) % $key_block_bytes);
129 
130  $cipher->setIV($iv);
131  $data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata));
132 
133  try {
134  $msg = OpenPGP_Message::parse($data);
135  } catch (Exception $ex) { $msg = NULL; }
136  if($msg) return $msg; /* Otherwise keep trying */
137  }
138 
139  return NULL; /* Failed */
140  }
141 
142  public static function getCipher($algo) {
143  $cipher = NULL;
144  switch($algo) {
145  case 2:
146  if(class_exists('Crypt_TripleDES')) {
147  $cipher = new Crypt_TripleDES(CRYPT_DES_MODE_CFB);
148  $key_bytes = 24;
149  $key_block_bytes = 8;
150  }
151  break;
152  case 3:
153  if(defined('MCRYPT_CAST_128')) {
154  $cipher = new MCryptWrapper(MCRYPT_CAST_128);
155  }
156  break;
157  case 7:
158  if(class_exists('Crypt_AES')) {
159  $cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
160  $cipher->setKeyLength(128);
161  }
162  break;
163  case 8:
164  if(class_exists('Crypt_AES')) {
165  $cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
166  $cipher->setKeyLength(192);
167  }
168  break;
169  case 9:
170  if(class_exists('Crypt_AES')) {
171  $cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
172  $cipher->setKeyLength(256);
173  }
174  break;
175  }
176  if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher
177  if(!isset($key_bytes)) $key_bytes = $cipher->key_size;
178  if(!isset($key_block_bytes)) $key_block_bytes = $cipher->block_size;
179  return array($cipher, $key_bytes, $key_block_bytes);
180  }
181 
182  public static function getEncryptedData($m) {
183  foreach($m as $p) {
184  if($p instanceof OpenPGP_EncryptedDataPacket) return $p;
185  }
186  throw new Exception("Can only decrypt EncryptedDataPacket");
187  }
188 
189  public static function checksum($s) {
190  $mkChk = 0;
191  for($i = 0; $i < strlen($s); $i++) {
192  $mkChk = ($mkChk + ord($s{$i})) % 65536;
193  }
194  return $mkChk;
195  }
196 }