Switch from using PyCrypto's Random to using os.urandom.

There's several reasons for this change:

1) It's faster for reads up to 1024 bytes (nearly 10x faster for 16 byte reads)
2) It receives considerably more security review since it's in the kernel.
3) It's yet another step towards running on PyPy.
4) Using userspace CSPRNGs is considered something of an anti-pattern. See:
   http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
   http://webcache.googleusercontent.com/search?q=cache:2nTvpCgKZXIJ:www.2uo.de/myths-about-urandom/+&cd=3&hl=en&ct=clnk&gl=us
This commit is contained in:
Alex Gaynor 2014-03-29 19:22:36 -07:00
parent 5a430def22
commit 6f211115f4
18 changed files with 84 additions and 84 deletions

View File

@ -364,7 +364,7 @@ class AgentKey(PKey):
def get_name(self):
return self.name
def sign_ssh_data(self, rng, data):
def sign_ssh_data(self, data):
msg = Message()
msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST)
msg.add_string(self.blob)

View File

@ -206,7 +206,7 @@ class AuthHandler (object):
m.add_string(self.private_key.get_name())
m.add_string(self.private_key)
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
sig = self.private_key.sign_ssh_data(self.transport.rng, blob)
sig = self.private_key.sign_ssh_data(blob)
m.add_string(sig)
elif self.auth_method == 'keyboard-interactive':
m.add_string('')

View File

@ -21,9 +21,10 @@ Abstraction for an SSH2 channel.
"""
import binascii
import os
import socket
import time
import threading
import socket
from paramiko import util
from paramiko.common import cMSG_CHANNEL_REQUEST, cMSG_CHANNEL_WINDOW_ADJUST, \
@ -358,7 +359,7 @@ class Channel (object):
if auth_protocol is None:
auth_protocol = 'MIT-MAGIC-COOKIE-1'
if auth_cookie is None:
auth_cookie = binascii.hexlify(self.transport.rng.read(16))
auth_cookie = binascii.hexlify(os.urandom(16))
m = Message()
m.add_byte(cMSG_CHANNEL_REQUEST)

View File

@ -126,11 +126,6 @@ CONNECTION_FAILED_CODE = {
DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \
DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14
from Crypto import Random
# keep a crypto-strong PRNG nearby
rng = Random.new()
zero_byte = byte_chr(0)
one_byte = byte_chr(1)
four_byte = byte_chr(4)

View File

@ -20,11 +20,13 @@
DSS keys.
"""
import os
from Crypto.PublicKey import DSA
from Crypto.Hash import SHA
from paramiko import util
from paramiko.common import zero_byte, rng
from paramiko.common import zero_byte
from paramiko.py3compat import long
from paramiko.ssh_exception import SSHException
from paramiko.message import Message
@ -95,13 +97,13 @@ class DSSKey (PKey):
def can_sign(self):
return self.x is not None
def sign_ssh_data(self, rng, data):
def sign_ssh_data(self, data):
digest = SHA.new(data).digest()
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x)))
# generate a suitable k
qsize = len(util.deflate_long(self.q, 0))
while True:
k = util.inflate_long(rng.read(qsize), 1)
k = util.inflate_long(os.urandom(qsize), 1)
if (k > 2) and (k < self.q):
break
r, s = dss.sign(util.inflate_long(digest, 1), k)
@ -163,7 +165,7 @@ class DSSKey (PKey):
by ``pyCrypto.PublicKey``).
:return: new `.DSSKey` private key
"""
dsa = DSA.generate(bits, rng.read, progress_func)
dsa = DSA.generate(bits, os.urandom, progress_func)
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
key.x = dsa.x
return key

View File

@ -21,11 +21,14 @@ L{ECDSAKey}
"""
import binascii
from ecdsa import SigningKey, VerifyingKey, der, curves
from Crypto.Hash import SHA256
from ecdsa.test_pyecdsa import ECDSA
from paramiko.common import four_byte, one_byte
import os
from ecdsa import SigningKey, VerifyingKey, der, curves
from ecdsa.test_pyecdsa import ECDSA
from Crypto.Hash import SHA256
from paramiko.common import four_byte, one_byte
from paramiko.message import Message
from paramiko.pkey import PKey
from paramiko.py3compat import byte_chr, u
@ -97,9 +100,9 @@ class ECDSAKey (PKey):
def can_sign(self):
return self.signing_key is not None
def sign_ssh_data(self, rpool, data):
def sign_ssh_data(self, data):
digest = SHA256.new(data).digest()
sig = self.signing_key.sign_digest(digest, entropy=rpool.read,
sig = self.signing_key.sign_digest(digest, entropy=os.urandom,
sigencode=self._sigencode)
m = Message()
m.add_string('ecdsa-sha2-nistp256')

View File

@ -18,8 +18,10 @@
import binascii
import os
from Crypto.Hash import SHA, HMAC
from paramiko.common import rng
from paramiko.py3compat import b, u, encodebytes, decodebytes
try:
@ -262,7 +264,7 @@ class HostKeys (MutableMapping):
:return: the hashed hostname as a `str`
"""
if salt is None:
salt = rng.read(SHA.digest_size)
salt = os.urandom(SHA.digest_size)
else:
if salt.startswith('|1|'):
salt = salt.split('|')[2]

View File

@ -22,6 +22,8 @@ generator "g" are provided by the server. A bit more work is required on the
client side, and a B{lot} more on the server side.
"""
import os
from Crypto.Hash import SHA
from paramiko import util
@ -101,7 +103,7 @@ class KexGex (object):
qhbyte <<= 1
qmask >>= 1
while True:
x_bytes = self.transport.rng.read(byte_count)
x_bytes = os.urandom(byte_count)
x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:]
x = util.inflate_long(x_bytes, 1)
if (x > 1) and (x < q):
@ -206,7 +208,7 @@ class KexGex (object):
H = SHA.new(hm.asbytes()).digest()
self.transport._set_K_H(K, H)
# sign it
sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H)
sig = self.transport.get_server_key().sign_ssh_data(H)
# send reply
m = Message()
m.add_byte(c_MSG_KEXDH_GEX_REPLY)

View File

@ -21,6 +21,8 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
1024 bit key halves, using a known "p" prime and "g" generator.
"""
import os
from Crypto.Hash import SHA
from paramiko import util
@ -82,7 +84,7 @@ class KexGroup1(object):
# potential x where the first 63 bits are 1, because some of those will be
# larger than q (but this is a tiny tiny subset of potential x).
while 1:
x_bytes = self.transport.rng.read(128)
x_bytes = os.urandom(128)
x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
if (x_bytes[:8] != b7fffffffffffffff and
x_bytes[:8] != b0000000000000000):
@ -127,7 +129,7 @@ class KexGroup1(object):
H = SHA.new(hm.asbytes()).digest()
self.transport._set_K_H(K, H)
# sign it
sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H)
sig = self.transport.get_server_key().sign_ssh_data(H)
# send reply
m = Message()
m.add_byte(c_MSG_KEXDH_REPLY)

View File

@ -21,6 +21,7 @@ Packet handling
"""
import errno
import os
import socket
import struct
import threading
@ -28,7 +29,7 @@ import time
from paramiko import util
from paramiko.common import linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, \
DEBUG, xffffffff, zero_byte, rng
DEBUG, xffffffff, zero_byte
from paramiko.py3compat import u, byte_ord
from paramiko.ssh_exception import SSHException, ProxyCommandFailure
from paramiko.message import Message
@ -455,7 +456,7 @@ class Packetizer (object):
# don't waste random bytes for the padding
packet += (zero_byte * padding)
else:
packet += rng.read(padding)
packet += os.urandom(padding)
return packet
def _trigger_rekey(self):

View File

@ -28,7 +28,7 @@ from Crypto.Hash import MD5
from Crypto.Cipher import DES3, AES
from paramiko import util
from paramiko.common import o600, rng, zero_byte
from paramiko.common import o600, zero_byte
from paramiko.py3compat import u, encodebytes, decodebytes, b
from paramiko.ssh_exception import SSHException, PasswordRequiredException
@ -138,12 +138,11 @@ class PKey (object):
"""
return u(encodebytes(self.asbytes())).replace('\n', '')
def sign_ssh_data(self, rng, data):
def sign_ssh_data(self, data):
"""
Sign a blob of data with this private key, and return a `.Message`
representing an SSH signature message.
:param .Crypto.Util.rng.RandomPool rng: a secure random number generator.
:param str data: the data to sign.
:return: an SSH signature `message <.Message>`.
"""
@ -331,11 +330,11 @@ class PKey (object):
keysize = self._CIPHER_TABLE[cipher_name]['keysize']
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
mode = self._CIPHER_TABLE[cipher_name]['mode']
salt = rng.read(16)
salt = os.urandom(16)
key = util.generate_key_bytes(MD5, salt, password, keysize)
if len(data) % blocksize != 0:
n = blocksize - len(data) % blocksize
#data += rng.read(n)
#data += os.urandom(n)
# that would make more sense ^, but it confuses openssh.
data += zero_byte * n
data = cipher.new(key, mode, salt).encrypt(data)

View File

@ -20,6 +20,8 @@
Utility functions for dealing with primes.
"""
import os
from Crypto.Util import number
from paramiko import util
@ -27,12 +29,12 @@ from paramiko.py3compat import byte_mask, long
from paramiko.ssh_exception import SSHException
def _generate_prime(bits, rng):
def _generate_prime(bits):
"""primtive attempt at prime generation"""
hbyte_mask = pow(2, bits % 8) - 1
while True:
# loop catches the case where we increment n into a higher bit-range
x = rng.read((bits + 7) // 8)
x = os.urandom((bits + 7) // 8)
if hbyte_mask > 0:
x = byte_mask(x[0], hbyte_mask) + x[1:]
n = util.inflate_long(x, 1)
@ -45,7 +47,7 @@ def _generate_prime(bits, rng):
return n
def _roll_random(rng, n):
def _roll_random(n):
"""returns a random # from 0 to N-1"""
bits = util.bit_length(n - 1)
byte_count = (bits + 7) // 8
@ -58,7 +60,7 @@ def _roll_random(rng, n):
# fits, so i can't guarantee that this loop will ever finish, but the odds
# of it looping forever should be infinitesimal.
while True:
x = rng.read(byte_count)
x = os.urandom(byte_count)
if hbyte_mask > 0:
x = byte_mask(x[0], hbyte_mask) + x[1:]
num = util.inflate_long(x, 1)
@ -73,11 +75,10 @@ class ModulusPack (object):
on systems that have such a file.
"""
def __init__(self, rpool):
def __init__(self):
# pack is a hash of: bits -> [ (generator, modulus) ... ]
self.pack = {}
self.discarded = []
self.rng = rpool
def _parse_modulus(self, line):
timestamp, mod_type, tests, tries, size, generator, modulus = line.split()
@ -147,5 +148,5 @@ class ModulusPack (object):
if min > good:
good = bitsizes[-1]
# now pick a random modulus of this bitsize
n = _roll_random(self.rng, len(self.pack[good]))
n = _roll_random(len(self.pack[good]))
return self.pack[good][n]

View File

@ -20,11 +20,13 @@
RSA keys.
"""
import os
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
from paramiko import util
from paramiko.common import rng, max_byte, zero_byte, one_byte
from paramiko.common import max_byte, zero_byte, one_byte
from paramiko.message import Message
from paramiko.ber import BER, BERException
from paramiko.pkey import PKey
@ -90,7 +92,7 @@ class RSAKey (PKey):
def can_sign(self):
return self.d is not None
def sign_ssh_data(self, rpool, data):
def sign_ssh_data(self, data):
digest = SHA.new(data).digest()
rsa = RSA.construct((long(self.n), long(self.e), long(self.d)))
sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), bytes())[0], 0)
@ -140,7 +142,7 @@ class RSAKey (PKey):
by ``pyCrypto.PublicKey``).
:return: new `.RSAKey` private key
"""
rsa = RSA.generate(bits, rng.read, progress_func)
rsa = RSA.generate(bits, os.urandom, progress_func)
key = RSAKey(vals=(rsa.e, rsa.n))
key.d = rsa.d
key.p = rsa.p

View File

@ -20,6 +20,7 @@
Core protocol implementation
"""
import os
import socket
import sys
import threading
@ -30,7 +31,7 @@ import paramiko
from paramiko import util
from paramiko.auth_handler import AuthHandler
from paramiko.channel import Channel
from paramiko.common import rng, xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
cMSG_GLOBAL_REQUEST, DEBUG, MSG_KEXINIT, MSG_IGNORE, MSG_DISCONNECT, \
MSG_DEBUG, ERROR, WARNING, cMSG_UNIMPLEMENTED, INFO, cMSG_KEXINIT, \
cMSG_NEWKEYS, MSG_NEWKEYS, cMSG_REQUEST_SUCCESS, cMSG_REQUEST_FAILURE, \
@ -57,7 +58,6 @@ from paramiko.ssh_exception import (SSHException, BadAuthenticationType,
ChannelException, ProxyCommandFailure)
from paramiko.util import retry_on_signal
from Crypto import Random
from Crypto.Cipher import Blowfish, AES, DES3, ARC4
from Crypto.Hash import SHA, MD5
try:
@ -192,7 +192,6 @@ class Transport (threading.Thread):
# okay, normal socket-ish flow here...
threading.Thread.__init__(self)
self.setDaemon(True)
self.rng = rng
self.sock = sock
# Python < 2.3 doesn't have the settimeout method - RogerB
try:
@ -339,7 +338,6 @@ class Transport (threading.Thread):
# synchronous, wait for a result
self.completion_event = event = threading.Event()
self.start()
Random.atfork()
while True:
event.wait(0.1)
if not self.active:
@ -475,7 +473,7 @@ class Transport (threading.Thread):
.. note:: This has no effect when used in client mode.
"""
Transport._modulus_pack = ModulusPack(rng)
Transport._modulus_pack = ModulusPack()
# places to look for the openssh "moduli" file
file_list = ['/etc/ssh/moduli', '/usr/local/etc/moduli']
if filename is not None:
@ -732,8 +730,8 @@ class Transport (threading.Thread):
m = Message()
m.add_byte(cMSG_IGNORE)
if byte_count is None:
byte_count = (byte_ord(rng.read(1)) % 32) + 10
m.add_bytes(rng.read(byte_count))
byte_count = (byte_ord(os.urandom(1)) % 32) + 10
m.add_bytes(os.urandom(byte_count))
self._send_user_message(m)
def renegotiate_keys(self):
@ -1402,10 +1400,6 @@ class Transport (threading.Thread):
# interpreter shutdown.
self.sys = sys
# Required to prevent RNG errors when running inside many subprocess
# containers.
Random.atfork()
# active=True occurs before the thread is launched, to avoid a race
_active_threads.append(self)
if self.server_mode:
@ -1590,7 +1584,7 @@ class Transport (threading.Thread):
m = Message()
m.add_byte(cMSG_KEXINIT)
m.add_bytes(rng.read(16))
m.add_bytes(os.urandom(16))
m.add_list(self._preferred_kex)
m.add_list(available_server_keys)
m.add_list(self._preferred_ciphers)

View File

@ -21,7 +21,9 @@ Some unit tests for the key exchange protocols.
"""
from binascii import hexlify
import os
import unittest
import paramiko.util
from paramiko.kex_group1 import KexGroup1
from paramiko.kex_gex import KexGex
@ -29,9 +31,8 @@ from paramiko import Message
from paramiko.common import byte_chr
class FakeRng (object):
def read(self, n):
return byte_chr(0xcc) * n
def dummy_urandom(n):
return byte_chr(0xcc) * n
class FakeKey (object):
@ -41,7 +42,7 @@ class FakeKey (object):
def asbytes(self):
return b'fake-key'
def sign_ssh_data(self, rng, H):
def sign_ssh_data(self, H):
return b'fake-sig'
@ -53,8 +54,7 @@ class FakeModulusPack (object):
return self.G, self.P
class FakeTransport (object):
rng = FakeRng()
class FakeTransport(object):
local_version = 'SSH-2.0-paramiko_1.0'
remote_version = 'SSH-2.0-lame'
local_kex_init = 'local-kex-init'
@ -91,10 +91,11 @@ class KexTest (unittest.TestCase):
K = 14730343317708716439807310032871972459448364195094179797249681733965528989482751523943515690110179031004049109375612685505881911274101441415545039654102474376472240501616988799699744135291070488314748284283496055223852115360852283821334858541043710301057312858051901453919067023103730011648890038847384890504
def setUp(self):
pass
self._original_urandom = os.urandom
os.urandom = dummy_urandom
def tearDown(self):
pass
os.urandom = self._original_urandom
def test_1_group1_client(self):
transport = FakeTransport()

View File

@ -22,9 +22,10 @@ Some unit tests for public/private key objects.
from binascii import hexlify
import unittest
from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util
from paramiko.py3compat import StringIO, byte_chr, b, bytes
from paramiko.common import rng
from tests.util import test_path
# from openssh's ssh-keygen
@ -166,7 +167,7 @@ class KeyTest (unittest.TestCase):
def test_8_sign_rsa(self):
# verify that the rsa private key can sign and verify
key = RSAKey.from_private_key_file(test_path('test_rsa.key'))
msg = key.sign_ssh_data(rng, b'ice weasels')
msg = key.sign_ssh_data(b'ice weasels')
self.assertTrue(type(msg) is Message)
msg.rewind()
self.assertEqual('ssh-rsa', msg.get_text())
@ -179,7 +180,7 @@ class KeyTest (unittest.TestCase):
def test_9_sign_dss(self):
# verify that the dss private key can sign and verify
key = DSSKey.from_private_key_file(test_path('test_dss.key'))
msg = key.sign_ssh_data(rng, b'ice weasels')
msg = key.sign_ssh_data(b'ice weasels')
self.assertTrue(type(msg) is Message)
msg.rewind()
self.assertEqual('ssh-dss', msg.get_text())
@ -193,13 +194,13 @@ class KeyTest (unittest.TestCase):
def test_A_generate_rsa(self):
key = RSAKey.generate(1024)
msg = key.sign_ssh_data(rng, b'jerri blank')
msg = key.sign_ssh_data(b'jerri blank')
msg.rewind()
self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
def test_B_generate_dss(self):
key = DSSKey.generate(1024)
msg = key.sign_ssh_data(rng, b'jerri blank')
msg = key.sign_ssh_data(b'jerri blank')
msg.rewind()
self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg))
@ -240,7 +241,7 @@ class KeyTest (unittest.TestCase):
def test_13_sign_ecdsa(self):
# verify that the rsa private key can sign and verify
key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key'))
msg = key.sign_ssh_data(rng, b'ice weasels')
msg = key.sign_ssh_data(b'ice weasels')
self.assertTrue(type(msg) is Message)
msg.rewind()
self.assertEqual('ecdsa-sha2-nistp256', msg.get_text())

View File

@ -153,12 +153,6 @@ class UtilTest(ParamikoTest):
finally:
os.unlink('hostfile.temp')
def test_6_random(self):
from paramiko.common import rng
# just verify that we can pull out 32 bytes and not get an exception.
x = rng.read(32)
self.assertEqual(len(x), 32)
def test_7_host_config_expose_issue_33(self):
test_config_file = """
Host www13.*