| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- <?php
- /**
- * Pure-PHP FIPS 186-4 compliant implementation of DSA.
- *
- * PHP version 5
- *
- * Here's an example of how to create signatures and verify signatures with this library:
- * <code>
- * <?php
- * include 'vendor/autoload.php';
- *
- * extract(\phpseclib\Crypt\DSA::createKey());
- *
- * $plaintext = 'terrafrost';
- *
- * $signature = $privatekey->sign($plaintext, 'ASN1');
- *
- * echo $publickey->verify($plaintext, $signature) ? 'verified' : 'unverified';
- * ?>
- * </code>
- *
- * @category Crypt
- * @package DSA
- * @author Jim Wigginton <terrafrost@php.net>
- * @copyright 2016 Jim Wigginton
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
- * @link http://phpseclib.sourceforge.net
- */
- namespace phpseclib\Crypt;
- use ParagonIE\ConstantTime\Base64;
- use phpseclib\File\ASN1;
- use phpseclib\Math\BigInteger;
- use phpseclib\Crypt\Common\AsymmetricKey;
- use phpseclib\Common\Functions\Strings;
- /**
- * Pure-PHP FIPS 186-4 compliant implementation of DSA.
- *
- * @package DSA
- * @author Jim Wigginton <terrafrost@php.net>
- * @access public
- */
- class DSA extends AsymmetricKey
- {
- /**
- * Algorithm Name
- *
- * @var string
- * @access private
- */
- const ALGORITHM = 'DSA';
- /**
- * DSA Prime P
- *
- * @var \phpseclib\Math\BigInteger
- * @access private
- */
- private $p;
- /**
- * DSA Group Order q
- *
- * Prime divisor of p-1
- *
- * @var \phpseclib\Math\BigInteger
- * @access private
- */
- protected $q;
- /**
- * DSA Group Generator G
- *
- * @var \phpseclib\Math\BigInteger
- * @access private
- */
- private $g;
- /**
- * DSA secret exponent x
- *
- * @var \phpseclib\Math\BigInteger
- * @access private
- */
- protected $x;
- /**
- * DSA public key value y
- *
- * @var \phpseclib\Math\BigInteger
- * @access private
- */
- private $y;
- /**
- * Parameters Format
- *
- * @var string
- * @access private
- */
- private $parametersFormat = 'PKCS1';
- /**
- * Create DSA parameters
- *
- * @access public
- * @param int $L
- * @param int $N
- * @return \phpseclib\Crypt\DSA
- */
- static function createParameters($L = 2048, $N = 224)
- {
- self::initialize_static_variables();
- switch (true) {
- case $N == 160:
- /*
- in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024.
- RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most
- SSH DSA implementations only support keys with an N of 160.
- puttygen let's you set the size of L (but not the size of N) and uses 2048 as the
- default L value. that's not really compliant with any of the FIPS standards, however,
- for the purposes of maintaining compatibility with puttygen, we'll support it
- */
- //case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160:
- // FIPS 186-3 changed this as follows:
- //case $L == 1024 && $N == 160:
- case $L == 2048 && $N == 224:
- case $L == 2048 && $N == 256:
- case $L == 3072 && $N == 256:
- break;
- default:
- return false;
- }
- $two = new BigInteger(2);
- $q = BigInteger::randomPrime($N);
- $divisor = $q->multiply($two);
- do {
- $x = BigInteger::random($L);
- list(, $c) = $x->divide($divisor);
- $p = $x->subtract($c->subtract(self::$one));
- } while ($p->getLength() != $L || !$p->isPrime());
- $p_1 = $p->subtract(self::$one);
- list($e) = $p_1->divide($q);
- // quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 ,
- // "h could be obtained from a random number generator or from a counter that
- // changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments
- // it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that
- $h = clone $two;
- while (true) {
- $g = $h->powMod($e, $p);
- if (!$g->equals(self::$one)) {
- break;
- }
- $h = $h->add(self::$one);
- }
- $dsa = new DSA();
- $dsa->p = $p;
- $dsa->q = $q;
- $dsa->g = $g;
- return $dsa;
- }
- /**
- * Create public / private key pair.
- *
- * This method is a bit polymorphic. It can take a DSA object (eg. pre-loaded with parameters),
- * L / N as two distinct parameters or no parameters (at which point L and N will be generated
- * with this method)
- *
- * Returns an array with the following two elements:
- * - 'privatekey': The private key.
- * - 'publickey': The public key.
- *
- * @access public
- * @return \phpseclib\Crypt\DSA
- */
- static function createKey()
- {
- self::initialize_static_variables();
- $args = func_get_args();
- if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) {
- $private = self::createParameters($args[0], $args[1]);
- } else if (count($args) == 1 && $args[0] instanceof DSA) {
- $private = clone $args[0];
- } else if (!count($args)) {
- $private = self::createParameters();
- } else {
- throw new \InvalidArgumentException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.');
- }
- $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one));
- $private->y = $private->g->powMod($private->x, $private->p);
- $public = clone $private;
- unset($public->x);
- return ['privatekey' => $private, 'publickey' => $public];
- }
- /**
- * Loads a public or private key
- *
- * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed)
- *
- * @access public
- * @param string $key
- * @param int $type optional
- */
- public function load($key, $type = false)
- {
- if ($key instanceof DSA) {
- $this->privateKeyFormat = $key->privateKeyFormat;
- $this->publicKeyFormat = $key->publicKeyFormat;
- $this->format = $key->format;
- $this->p = $key->p;
- $this->q = $key->q;
- $this->g = $key->g;
- $this->x = $key->x;
- $this->y = $key->y;
- $this->parametersFormat = $key->parametersFormat;
- return true;
- }
- $components = parent::load($key, $type);
- if ($components === false) {
- return false;
- }
- if (isset($components['p'])) {
- switch (true) {
- case isset($this->p) && !$this->p->equals($components['p']):
- case isset($this->q) && !$this->q->equals($components['q']):
- case isset($this->g) && !$this->g->equals($components['g']):
- $this->x = $this->y = null;
- }
- $this->p = $components['p'];
- $this->q = $components['q'];
- $this->g = $components['g'];
- }
- if (isset($components['x'])) {
- $this->x = $components['x'];
- }
- if (isset($components['y'])) {
- $this->y = $components['y'];
- }
- //} else if (isset($components['x'])) {
- // $this->y = $this->g->powMod($this->x, $this->p);
- //}
- return true;
- }
- /**
- * Returns the key size
- *
- * More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q)
- *
- * @access public
- * @return array
- */
- public function getLength()
- {
- return isset($this->p) ?
- ['L' => $this->p->getLength(), 'N' => $this->q->getLength()] :
- ['L' => 0, 'N' => 0];
- }
- /**
- * __toString() magic method
- *
- * @access public
- * @return string
- */
- public function __toString()
- {
- $key = parent::__toString();
- if (!empty($key)) {
- return $key;
- }
- try {
- $key = $this->getParameters($this->parametersFormat);
- return is_string($key) ? $key : '';
- } catch (\Exception $e) {
- return '';
- }
- }
- /**
- * Returns the private key
- *
- * PKCS1 DSA private keys contain x and y. PKCS8 DSA private keys just contain x
- * but y can be derived from x.
- *
- * @see self::getPublicKey()
- * @access public
- * @param string $type optional
- * @return mixed
- */
- public function getPrivateKey($type = 'PKCS8')
- {
- $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
- if ($type === false) {
- return false;
- }
- if (!isset($this->x)) {
- return false;
- }
- if (!isset($this->y)) {
- $this->y = $this->g->powMod($this->x, $this->p);
- }
- return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password);
- }
- /**
- * Returns the public key
- *
- * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key
- * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING.
- * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this
- * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g
- * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified
- * by getting a DSA PKCS8 public key:
- *
- * "openssl dsa -in private.dsa -pubout -outform PEM"
- *
- * ie. just swap out rsa with dsa in the rsa command above.
- *
- * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA
- * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature
- * without the parameters and the PKCS1 DSA public key format does not include the parameters.
- *
- * @see self::getPrivateKey()
- * @access public
- * @param string $type optional
- * @return mixed
- */
- public function getPublicKey($type = 'PKCS8')
- {
- $type = self::validatePlugin('Keys', $type, 'savePublicKey');
- if ($type === false) {
- return false;
- }
- if (!isset($this->y)) {
- if (!isset($this->x) || !isset($this->p)) {
- return false;
- }
- $this->y = $this->g->powMod($this->x, $this->p);
- }
- return $type::savePublicKey($this->p, $this->q, $this->g, $this->y);
- }
- /**
- * Returns the parameters
- *
- * A public / private key is only returned if the currently loaded "key" contains an x or y
- * value.
- *
- * @see self::getPublicKey()
- * @see self::getPrivateKey()
- * @access public
- * @param string $type optional
- * @return mixed
- */
- public function getParameters($type = 'PKCS1')
- {
- $type = self::validatePlugin('Keys', $type, 'saveParameters');
- if ($type === false) {
- return false;
- }
- if (!isset($this->p) || !isset($this->q) || !isset($this->g)) {
- return false;
- }
- return $type::saveParameters($this->p, $this->q, $this->g);
- }
- /**
- * Create a signature
- *
- * @see self::verify()
- * @access public
- * @param string $message
- * @param string $format optional
- * @return mixed
- */
- function sign($message, $format = 'Raw')
- {
- $format = self::validatePlugin('Signature', $format);
- if ($format === false) {
- return false;
- }
- if (empty($this->x) || empty($this->p)) {
- return false;
- }
- while (true) {
- $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one));
- $r = $this->g->powMod($k, $this->p);
- list(, $r) = $r->divide($this->q);
- if ($r->equals(self::$zero)) {
- continue;
- }
- $kinv = $k->modInverse($this->q);
- $h = $this->hash->hash($message);
- $h = $this->bits2int($h);
- $temp = $h->add($this->x->multiply($r));
- $temp = $kinv->multiply($temp);
- list(, $s) = $temp->divide($this->q);
- if (!$s->equals(self::$zero)) {
- break;
- }
- }
- // the following is an RFC6979 compliant implementation of deterministic DSA
- // it's unused because it's mainly intended for use when a good CSPRNG isn't
- // available. if phpseclib's CSPRNG isn't good then even key generation is
- // suspect
- /*
- $h1 = $this->hash->hash($message);
- $k = $this->computek($h1);
- $r = $this->g->powMod($k, $this->p);
- list(, $r) = $r->divide($this->q);
- $kinv = $k->modInverse($this->q);
- $h1 = $this->bits2int($h1);
- $temp = $h1->add($this->x->multiply($r));
- $temp = $kinv->multiply($temp);
- list(, $s) = $temp->divide($this->q);
- */
- return $format::save($r, $s);
- }
- /**
- * Verify a signature
- *
- * @see self::verify()
- * @access public
- * @param string $message
- * @param string $format optional
- * @return mixed
- */
- function verify($message, $signature, $format = 'Raw')
- {
- $format = self::validatePlugin('Signature', $format);
- if ($format === false) {
- return false;
- }
- $params = $format::load($signature);
- if ($params === false || count($params) != 2) {
- return false;
- }
- extract($params);
- if (empty($this->y) || empty($this->p)) {
- return false;
- }
- $q_1 = $this->q->subtract(self::$one);
- if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) {
- return false;
- }
- $w = $s->modInverse($this->q);
- $h = $this->hash->hash($message);
- $h = $this->bits2int($h);
- list(, $u1) = $h->multiply($w)->divide($this->q);
- list(, $u2) = $r->multiply($w)->divide($this->q);
- $v1 = $this->g->powMod($u1, $this->p);
- $v2 = $this->y->powMod($u2, $this->p);
- list(, $v) = $v1->multiply($v2)->divide($this->p);
- list(, $v) = $v->divide($this->q);
- return Strings::equals($v->toBytes(), $r->toBytes());
- }
- }
|