1<?php 2 3/** 4 * Pure-PHP FIPS 186-4 compliant implementation of DSA. 5 * 6 * PHP version 5 7 * 8 * Here's an example of how to create signatures and verify signatures with this library: 9 * <code> 10 * <?php 11 * include 'vendor/autoload.php'; 12 * 13 * $private = \phpseclib3\Crypt\DSA::createKey(); 14 * $public = $private->getPublicKey(); 15 * 16 * $plaintext = 'terrafrost'; 17 * 18 * $signature = $private->sign($plaintext); 19 * 20 * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified'; 21 * ?> 22 * </code> 23 * 24 * @category Crypt 25 * @package DSA 26 * @author Jim Wigginton <terrafrost@php.net> 27 * @copyright 2016 Jim Wigginton 28 * @license http://www.opensource.org/licenses/mit-license.html MIT License 29 * @link http://phpseclib.sourceforge.net 30 */ 31 32namespace phpseclib3\Crypt; 33 34use phpseclib3\Crypt\Common\AsymmetricKey; 35use phpseclib3\Crypt\DSA\Parameters; 36use phpseclib3\Crypt\DSA\PrivateKey; 37use phpseclib3\Crypt\DSA\PublicKey; 38use phpseclib3\Exception\InsufficientSetupException; 39use phpseclib3\Math\BigInteger; 40 41/** 42 * Pure-PHP FIPS 186-4 compliant implementation of DSA. 43 * 44 * @package DSA 45 * @author Jim Wigginton <terrafrost@php.net> 46 * @access public 47 */ 48abstract class DSA extends AsymmetricKey 49{ 50 /** 51 * Algorithm Name 52 * 53 * @var string 54 * @access private 55 */ 56 const ALGORITHM = 'DSA'; 57 58 /** 59 * DSA Prime P 60 * 61 * @var \phpseclib3\Math\BigInteger 62 * @access private 63 */ 64 protected $p; 65 66 /** 67 * DSA Group Order q 68 * 69 * Prime divisor of p-1 70 * 71 * @var \phpseclib3\Math\BigInteger 72 * @access private 73 */ 74 protected $q; 75 76 /** 77 * DSA Group Generator G 78 * 79 * @var \phpseclib3\Math\BigInteger 80 * @access private 81 */ 82 protected $g; 83 84 /** 85 * DSA public key value y 86 * 87 * @var \phpseclib3\Math\BigInteger 88 * @access private 89 */ 90 protected $y; 91 92 /** 93 * Signature Format 94 * 95 * @var string 96 * @access private 97 */ 98 protected $sigFormat; 99 100 /** 101 * Signature Format (Short) 102 * 103 * @var string 104 * @access private 105 */ 106 protected $shortFormat; 107 108 /** 109 * Create DSA parameters 110 * 111 * @access public 112 * @param int $L 113 * @param int $N 114 * @return \phpseclib3\Crypt\DSA|bool 115 */ 116 public static function createParameters($L = 2048, $N = 224) 117 { 118 self::initialize_static_variables(); 119 120 if (!isset(self::$engines['PHP'])) { 121 self::useBestEngine(); 122 } 123 124 switch (true) { 125 case $N == 160: 126 /* 127 in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024. 128 RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most 129 SSH DSA implementations only support keys with an N of 160. 130 puttygen let's you set the size of L (but not the size of N) and uses 2048 as the 131 default L value. that's not really compliant with any of the FIPS standards, however, 132 for the purposes of maintaining compatibility with puttygen, we'll support it 133 */ 134 //case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160: 135 // FIPS 186-3 changed this as follows: 136 //case $L == 1024 && $N == 160: 137 case $L == 2048 && $N == 224: 138 case $L == 2048 && $N == 256: 139 case $L == 3072 && $N == 256: 140 break; 141 default: 142 throw new \InvalidArgumentException('Invalid values for N and L'); 143 } 144 145 $two = new BigInteger(2); 146 147 $q = BigInteger::randomPrime($N); 148 $divisor = $q->multiply($two); 149 150 do { 151 $x = BigInteger::random($L); 152 list(, $c) = $x->divide($divisor); 153 $p = $x->subtract($c->subtract(self::$one)); 154 } while ($p->getLength() != $L || !$p->isPrime()); 155 156 $p_1 = $p->subtract(self::$one); 157 list($e) = $p_1->divide($q); 158 159 // quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 , 160 // "h could be obtained from a random number generator or from a counter that 161 // changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments 162 // it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that 163 $h = clone $two; 164 while (true) { 165 $g = $h->powMod($e, $p); 166 if (!$g->equals(self::$one)) { 167 break; 168 } 169 $h = $h->add(self::$one); 170 } 171 172 $dsa = new Parameters(); 173 $dsa->p = $p; 174 $dsa->q = $q; 175 $dsa->g = $g; 176 177 return $dsa; 178 } 179 180 /** 181 * Create public / private key pair. 182 * 183 * This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or 184 * no parameters (at which point L and N will be generated with this method) 185 * 186 * Returns the private key, from which the publickey can be extracted 187 * 188 * @param int[] ...$args 189 * @access public 190 * @return DSA\PrivateKey 191 */ 192 public static function createKey(...$args) 193 { 194 self::initialize_static_variables(); 195 196 if (!isset(self::$engines['PHP'])) { 197 self::useBestEngine(); 198 } 199 200 if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) { 201 $params = self::createParameters($args[0], $args[1]); 202 } elseif (count($args) == 1 && $args[0] instanceof Parameters) { 203 $params = $args[0]; 204 } elseif (!count($args)) { 205 $params = self::createParameters(); 206 } else { 207 throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.'); 208 } 209 210 $private = new PrivateKey(); 211 $private->p = $params->p; 212 $private->q = $params->q; 213 $private->g = $params->g; 214 215 $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one)); 216 $private->y = $private->g->powMod($private->x, $private->p); 217 218 //$public = clone $private; 219 //unset($public->x); 220 221 return $private 222 ->withHash($params->hash->getHash()) 223 ->withSignatureFormat($params->shortFormat); 224 } 225 226 /** 227 * OnLoad Handler 228 * 229 * @return bool 230 * @access protected 231 * @param array $components 232 */ 233 protected static function onLoad($components) 234 { 235 if (!isset(self::$engines['PHP'])) { 236 self::useBestEngine(); 237 } 238 239 if (!isset($components['x']) && !isset($components['y'])) { 240 $new = new Parameters(); 241 } elseif (isset($components['x'])) { 242 $new = new PrivateKey(); 243 $new->x = $components['x']; 244 } else { 245 $new = new PublicKey(); 246 } 247 248 $new->p = $components['p']; 249 $new->q = $components['q']; 250 $new->g = $components['g']; 251 252 if (isset($components['y'])) { 253 $new->y = $components['y']; 254 } 255 256 return $new; 257 } 258 259 /** 260 * Constructor 261 * 262 * PublicKey and PrivateKey objects can only be created from abstract RSA class 263 */ 264 protected function __construct() 265 { 266 $this->sigFormat = self::validatePlugin('Signature', 'ASN1'); 267 $this->shortFormat = 'ASN1'; 268 269 parent::__construct(); 270 } 271 272 /** 273 * Returns the key size 274 * 275 * More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q) 276 * 277 * @access public 278 * @return array 279 */ 280 public function getLength() 281 { 282 return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()]; 283 } 284 285 /** 286 * Returns the current engine being used 287 * 288 * @see self::useInternalEngine() 289 * @see self::useBestEngine() 290 * @access public 291 * @return string 292 */ 293 public function getEngine() 294 { 295 if (!isset(self::$engines['PHP'])) { 296 self::useBestEngine(); 297 } 298 return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ? 299 'OpenSSL' : 'PHP'; 300 } 301 302 /** 303 * Returns the parameters 304 * 305 * A public / private key is only returned if the currently loaded "key" contains an x or y 306 * value. 307 * 308 * @see self::getPublicKey() 309 * @access public 310 * @return mixed 311 */ 312 public function getParameters() 313 { 314 $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters'); 315 316 $key = $type::saveParameters($this->p, $this->q, $this->g); 317 return DSA::load($key, 'PKCS1') 318 ->withHash($this->hash->getHash()) 319 ->withSignatureFormat($this->shortFormat); 320 } 321 322 /** 323 * Determines the signature padding mode 324 * 325 * Valid values are: ASN1, SSH2, Raw 326 * 327 * @access public 328 * @param string $format 329 */ 330 public function withSignatureFormat($format) 331 { 332 $new = clone $this; 333 $new->shortFormat = $format; 334 $new->sigFormat = self::validatePlugin('Signature', $format); 335 return $new; 336 } 337 338 /** 339 * Returns the signature format currently being used 340 * 341 * @access public 342 */ 343 public function getSignatureFormat() 344 { 345 return $this->shortFormat; 346 } 347} 348