patch for ARC4 cipher support, and CTR block chaining, from denis bernard.
This commit is contained in:
parent
a0313a47e4
commit
ac42ba88d7
|
@ -50,8 +50,12 @@ from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelE
|
||||||
# i believe this on the standards track.
|
# i believe this on the standards track.
|
||||||
# PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
|
# PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
|
||||||
# http://nitace.bsd.uchicago.edu:8080/hashtar
|
# http://nitace.bsd.uchicago.edu:8080/hashtar
|
||||||
from Crypto.Cipher import Blowfish, AES, DES3
|
from Crypto.Cipher import Blowfish, AES, DES3, ARC4
|
||||||
from Crypto.Hash import SHA, MD5
|
from Crypto.Hash import SHA, MD5
|
||||||
|
try:
|
||||||
|
from Crypto.Util import Counter
|
||||||
|
except ImportError:
|
||||||
|
from paramiko.util import Counter
|
||||||
|
|
||||||
|
|
||||||
# for thread cleanup
|
# for thread cleanup
|
||||||
|
@ -196,17 +200,22 @@ class Transport (threading.Thread):
|
||||||
_PROTO_ID = '2.0'
|
_PROTO_ID = '2.0'
|
||||||
_CLIENT_ID = 'paramiko_1.7.4'
|
_CLIENT_ID = 'paramiko_1.7.4'
|
||||||
|
|
||||||
_preferred_ciphers = ( 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' )
|
_preferred_ciphers = ( 'aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc',
|
||||||
|
'arcfour128', 'arcfour256' )
|
||||||
_preferred_macs = ( 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' )
|
_preferred_macs = ( 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' )
|
||||||
_preferred_keys = ( 'ssh-rsa', 'ssh-dss' )
|
_preferred_keys = ( 'ssh-rsa', 'ssh-dss' )
|
||||||
_preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' )
|
_preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' )
|
||||||
_preferred_compression = ( 'none', )
|
_preferred_compression = ( 'none', )
|
||||||
|
|
||||||
_cipher_info = {
|
_cipher_info = {
|
||||||
|
'aes128-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16 },
|
||||||
|
'aes256-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32 },
|
||||||
'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
|
'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
|
||||||
'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 },
|
'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 },
|
||||||
'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 },
|
'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 },
|
||||||
'3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24 },
|
'3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24 },
|
||||||
|
'arcfour128': { 'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 16 },
|
||||||
|
'arcfour256': { 'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
_mac_info = {
|
_mac_info = {
|
||||||
|
@ -1447,7 +1456,19 @@ class Transport (threading.Thread):
|
||||||
def _get_cipher(self, name, key, iv):
|
def _get_cipher(self, name, key, iv):
|
||||||
if name not in self._cipher_info:
|
if name not in self._cipher_info:
|
||||||
raise SSHException('Unknown client cipher ' + name)
|
raise SSHException('Unknown client cipher ' + name)
|
||||||
return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv)
|
if name in ('arcfour128', 'arcfour256'):
|
||||||
|
# arcfour cipher
|
||||||
|
cipher = self._cipher_info[name]['class'].new(key)
|
||||||
|
# as per RFC 4345, the first 1536 bytes of keystream
|
||||||
|
# generated by the cipher MUST be discarded
|
||||||
|
cipher.encrypt(" " * 1536)
|
||||||
|
return cipher
|
||||||
|
elif name.endswith("-ctr"):
|
||||||
|
# CTR modes, we need a counter
|
||||||
|
counter = Counter.new(nbits=self._cipher_info[name]['block-size'] * 8, initial_value=util.inflate_long(iv, True))
|
||||||
|
return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv, counter)
|
||||||
|
else:
|
||||||
|
return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv)
|
||||||
|
|
||||||
def _set_x11_handler(self, handler):
|
def _set_x11_handler(self, handler):
|
||||||
# only called if a channel has turned on x11 forwarding
|
# only called if a channel has turned on x11 forwarding
|
||||||
|
|
|
@ -22,6 +22,7 @@ Useful functions used by the rest of paramiko.
|
||||||
|
|
||||||
from __future__ import generators
|
from __future__ import generators
|
||||||
|
|
||||||
|
import array
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
@ -186,10 +187,10 @@ def load_host_keys(filename):
|
||||||
return a compound dict of C{hostname -> keytype ->} L{PKey <paramiko.pkey.PKey>}.
|
return a compound dict of C{hostname -> keytype ->} L{PKey <paramiko.pkey.PKey>}.
|
||||||
The hostname may be an IP address or DNS name. The keytype will be either
|
The hostname may be an IP address or DNS name. The keytype will be either
|
||||||
C{"ssh-rsa"} or C{"ssh-dss"}.
|
C{"ssh-rsa"} or C{"ssh-dss"}.
|
||||||
|
|
||||||
This type of file unfortunately doesn't exist on Windows, but on posix,
|
This type of file unfortunately doesn't exist on Windows, but on posix,
|
||||||
it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}.
|
it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}.
|
||||||
|
|
||||||
Since 1.5.3, this is just a wrapper around L{HostKeys}.
|
Since 1.5.3, this is just a wrapper around L{HostKeys}.
|
||||||
|
|
||||||
@param filename: name of the file to read host keys from
|
@param filename: name of the file to read host keys from
|
||||||
|
@ -270,3 +271,32 @@ def get_logger(name):
|
||||||
return l
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
class Counter (object):
|
||||||
|
"""Stateful counter for CTR mode crypto"""
|
||||||
|
def __init__(self, nbits, initial_value=1L, overflow=0L):
|
||||||
|
self.blocksize = nbits / 8
|
||||||
|
self.overflow = overflow
|
||||||
|
# start with value - 1 so we don't have to store intermediate values when counting
|
||||||
|
# could the iv be 0?
|
||||||
|
if initial_value == 0:
|
||||||
|
self.value = array.array('c', '\xFF' * self.blocksize)
|
||||||
|
else:
|
||||||
|
x = deflate_long(initial_value - 1, add_sign_padding=False)
|
||||||
|
self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x)
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""Increament the counter and return the new value"""
|
||||||
|
i = self.blocksize - 1
|
||||||
|
while i > -1:
|
||||||
|
c = self.value[i] = chr((ord(self.value[i]) + 1) % 256)
|
||||||
|
if c != '\x00':
|
||||||
|
return self.value.tostring()
|
||||||
|
i -= 1
|
||||||
|
# counter reset
|
||||||
|
x = deflate_long(self.overflow, add_sign_padding=False)
|
||||||
|
self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x)
|
||||||
|
return self.value.tostring()
|
||||||
|
|
||||||
|
def new(cls, nbits, initial_value=1L, overflow=0L):
|
||||||
|
return cls(nbits, initial_value=initial_value, overflow=overflow)
|
||||||
|
new = classmethod(new)
|
||||||
|
|
Loading…
Reference in New Issue