primes.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
  2. #
  3. # This file is part of paramiko.
  4. #
  5. # Paramiko is free software; you can redistribute it and/or modify it under the
  6. # terms of the GNU Lesser General Public License as published by the Free
  7. # Software Foundation; either version 2.1 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
  11. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13. # details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public License
  16. # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. """
  19. Utility functions for dealing with primes.
  20. """
  21. import os
  22. from paramiko import util
  23. from paramiko.py3compat import byte_mask, long
  24. from paramiko.ssh_exception import SSHException
  25. def _roll_random(n):
  26. """returns a random # from 0 to N-1"""
  27. bits = util.bit_length(n - 1)
  28. byte_count = (bits + 7) // 8
  29. hbyte_mask = pow(2, bits % 8) - 1
  30. # so here's the plan:
  31. # we fetch as many random bits as we'd need to fit N-1, and if the
  32. # generated number is >= N, we try again. in the worst case (N-1 is a
  33. # power of 2), we have slightly better than 50% odds of getting one that
  34. # fits, so i can't guarantee that this loop will ever finish, but the odds
  35. # of it looping forever should be infinitesimal.
  36. while True:
  37. x = os.urandom(byte_count)
  38. if hbyte_mask > 0:
  39. x = byte_mask(x[0], hbyte_mask) + x[1:]
  40. num = util.inflate_long(x, 1)
  41. if num < n:
  42. break
  43. return num
  44. class ModulusPack(object):
  45. """
  46. convenience object for holding the contents of the /etc/ssh/moduli file,
  47. on systems that have such a file.
  48. """
  49. def __init__(self):
  50. # pack is a hash of: bits -> [ (generator, modulus) ... ]
  51. self.pack = {}
  52. self.discarded = []
  53. def _parse_modulus(self, line):
  54. (
  55. timestamp,
  56. mod_type,
  57. tests,
  58. tries,
  59. size,
  60. generator,
  61. modulus,
  62. ) = line.split()
  63. mod_type = int(mod_type)
  64. tests = int(tests)
  65. tries = int(tries)
  66. size = int(size)
  67. generator = int(generator)
  68. modulus = long(modulus, 16)
  69. # weed out primes that aren't at least:
  70. # type 2 (meets basic structural requirements)
  71. # test 4 (more than just a small-prime sieve)
  72. # tries < 100 if test & 4 (at least 100 tries of miller-rabin)
  73. if (
  74. mod_type < 2
  75. or tests < 4
  76. or (tests & 4 and tests < 8 and tries < 100)
  77. ):
  78. self.discarded.append(
  79. (modulus, "does not meet basic requirements")
  80. )
  81. return
  82. if generator == 0:
  83. generator = 2
  84. # there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay!
  85. # call cnn!) where it understates the bit lengths of these primes by 1.
  86. # this is okay.
  87. bl = util.bit_length(modulus)
  88. if (bl != size) and (bl != size + 1):
  89. self.discarded.append(
  90. (modulus, "incorrectly reported bit length {}".format(size))
  91. )
  92. return
  93. if bl not in self.pack:
  94. self.pack[bl] = []
  95. self.pack[bl].append((generator, modulus))
  96. def read_file(self, filename):
  97. """
  98. :raises IOError: passed from any file operations that fail.
  99. """
  100. self.pack = {}
  101. with open(filename, "r") as f:
  102. for line in f:
  103. line = line.strip()
  104. if (len(line) == 0) or (line[0] == "#"):
  105. continue
  106. try:
  107. self._parse_modulus(line)
  108. except:
  109. continue
  110. def get_modulus(self, min, prefer, max):
  111. bitsizes = sorted(self.pack.keys())
  112. if len(bitsizes) == 0:
  113. raise SSHException("no moduli available")
  114. good = -1
  115. # find nearest bitsize >= preferred
  116. for b in bitsizes:
  117. if (b >= prefer) and (b <= max) and (b < good or good == -1):
  118. good = b
  119. # if that failed, find greatest bitsize >= min
  120. if good == -1:
  121. for b in bitsizes:
  122. if (b >= min) and (b <= max) and (b > good):
  123. good = b
  124. if good == -1:
  125. # their entire (min, max) range has no intersection with our range.
  126. # if their range is below ours, pick the smallest. otherwise pick
  127. # the largest. it'll be out of their range requirement either way,
  128. # but we'll be sending them the closest one we have.
  129. good = bitsizes[0]
  130. if min > good:
  131. good = bitsizes[-1]
  132. # now pick a random modulus of this bitsize
  133. n = _roll_random(len(self.pack[good]))
  134. return self.pack[good][n]