From 6eb5ea3ef7bf82fd4af004e89687c872b1fed4a4 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Tue, 20 Oct 2015 22:11:41 -0500 Subject: [PATCH] RSA: add support for the microsoft BLOB key format --- phpseclib/Crypt/RSA/BLOB.php | 223 +++++++++++++++++++++++++++ tests/Unit/Crypt/RSA/LoadKeyTest.php | 31 ++++ 2 files changed, 254 insertions(+) create mode 100644 phpseclib/Crypt/RSA/BLOB.php diff --git a/phpseclib/Crypt/RSA/BLOB.php b/phpseclib/Crypt/RSA/BLOB.php new file mode 100644 index 00000000..90585b62 --- /dev/null +++ b/phpseclib/Crypt/RSA/BLOB.php @@ -0,0 +1,223 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\RSA; + +use phpseclib\Math\BigInteger; + +/** + * BLOB Formatted RSA Key Handler + * + * @package RSA + * @author Jim Wigginton + * @access public + */ +class BLOB +{ + /**#@+ + * @access private + */ + /** + * Public/Private Key Pair + */ + const PRIVATEKEYBLOB = 0x7; + /** + * Public Key + */ + const PUBLICKEYBLOB = 0x6; + /** + * Public Key + */ + const PUBLICKEYBLOBEX = 0xA; + /** + * RSA public key exchange algorithm + */ + const CALG_RSA_KEYX = 0x0000A400; + /** + * RSA public key exchange algorithm + */ + const CALG_RSA_SIGN = 0x00002400; + /** + * Public Key + */ + const RSA1 = 0x31415352; + /** + * Private Key + */ + const RSA2 = 0x32415352; + /**#@-*/ + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + static function load($key, $password = '') + { + if (!is_string($key)) { + return false; + } + + $key = base64_decode($key); + + if (!is_string($key) || strlen($key) < 20) { + return false; + } + + // PUBLICKEYSTRUC publickeystruc + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx + extract(unpack('atype/aversion/vreserved/Valgo', self::_string_shift($key, 8))); + switch (ord($type)) { + case self::PUBLICKEYBLOB: + case self::PUBLICKEYBLOBEX: + $publickey = true; + break; + case self::PRIVATEKEYBLOB: + $publickey = false; + break; + default: + return false; + } + + $components = array('isPublicKey' => $publickey); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx + switch ($algo) { + case self::CALG_RSA_KEYX: + case self::CALG_RSA_SIGN: + break; + default: + return false; + } + + // RSAPUBKEY rsapubkey + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx + // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit + extract(unpack('Vmagic/Vbitlen/a4pubexp', self::_string_shift($key, 12))); + switch ($magic) { + case self::RSA2: + $components['isPublicKey'] = false; + case self::RSA1: + break; + default: + return false; + } + + $baseLength = $bitlen / 16; + if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) { + return false; + } + + $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256); + // BYTE modulus[rsapubkey.bitlen/8] + $components['modulus'] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 8)), 256); + + if ($publickey) { + return $components; + } + + $components['isPublicKey'] = false; + + // BYTE prime1[rsapubkey.bitlen/16] + $components['primes'] = array(1 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256)); + // BYTE prime2[rsapubkey.bitlen/16] + $components['primes'][] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256); + // BYTE exponent1[rsapubkey.bitlen/16] + $components['exponents'] = array(1 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256)); + // BYTE exponent2[rsapubkey.bitlen/16] + $components['exponents'][] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256); + // BYTE coefficient[rsapubkey.bitlen/16] + $components['coefficients'] = array(2 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256)); + if (isset($components['privateExponent'])) { + $components['publicExponent'] = $components['privateExponent']; + } + // BYTE privateExponent[rsapubkey.bitlen/8] + $components['privateExponent'] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 8)), 256); + + return $components; + } + + /** + * Convert a private key to the appropriate format. + * + * @access public + * @param \phpseclib\Math\BigInteger $n + * @param \phpseclib\Math\BigInteger $e + * @param \phpseclib\Math\BigInteger $d + * @param array $primes + * @param array $exponents + * @param array $coefficients + * @param string $password optional + * @return string + */ + static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '') + { + $n = strrev($n->toBytes()); + $e = str_pad(strrev($e->toBytes()), 4, "\0"); + $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); + $key.= pack('VVa*', self::RSA2, 8 * strlen($n), $e); + $key.= $n; + $key.= strrev($primes[1]->toBytes()); + $key.= strrev($primes[2]->toBytes()); + $key.= strrev($exponents[1]->toBytes()); + $key.= strrev($exponents[2]->toBytes()); + $key.= strrev($coefficients[1]->toBytes()); + $key.= strrev($d->toBytes()); + + return base64_encode($key); + } + + /** + * Convert a public key to the appropriate format + * + * @access public + * @param \phpseclib\Math\BigInteger $n + * @param \phpseclib\Math\BigInteger $e + * @return string + */ + static function savePublicKey(BigInteger $n, BigInteger $e) + { + $n = strrev($n->toBytes()); + $e = str_pad(strrev($e->toBytes()), 4, "\0"); + $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); + $key.= pack('VVa*', self::RSA1, 8 * strlen($n), $e); + $key.= $n; + + return base64_encode($key); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param string $string + * @param int $index + * @return string + * @access private + */ + static function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} diff --git a/tests/Unit/Crypt/RSA/LoadKeyTest.php b/tests/Unit/Crypt/RSA/LoadKeyTest.php index f309c7d9..54b34c5b 100644 --- a/tests/Unit/Crypt/RSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/RSA/LoadKeyTest.php @@ -408,4 +408,35 @@ Private-MAC: 35134b7434bf828b21404099861d455e660e8740'; $rsa->load($raw); $this->assertGreaterThanOrEqual(1, strlen("$rsa")); } + + public function testPrivateBlob() + { + $key = 'BwIAAACkAABSU0EyAAQAAAEAAQAnh6FFs6kYe/gmb9dzqsQKmtjFE9mxNAe9mEU3OwOEEfyI' . + 'wkAx0/8dwh12fuP4wzNbdZAq4mmqCE6Lo8wTNNIJVNYEhKq5chHg1+hPDgfETFgtEO54JZSg' . + '3cBZWEV/Tq3LHEX8CaLvHZxMEfFXbTfliFYMLoJ+YK1mpg9GYcmbrVmMAKSoOgETkkiJJzYm' . + 'XftO3KOveBtvkAzjHxxSS1yP/Ba10BzeIleH96SbTuQtQRLXwRykdX9uazK+YsiSud9/PyLb' . + 'gy5TI+o28OHq5P+0y5+a9IaAQ/92UwlrkHUYfhN/xTVlUIxKlTEdUQTIf+iHif8d4ABb3OdY' . + 'JXZOW6fGeUP10jMyvbnrEoPDsYy9qfNk++0/8UP2NeO1IATszuZYg1nEXOW/5jmUxMCdiFyd' . + 'p9ES211kpEZ4XcvjGaDlaQ+bLWj05i2m/9aHYcBrfcxxvlMa/9ZvrX4DfPWeydUDDDQ4+ntp' . + 'T50BunSvmyf7cUk76Bf2sPgLXUQFoufEQ5g1Qo/v1uyhWBJzh6OSUO/DDXN/s8ec/tN05RQQ' . + 'FZQ0na+v0hOCrV9IuRqtBuj4WAj1I/A1JjwyyP9Y/6yWFPM6EcS/6lyPy30lJPoULh7G29zk' . + 'n7NVdTEkDtthdDjtX7Qhgd9qWvm5ADlmnvsS9A5m7ToOgQyOxtJoSlLitLbf/09LRycl/cdI' . + 'zoMOCEdPe3DQcyEKqUPsghAq+DKw3uZpXwHzwTdfqlHSWAnHDggFKV1HZuWc1c4rV4k4b513TqE='; + + $plaintext = 'zzz'; + + $privKey = new RSA(); + $privKey->load($key); + + $this->assertGreaterThanOrEqual(1, strlen("$privKey")); + + $pubKey = new RSA(); + $pubKey->load($privKey->getPublicKey('blob')); + + $this->assertGreaterThanOrEqual(1, strlen("$pubKey")); + + $ciphertext = $pubKey->encrypt($plaintext); + + $this->assertSame($privKey->decrypt($ciphertext), $plaintext); + } }