Switched hash functions from PyCrypto to hashlib.

There's a few advantages to this:

1) It's probably fast, OpenSSL, which typically backs hashlib, receives far
   more attention for optimizaitons than PyCrypto.
2) It's the first step to supporting PyPy, where PyCrypto doesn't run.
This commit is contained in:
Alex Gaynor 2014-03-29 16:55:01 -07:00
parent 5a430def22
commit 4d3e0b711a
14 changed files with 71 additions and 66 deletions

View File

@ -20,8 +20,9 @@
DSS keys.
"""
from hashlib import sha1
from Crypto.PublicKey import DSA
from Crypto.Hash import SHA
from paramiko import util
from paramiko.common import zero_byte, rng
@ -96,7 +97,7 @@ class DSSKey (PKey):
return self.x is not None
def sign_ssh_data(self, rng, data):
digest = SHA.new(data).digest()
digest = sha1(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))
@ -130,7 +131,7 @@ class DSSKey (PKey):
# pull out (r, s) which are NOT encoded as mpints
sigR = util.inflate_long(sig[:20], 1)
sigS = util.inflate_long(sig[20:], 1)
sigM = util.inflate_long(SHA.new(data).digest(), 1)
sigM = util.inflate_long(sha1(data).digest(), 1)
dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q)))
return dss.verify(sigM, (sigR, sigS))

View File

@ -21,11 +21,12 @@ 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
from hashlib import sha256
from ecdsa import SigningKey, VerifyingKey, der, curves
from ecdsa.test_pyecdsa import ECDSA
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
@ -98,7 +99,7 @@ class ECDSAKey (PKey):
return self.signing_key is not None
def sign_ssh_data(self, rpool, data):
digest = SHA256.new(data).digest()
digest = sha256(data).digest()
sig = self.signing_key.sign_digest(digest, entropy=rpool.read,
sigencode=self._sigencode)
m = Message()
@ -113,7 +114,7 @@ class ECDSAKey (PKey):
# verify the signature by SHA'ing the data and encrypting it
# using the public key.
hash_obj = SHA256.new(data).digest()
hash_obj = sha256(data).digest()
return self.verifying_key.verify_digest(sig, hash_obj,
sigdecode=self._sigdecode)

View File

@ -18,7 +18,9 @@
import binascii
from Crypto.Hash import SHA, HMAC
from hashlib import sha1
from hmac import HMAC
from paramiko.common import rng
from paramiko.py3compat import b, u, encodebytes, decodebytes
@ -262,13 +264,13 @@ class HostKeys (MutableMapping):
:return: the hashed hostname as a `str`
"""
if salt is None:
salt = rng.read(SHA.digest_size)
salt = rng.read(sha1().digest_size)
else:
if salt.startswith('|1|'):
salt = salt.split('|')[2]
salt = decodebytes(b(salt))
assert len(salt) == SHA.digest_size
hmac = HMAC.HMAC(salt, b(hostname), SHA).digest()
assert len(salt) == sha1().digest_size
hmac = HMAC(salt, b(hostname), sha1).digest()
hostkey = '|1|%s|%s' % (u(encodebytes(salt)), u(encodebytes(hmac)))
return hostkey.replace('\n', '')
hash_host = staticmethod(hash_host)

View File

@ -22,7 +22,7 @@ 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.
"""
from Crypto.Hash import SHA
from hashlib import sha1
from paramiko import util
from paramiko.common import DEBUG
@ -203,7 +203,7 @@ class KexGex (object):
hm.add_mpint(self.e)
hm.add_mpint(self.f)
hm.add_mpint(K)
H = SHA.new(hm.asbytes()).digest()
H = sha1(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)
@ -238,6 +238,6 @@ class KexGex (object):
hm.add_mpint(self.e)
hm.add_mpint(self.f)
hm.add_mpint(K)
self.transport._set_K_H(K, SHA.new(hm.asbytes()).digest())
self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
self.transport._verify_key(host_key, sig)
self.transport._activate_outbound()

View File

@ -21,7 +21,7 @@ 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.
"""
from Crypto.Hash import SHA
from hashlib import sha1
from paramiko import util
from paramiko.common import max_byte, zero_byte
@ -105,7 +105,7 @@ class KexGroup1(object):
hm.add_mpint(self.e)
hm.add_mpint(self.f)
hm.add_mpint(K)
self.transport._set_K_H(K, SHA.new(hm.asbytes()).digest())
self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
self.transport._verify_key(host_key, sig)
self.transport._activate_outbound()
@ -124,7 +124,7 @@ class KexGroup1(object):
hm.add_mpint(self.e)
hm.add_mpint(self.f)
hm.add_mpint(K)
H = SHA.new(hm.asbytes()).digest()
H = sha1(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)

View File

@ -25,6 +25,7 @@ import socket
import struct
import threading
import time
from hmac import HMAC
from paramiko import util
from paramiko.common import linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, \
@ -34,12 +35,6 @@ from paramiko.ssh_exception import SSHException, ProxyCommandFailure
from paramiko.message import Message
try:
from r_hmac import HMAC
except ImportError:
from Crypto.Hash.HMAC import HMAC
def compute_hmac(key, message, digest_class):
return HMAC(key, message, digest_class).digest()

View File

@ -23,8 +23,8 @@ Common API for all public keys.
import base64
from binascii import hexlify, unhexlify
import os
from hashlib import md5
from Crypto.Hash import MD5
from Crypto.Cipher import DES3, AES
from paramiko import util
@ -126,7 +126,7 @@ class PKey (object):
a 16-byte `string <str>` (binary) of the MD5 fingerprint, in SSH
format.
"""
return MD5.new(self.asbytes()).digest()
return md5(self.asbytes()).digest()
def get_base64(self):
"""
@ -300,7 +300,7 @@ class PKey (object):
keysize = self._CIPHER_TABLE[encryption_type]['keysize']
mode = self._CIPHER_TABLE[encryption_type]['mode']
salt = unhexlify(b(saltstr))
key = util.generate_key_bytes(MD5, salt, password, keysize)
key = util.generate_key_bytes(md5, salt, password, keysize)
return cipher.new(key, mode, salt).decrypt(data)
def _write_private_key_file(self, tag, filename, data, password=None):
@ -332,7 +332,7 @@ class PKey (object):
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
mode = self._CIPHER_TABLE[cipher_name]['mode']
salt = rng.read(16)
key = util.generate_key_bytes(MD5, salt, password, keysize)
key = util.generate_key_bytes(md5, salt, password, keysize)
if len(data) % blocksize != 0:
n = blocksize - len(data) % blocksize
#data += rng.read(n)

View File

@ -20,8 +20,9 @@
RSA keys.
"""
from hashlib import sha1
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
@ -91,7 +92,7 @@ class RSAKey (PKey):
return self.d is not None
def sign_ssh_data(self, rpool, data):
digest = SHA.new(data).digest()
digest = sha1(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)
m = Message()
@ -106,7 +107,7 @@ class RSAKey (PKey):
# verify the signature by SHA'ing the data and encrypting it using the
# public key. some wackiness ensues where we "pkcs1imify" the 20-byte
# hash into a string as long as the RSA key.
hash_obj = util.inflate_long(self._pkcs1imify(SHA.new(data).digest()), True)
hash_obj = util.inflate_long(self._pkcs1imify(sha1(data).digest()), True)
rsa = RSA.construct((long(self.n), long(self.e)))
return rsa.verify(hash_obj, (sig,))

View File

@ -22,9 +22,9 @@ Server-mode SFTP support.
import os
import errno
from Crypto.Hash import MD5, SHA
import sys
from hashlib import md5, sha1
from paramiko import util
from paramiko.sftp import BaseSFTP, Message, SFTP_FAILURE, \
SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE
@ -45,8 +45,8 @@ from paramiko.sftp import CMD_HANDLE, SFTP_DESC, CMD_STATUS, SFTP_EOF, CMD_NAME,
CMD_READLINK, CMD_SYMLINK, CMD_REALPATH, CMD_EXTENDED, SFTP_OP_UNSUPPORTED
_hash_class = {
'sha1': SHA,
'md5': MD5,
'sha1': sha1,
'md5': md5,
}
@ -281,7 +281,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
# don't try to read more than about 64KB at a time
chunklen = min(blocklen, 65536)
count = 0
hash_obj = alg.new()
hash_obj = alg()
while count < blocklen:
data = f.read(offset, chunklen)
if not isinstance(data, bytes_types):

View File

@ -25,6 +25,7 @@ import sys
import threading
import time
import weakref
from hashlib import md5, sha1
import paramiko
from paramiko import util
@ -59,7 +60,6 @@ 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:
from Crypto.Util import Counter
except ImportError:
@ -107,10 +107,10 @@ class Transport (threading.Thread):
}
_mac_info = {
'hmac-sha1': {'class': SHA, 'size': 20},
'hmac-sha1-96': {'class': SHA, 'size': 12},
'hmac-md5': {'class': MD5, 'size': 16},
'hmac-md5-96': {'class': MD5, 'size': 12},
'hmac-sha1': {'class': sha1, 'size': 20},
'hmac-sha1-96': {'class': sha1, 'size': 12},
'hmac-md5': {'class': md5, 'size': 16},
'hmac-md5-96': {'class': md5, 'size': 12},
}
_key_info = {
@ -1338,13 +1338,13 @@ class Transport (threading.Thread):
m.add_bytes(self.H)
m.add_byte(b(id))
m.add_bytes(self.session_id)
out = sofar = SHA.new(m.asbytes()).digest()
out = sofar = sha1(m.asbytes()).digest()
while len(out) < nbytes:
m = Message()
m.add_mpint(self.K)
m.add_bytes(self.H)
m.add_bytes(sofar)
digest = SHA.new(m.asbytes()).digest()
digest = sha1(m.asbytes()).digest()
out += digest
sofar += digest
return out[:nbytes]
@ -1719,9 +1719,9 @@ class Transport (threading.Thread):
# initial mac keys are done in the hash's natural size (not the potentially truncated
# transmission size)
if self.server_mode:
mac_key = self._compute_key('E', mac_engine.digest_size)
mac_key = self._compute_key('E', mac_engine().digest_size)
else:
mac_key = self._compute_key('F', mac_engine.digest_size)
mac_key = self._compute_key('F', mac_engine().digest_size)
self.packetizer.set_inbound_cipher(engine, block_size, mac_engine, mac_size, mac_key)
compress_in = self._compression_info[self.remote_compression][1]
if (compress_in is not None) and ((self.remote_compression != 'zlib@openssh.com') or self.authenticated):
@ -1746,9 +1746,9 @@ class Transport (threading.Thread):
# initial mac keys are done in the hash's natural size (not the potentially truncated
# transmission size)
if self.server_mode:
mac_key = self._compute_key('F', mac_engine.digest_size)
mac_key = self._compute_key('F', mac_engine().digest_size)
else:
mac_key = self._compute_key('E', mac_engine.digest_size)
mac_key = self._compute_key('E', mac_engine().digest_size)
sdctr = self.local_cipher.endswith('-ctr')
self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key, sdctr)
compress_out = self._compression_info[self.local_compression][0]

View File

@ -143,15 +143,14 @@ def tb_strings():
return ''.join(traceback.format_exception(*sys.exc_info())).split('\n')
def generate_key_bytes(hashclass, salt, key, nbytes):
def generate_key_bytes(hash_alg, salt, key, nbytes):
"""
Given a password, passphrase, or other human-source key, scramble it
through a secure hash into some keyworthy bytes. This specific algorithm
is used for encrypting/decrypting private key files.
:param class hashclass:
class from `Crypto.Hash` that can be used as a secure hashing function
(like ``MD5`` or ``SHA``).
:param function hash_alg: A function which creates a new hash object, such
as ``hashlib.sha256``.
:param salt: data to salt the hash with.
:type salt: byte string
:param str key: human-entered password or passphrase.
@ -163,7 +162,7 @@ def generate_key_bytes(hashclass, salt, key, nbytes):
if len(salt) > 8:
salt = salt[:8]
while nbytes > 0:
hash_obj = hashclass.new()
hash_obj = hash_alg()
if len(digest) > 0:
hash_obj.update(digest)
hash_obj.update(b(key))

View File

@ -21,9 +21,12 @@ Some unit tests for the ssh2 protocol in Transport.
"""
import unittest
from hashlib import sha1
from tests.loop import LoopSocket
from Crypto.Cipher import AES
from Crypto.Hash import SHA
from paramiko import Message, Packetizer, util
from paramiko.common import byte_chr, zero_byte
@ -41,7 +44,7 @@ class PacketizerTest (unittest.TestCase):
p.set_log(util.get_logger('paramiko.transport'))
p.set_hexdump(True)
cipher = AES.new(zero_byte * 16, AES.MODE_CBC, x55 * 16)
p.set_outbound_cipher(cipher, 16, SHA, 12, x1f * 20)
p.set_outbound_cipher(cipher, 16, sha1, 12, x1f * 20)
# message has to be at least 16 bytes long, so we'll have at least one
# block of data encrypted that contains zero random padding bytes
@ -64,7 +67,7 @@ class PacketizerTest (unittest.TestCase):
p.set_log(util.get_logger('paramiko.transport'))
p.set_hexdump(True)
cipher = AES.new(zero_byte * 16, AES.MODE_CBC, x55 * 16)
p.set_inbound_cipher(cipher, 16, SHA, 12, x1f * 20)
p.set_inbound_cipher(cipher, 16, sha1, 12, x1f * 20)
wsock.send(b'\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0\x90\xd2\x16\x56\x0d\x71\x73\x61\x38\x7c\x4c\x3d\xfb\x97\x7d\xe2\x6e\x03\xb1\xa0\xc2\x1c\xd6\x41\x41\x4c\xb4\x59')
cmd, m = p.read_message()
self.assertEqual(100, cmd)

View File

@ -20,11 +20,14 @@
Some unit tests for public/private key objects.
"""
from binascii import hexlify
import unittest
from binascii import hexlify
from hashlib import md5
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
@ -90,8 +93,7 @@ class KeyTest (unittest.TestCase):
pass
def test_1_generate_key_bytes(self):
from Crypto.Hash import MD5
key = util.generate_key_bytes(MD5, x1234, 'happy birthday', 30)
key = util.generate_key_bytes(md5, x1234, 'happy birthday', 30)
exp = b'\x61\xE1\xF2\x72\xF4\xC1\xC4\x56\x15\x86\xBD\x32\x24\x98\xC0\xE9\x24\x67\x27\x80\xF4\x7B\xB3\x7D\xDA\x7D\x54\x01\x9E\x64'
self.assertEqual(exp, key)

View File

@ -23,7 +23,8 @@ Some unit tests for utility functions.
from binascii import hexlify
import errno
import os
from Crypto.Hash import SHA
from hashlib import sha1
import paramiko.util
from paramiko.util import lookup_ssh_host_config as host_config
from paramiko.py3compat import StringIO, byte_ord
@ -136,7 +137,7 @@ class UtilTest(ParamikoTest):
)
def test_4_generate_key_bytes(self):
x = paramiko.util.generate_key_bytes(SHA, b'ABCDEFGH', 'This is my secret passphrase.', 64)
x = paramiko.util.generate_key_bytes(sha1, b'ABCDEFGH', 'This is my secret passphrase.', 64)
hex = ''.join(['%02x' % byte_ord(c) for c in x])
self.assertEqual(hex, '9110e2f6793b69363e58173e9436b13a5a4b339005741d5c680e505f57d871347b4239f14fb5c46e857d5e100424873ba849ac699cea98d729e57b3e84378e8b')