Hi all,
I know paramiko does not support "custom"/vendor SSH implementations, however, the recent version 2.0.0 seems to be breaking things previously working. I can connect fine using paramiko's 1.17.0 invoke_shell to an Alcatel (Nokia now) ISAM (old, complex and a bit slow devices...).
With 2.0.0 I get the following error, which originates from cryptography
transport.py: starting thread (client mode): 0xcf6bc780
transport.py: Local version/idstring: SSH-2.0-paramiko_2.0.0
transport.py: Remote version/idstring: SSH-2.0-IPSSH-1.12.0
transport.py: Connected (version 2.0, client IPSSH-1.12.0)
transport.py: kex algos:['diffie-hellman-group1-sha1'] server key:['ssh-dss'] client encrypt:['aes128-cbc', 'blowfish-cbc', '3des-cbc', 'des-cbc'] server encrypt:['aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'blowfish-cbc', 'cast128-cbc', '3des-cbc', 'des-cbc', 'des-cbc', 'arcfour'] client mac:['hmac-sha1', 'hmac-sha1-96'] server mac:['hmac-sha1', 'hmac-sha1-96', 'hmac-md5', 'hmac-md5-96'] client compress:['none'] server compress:['none'] client lang:[''] server lang:[''] kex follows?False
transport.py: Kex agreed: diffie-hellman-group1-sha1
transport.py: Cipher agreed: aes128-cbc
transport.py: MAC agreed: local=hmac-sha1-96, remote=hmac-md5
transport.py: Compression agreed: none
transport.py: Unknown exception: p must be exactly 1024, 2048, or 3072 bits long
transport.py: Traceback (most recent call last):
transport.py: File ".../site-packages/paramiko/transport.py", line 1757, in run
transport.py: self.kex_engine.parse_next(ptype, m)
transport.py: File ".../site-packages/paramiko/kex_group1.py", line 75, in parse_next
transport.py: return self._parse_kexdh_reply(m)
transport.py: File ".../site-packages/paramiko/kex_group1.py", line 111, in _parse_kexdh_reply
transport.py: self.transport._verify_key(host_key, sig)
transport.py: File ".../site-packages/paramiko/transport.py", line 1605, in _verify_key
transport.py: if not key.verify_ssh_sig(self.H, Message(sig)):
transport.py: File ".../site-packages/paramiko/dsskey.py", line 153, in verify_ssh_sig
transport.py: ).public_key(backend=default_backend())
transport.py: File ".../site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 187, in public_key
transport.py: return backend.load_dsa_public_numbers(self)
transport.py: File ".../site-packages/cryptography/hazmat/backends/multibackend.py", line 198, in load_dsa_public_numbers
transport.py: return b.load_dsa_public_numbers(numbers)
transport.py: File ".../site-packages/cryptography/hazmat/backends/openssl/backend.py", line 656, in load_dsa_public_numbers
transport.py: dsa._check_dsa_parameters(numbers.parameter_numbers)
transport.py: File ".../site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 120, in _check_dsa_parameters
transport.py: raise ValueError("p must be exactly 1024, 2048, or 3072 bits long")
transport.py: ValueError: p must be exactly 1024, 2048, or 3072 bits long
Let me know if this belongs to cryptography rather than paramiko and I will move it
Cheers,
Andreas
I'm having the same issue with Draytek routers
cc @alex / @reaperhulk from PyCA (p.s. do you guys have a catchall group like Graphite's @graphite-committers? >_>)
Are you able to modify paramiko/dsskey.py:verify_ssh_sig to print the locals so we can see more info about the public key.
Not a permanent fix but a workaround for now. I was attempting to use a Sentry Switched CDU from Paramiko and hit the same error. Added code to cryptography/hazmat/primitives/asymmetric/dsa.py that allowed me to find out that the Sentry CDU was using a 641 bit p dsa key. Commented out the few lines in _check_dsa_parameters that check parameters.p's bit_length and it worked fine. For anyone needing to make this work and not concerned with the security aspect of it, you can add this code to your script to pass these insecure server keys.
from cryptography.hazmat.primitives.asymmetric import dsa
def _override_check_dsa_parameters(parameters):
"""Override check_dsa_parameters from cryptography's dsa.py
Allows for shorter or longer parameters.p to be returned from the server's host key. This is a
HORRIBLE hack and a security risk, please remove if possible!
"""
# if utils.bit_length(parameters.p) not in [1024, 2048, 3072]:
# raise ValueError("p is {}, must be exactly 1024, 2048, or 3072 bits long".format(utils.bit_length(parameters.p)))
if crypto_utils.bit_length(parameters.q) not in [160, 256]:
raise ValueError("q must be exactly 160 or 256 bits long")
if not (1 < parameters.g < parameters.p):
raise ValueError("g, p don't satisfy 1 < g < p.")
dsa._check_dsa_parameters = _override_check_dsa_parameters
Hello, I've been using netmiko to manage some Alcatel Lucent AOS switches I found anything older then AOS version 6.7 requires I include the work around provided by Natrinicle with one correction. I had to use
if dsa.utils.bit_length(parameters.q) not in [160, 256]:
raise ValueError("q must be exactly 160 or 256 bits long")
I've run into the same problem with another vendor (Actelis) using 768 bits. I was able to use the workaround by @Natrinicle as well.
Gentleman,
Sorry for posting it here, Not sure if there is a better place.
We are running a python2 program using paramiko to upload files via sftp to bank which works fine.
We are in the process of converting all to python3. We updated it, but now we are getting below error. I can't figure out what parameters we need to specify to get this fixed or bypass this check. Regardless of what the cert is, I think there is an issue with paramiko for python3 where something has changes and no longer works for "some conditions" If there is a parameter on the connection I need to set to fix this? or make other changes please let me know. :
File "/home/zzzz/env_py3/lib/python3.4/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 132, in _check_dsa_parameters
raise ValueError("p must be exactly 1024, 2048, or 3072 bits long")
ValueError: p must be exactly 1024, 2048, or 3072 bits long
PYTHON3 - setup
pip show paramiko
Name: paramiko
Version: 2.2.1
Summary: SSH2 protocol library
Home-page: https://github.com/paramiko/paramiko/
Author: Jeff Forcier
Author-email: [email protected]
License: LGPL
Location: /home/zzzz/env_py3/lib/python3.4/site-packages
Requires: pynacl, pyasn1, bcrypt, cryptography
(env_py3)lucas@myserver:/home/zzz/ach-test/positivepay$ pip show cryptography
Name: cryptography
Version: 1.9
Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers.
Home-page: https://github.com/pyca/cryptography
Author: The cryptography developers
Author-email: [email protected]
License: BSD or Apache License, Version 2.0
Location: /home/zzzzz/env_py3/lib/python3.4/site-packages
Requires: idna, asn1crypto, six, cffi
Below does not have this problem:
PYTHON2 setup which
pip show paramiko
Name: paramiko
Version: 1.15.2
Location: /home/zzzzz/FIS_positivepay/venv/lib/python2.7/site-packages
Requires: pycrypto, ecdsa
(venv)lucas@myserverzzz:~/positivepay$ pip show cryptography
no results for this package
MY BANK using openssl, but I can't be sure this is the correct command to check what is the length of the cert. Regardless of what the cert is, I think there is an issue with paramiko for python3 where something has changes.
openssl s_client -showcerts -connect sftp.mybankzzz.com:22
CONNECTED(00000003)
139823415084688:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:782:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 289 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1518194186
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
Reference to other related comments:
https://github.com/paramiko/paramiko/issues/909
In my comment above there is an override for the dsa._check_dsa_parameters method. In the commented out section, you can see that I've changed what is raised. You could use that to determine what the size of p is.
I also noticed that you have very different versions of Paramiko between Python 2 and 3 and that your Python 2 doesn't seem to show Cryptography via Pip. Can you confirm versions of Cryptography as well as attempt using the latest Paramiko in Python2 to see if you start getting the same error?
Hello,
The python2 was written a long time ago, so it probably would get the same error in current python2 version.
Just to confirm:
You are saying I should add: below right above the pysft.Connect()
from cryptography.hazmat.primitives.asymmetric import dsa
def _override_check_dsa_parameters(parameters):
"""Override check_dsa_parameters from cryptography's dsa.py
Allows for shorter or longer parameters.p to be returned from the server's host key. This is a
HORRIBLE hack and a security risk, please remove if possible!
"""
# if utils.bit_length(parameters.p) not in [1024, 2048, 3072]:
# raise ValueError("p is {}, must be exactly 1024, 2048, or 3072 bits long".format(utils.bit_length(parameters.p)))
if crypto_utils.bit_length(parameters.q) not in [160, 256]:
raise ValueError("q must be exactly 160 or 256 bits long")
if not (1 < parameters.g < parameters.p):
raise ValueError("g, p don't satisfy 1 < g < p.")
dsa._check_dsa_parameters = _override_check_dsa_parameters
When I do that I get:
Traceback (most recent call last):
File "upload.py", line 62, in <module>
s=pysftp.Connection(host='sftp.zzzz.com',username="zzzz",password='zzzz')
File "/home/zzz/env_py3/lib/python3.4/site-packages/pysftp/__init__.py", line 143, in __init__
self._transport.connect(**self._tconnect)
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/transport.py", line 1123, in connect
self.start_client()
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/transport.py", line 510, in start_client
raise e
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/transport.py", line 1839, in run
self.kex_engine.parse_next(ptype, m)
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/kex_gex.py", line 91, in parse_next
return self._parse_kexdh_gex_reply(m)
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/kex_gex.py", line 260, in _parse_kexdh_gex_reply
self.transport._verify_key(host_key, sig)
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/transport.py", line 1699, in _verify_key
if not key.verify_ssh_sig(self.H, Message(sig)):
File "/home/zzz/env_py3/lib/python3.4/site-packages/paramiko/dsskey.py", line 148, in verify_ssh_sig
).public_key(backend=default_backend())
File "/home/zzz/env_py3/lib/python3.4/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 206, in public_key
return backend.load_dsa_public_numbers(self)
File "/home/zzz/env_py3/lib/python3.4/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 607, in load_dsa_public_numbers
dsa._check_dsa_parameters(numbers.parameter_numbers)
File "upload.py", line 53, in _override_check_dsa_parameters
if crypto_utils.bit_length(parameters.q) not in [160, 256]:
NameError: name 'crypto_utils' is not defined
Which package I need to import from to get ?
from cryptography import utils as crypto_utils
Thank you for your help!
For the google reference:
If you have below error after you did above:
paramiko.ssh_exception.SSHException: No hostkey for host sftp.zzzz.com found.
Exception ignored in: <bound method Connection.__del__ of <pysftp.Connection object at 0x7ff67b997d68>>
Traceback (most recent call last):
File "/home/zzz/env_py3/lib/python3.4/site-packages/pysftp/__init__.py", line 1013, in __del__
self.close()
File "/home/zzz/env_py3/lib/python3.4/site-packages/pysftp/__init__.py", line 784, in close
if self._sftp_live:
AttributeError: 'Connection' object has no attribute '_sftp_live'
Then you need the following :
##SFTP does not support live or has no key. Not sure what the error mean.
##To fix it per docs:
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
##You get errors with latest paramiko after you upgraded from python2.
##And you will reachout to host to fix their cert to be more secure.
##https://github.com/paramiko/paramiko/issues/750
#### ValueError: p must be exactly 1024, 2048, or 3072 bits long
from cryptography.hazmat.primitives.asymmetric import dsa
from cryptography import utils as crypto_utils
def _override_check_dsa_parameters(parameters):
"""Override check_dsa_parameters from cryptography's dsa.py
Allows for shorter or longer parameters.p to be returned from the server's host key. This is a
HORRIBLE hack and a security risk, please remove if possible!
"""
# if utils.bit_length(parameters.p) not in [1024, 2048, 3072]:
# raise ValueError("p is {}, must be exactly 1024, 2048, or 3072 bits long".format(utils.bit_length(parameters.p)))
if crypto_utils.bit_length(parameters.q) not in [160, 256]:
raise ValueError("q must be exactly 160 or 256 bits long")
if not (1 < parameters.g < parameters.p):
raise ValueError("g, p don't satisfy 1 < g < p.")
dsa._check_dsa_parameters = _override_check_dsa_parameters
#Then your connection
s=pysftp.Connection(host='sftp.zzzz.com',username="zzzzz",password='zzzz',cnopts=cnopts)
I've followed all of the instructions and right now the value of crypto_utils.bit_length(parameters.q) is 17. I don't know anything about SSH. I'm just trying to automate the log in to a google cloud instance using Python's Paramiko
That means I'm getting the error
ValueError: q must be exactly 160 or 256 bits long
A google cloud instance would definitely be running modern ssh, and 17 is not a plausible value for q, so something else is going very wrong for you.
Paramiko is a Python (2.7, 3.4+) implementation of the SSHv2 protocol
I've followed all of the instructions and right now the value of
crypto_utils.bit_length(parameters.q)is 17. I don't know anything about SSH. I'm just trying to automate the log in to a google cloud instance using Python'sParamiko
Are you still receiving the same issue?
I'm receiving the same error and the p value of 17 when trying to authenticate through Paramiko to an AWS SFTP. I also have an error saying the publickey is not valid. My private key exactly matches the public key on the host server.
try with https://github.com/paramiko/paramiko/pull/1606 (or paramiko-ng)
Hi all,
I also meet the q must be exactly 160, 224, or 256 bits long issue when I use dvc.
I specified the ssh username and password to let dvc access my files on remote server, and bumped into this issue.
It was quite confusing.
It looked like it was break on the connection/auth part, so I decided to follow the related call stack.
At last, I printed the input parameters of self._auth used in client.py.
And I found out that although I already specified the user/pwd, it still collected a private key file (key_filenames) for building up ssh connection, which was not necessary.
Turns out, I had my .ssh/config set up with using a specified private key to all the hosts like below.
Host *
PubkeyAcceptedKeyTypes=+ssh-dss
IdentityFile ~/.ssh/id_rsa_foobar
And that's why the self._auth used it during auth and failed.
After I removed this setting in ssh configuration, everything works.
It is a dumb situation. Write it down just in case if there is someone in the same situation.
In case someone still encounters this, I had the same issues, but solution by @jadore801120 worked for me. I would imagine some other people would run into this as well, since Github advices Mac users to put something like this into the ssh config file, see https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent.
Hello, I've been using netmiko to manage some Alcatel Lucent AOS switches I found anything older then AOS version 6.7 requires I include the work around provided by Natrinicle with one correction. I had to use
if dsa.utils.bit_length(parameters.q) not in [160, 256]:
raise ValueError("q must be exactly 160 or 256 bits long")
Hello @rbthomp
I am working with Alcatel switches as well and I've applied Natrinicle modifications to the file, but it is not working. Could you please share with me the whole section of the code you used? Thank you in advance
Leaving in all the imports just in case there's one that I missed previously. This is the code with the excess stuff removed and replaced with ellipsis. Don't judge it too harshly, it's pretty old stuff. I figured I might need this hack in the future, glad I documented and kept it just in case.
import argparse
from configobj import ConfigObj
from cryptography import utils as crypto_utils
from cryptography.hazmat.primitives.asymmetric import dsa
from datetime import datetime, timedelta
import ipaddress
import json
import logging
import os
import paramiko
from pprint import pformat
import serial
import serial.tools.list_ports
import socket
import sys
import time
...
def _override_check_dsa_parameters(parameters):
"""Override check_dsa_parameters from cryptography's dsa.py.
Sentry PDUs use a 641 bit p in their dsa key. Need to comment out that part of the check since
it's insecure but the only way we can interact with these old beasties. This is a HORRIBLE hack
and I feel bad for doing it! Please remove if Sentry ever updates their server host key.
"""
# if utils.bit_length(parameters.p) not in [1024, 2048, 3072]:
# raise ValueError("p is {}, must be exactly 1024, 2048, or 3072 bits long".format(
# utils.bit_length(parameters.p)))
if crypto_utils.bit_length(parameters.q) not in [160, 256]:
raise ValueError("q must be exactly 160 or 256 bits long")
if not (1 < parameters.g < parameters.p):
raise ValueError("g, p don't satisfy 1 < g < p.")
dsa._check_dsa_parameters = _override_check_dsa_parameters
...
class SentryPDU (object):
"""Wrapper device library for Sentry Switched PDUs.
This class allows end users to manipulate Sentry PDUs without requiring knowledge of
commands required to get the PDUs to do what we want.
"""
def __init__(self, hostname, username="admn", password="", connection_method="ssh"):
...
# Setup ssh stuff
self._ssh = paramiko.SSHClient()
self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self._ssh_shell = None
self._ssh_transport = None
self._ssh_connected = False
self._ssh_connection_attempts = 0
...
def connect(self):
"""Connect via connection method.
Connect via either SSH or serial port depending on how the class was initialized.
"""
if self.connection_method == "ssh":
return self._ssh_connect()
elif self.connection_method == "serial":
return self._serial_connect()
else:
raise ValueError("Unknown connection method")
def _ssh_connect(self):
"""Attempt to connect to Sentry PDU via SSH.
Using the arguments passed in to the init method, attempt to connect to the Sentry PDU
specified.
"""
# Don't try to reconnect if already connected
if self.is_connected():
return True
# Keep a count of how many times we've attempted to connect, bail if we repeatedly aren't
# able to connect. Should prevent infinite loops.
self._ssh_connection_attempts += 1
if not self._ssh_connected and self._ssh_connection_attempts > 5:
raise IOError("Could not connect to {} after 5 attempts".format(self.hostname))
log_message = "{} ssh connection attempt {}".format(self.hostname,
self._ssh_connection_attempts)
logger.debug(log_message)
try:
self._ssh.connect(
hostname=".".join([self.hostname, self.domain]),
username=self.username,
password=self.password,
allow_agent=False,
look_for_keys=False,
compress=True,
timeout=self.read_timeout,
banner_timeout=self.read_timeout,
)
except socket.gaierror:
log_message = "Name or service {} not known".format(self.hostname)
logger.error(log_message)
return False
except socket.error as e:
log_message = "Could not connect to {} - {}".format(self.hostname, str(e))
logger.error(log_message)
return False
except paramiko.ssh_exception.AuthenticationException:
log_message = "SSH authentication failure {}".format(self.hostname)
logger.error(log_message)
return False
except Exception as e:
logger.error(str(e))
return False
self._ssh_transport = self._ssh.get_transport()
self._ssh_shell = self._ssh.invoke_shell()
self._ssh_shell.settimeout(self.read_timeout)
self._ssh_connected = True
# Read in any lines during login to clear the buffer and make sure we're connected to a
# Sentry CDU
outlines = self.read_output()
if any("sentry" in line.lower() and
("cdu" in line.lower() or "pdu" in line.lower()) for line in outlines):
self._fetch_prompt()
return True
raise ValueError("Could not find Sentry PDU on SSH connection")
...
def is_connected(self):
"""Return true if connected via connection_method and False if not.
There are different ways of checking if connected via SSH or serial. Use the correct
method and allow us to see if we are connected to the PDU.
"""
if self.connection_method == "ssh":
if self._ssh_transport:
if self._ssh_transport.is_active():
return True
elif self.connection_method == "serial":
if self._serial.isOpen():
return True
else:
raise ValueError("Unknown connection method")
return False
...
def send_raw(self, raw_data):
"""Wrapper function to send raw lines to connected PDU via connection_method.
This allows us to abstract the read_output a bit more and consolidate down some of the
processing for responses.
"""
assert isinstance(raw_data, str)
if self.connection_method == "ssh":
self._ssh_send_raw(raw_data)
elif self.connection_method == "serial":
self._serial_send_raw(raw_data)
else:
raise ValueError("Unknown connection method")
def _ssh_send_raw(self, raw_data):
self._ssh_shell.send("{}".format(raw_data))
Thanks a lot. That is very useful.
Leaving in all the imports just in case there's one that I missed previously. This is the code with the excess stuff removed and replaced with ellipsis. Don't judge it too harshly, it's pretty old stuff. I figured I might need this hack in the future, glad I documented and kept it just in case.
import argparse from configobj import ConfigObj from cryptography import utils as crypto_utils from cryptography.hazmat.primitives.asymmetric import dsa from datetime import datetime, timedelta import ipaddress import json import logging import os import paramiko from pprint import pformat import serial import serial.tools.list_ports import socket import sys import time ... def _override_check_dsa_parameters(parameters): """Override check_dsa_parameters from cryptography's dsa.py. Sentry PDUs use a 641 bit p in their dsa key. Need to comment out that part of the check since it's insecure but the only way we can interact with these old beasties. This is a HORRIBLE hack and I feel bad for doing it! Please remove if Sentry ever updates their server host key. """ # if utils.bit_length(parameters.p) not in [1024, 2048, 3072]: # raise ValueError("p is {}, must be exactly 1024, 2048, or 3072 bits long".format( # utils.bit_length(parameters.p))) if crypto_utils.bit_length(parameters.q) not in [160, 256]: raise ValueError("q must be exactly 160 or 256 bits long") if not (1 < parameters.g < parameters.p): raise ValueError("g, p don't satisfy 1 < g < p.") dsa._check_dsa_parameters = _override_check_dsa_parameters ... class SentryPDU (object): """Wrapper device library for Sentry Switched PDUs. This class allows end users to manipulate Sentry PDUs without requiring knowledge of commands required to get the PDUs to do what we want. """ def __init__(self, hostname, username="admn", password="", connection_method="ssh"): ... # Setup ssh stuff self._ssh = paramiko.SSHClient() self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self._ssh_shell = None self._ssh_transport = None self._ssh_connected = False self._ssh_connection_attempts = 0 ... def connect(self): """Connect via connection method. Connect via either SSH or serial port depending on how the class was initialized. """ if self.connection_method == "ssh": return self._ssh_connect() elif self.connection_method == "serial": return self._serial_connect() else: raise ValueError("Unknown connection method") def _ssh_connect(self): """Attempt to connect to Sentry PDU via SSH. Using the arguments passed in to the init method, attempt to connect to the Sentry PDU specified. """ # Don't try to reconnect if already connected if self.is_connected(): return True # Keep a count of how many times we've attempted to connect, bail if we repeatedly aren't # able to connect. Should prevent infinite loops. self._ssh_connection_attempts += 1 if not self._ssh_connected and self._ssh_connection_attempts > 5: raise IOError("Could not connect to {} after 5 attempts".format(self.hostname)) log_message = "{} ssh connection attempt {}".format(self.hostname, self._ssh_connection_attempts) logger.debug(log_message) try: self._ssh.connect( hostname=".".join([self.hostname, self.domain]), username=self.username, password=self.password, allow_agent=False, look_for_keys=False, compress=True, timeout=self.read_timeout, banner_timeout=self.read_timeout, ) except socket.gaierror: log_message = "Name or service {} not known".format(self.hostname) logger.error(log_message) return False except socket.error as e: log_message = "Could not connect to {} - {}".format(self.hostname, str(e)) logger.error(log_message) return False except paramiko.ssh_exception.AuthenticationException: log_message = "SSH authentication failure {}".format(self.hostname) logger.error(log_message) return False except Exception as e: logger.error(str(e)) return False self._ssh_transport = self._ssh.get_transport() self._ssh_shell = self._ssh.invoke_shell() self._ssh_shell.settimeout(self.read_timeout) self._ssh_connected = True # Read in any lines during login to clear the buffer and make sure we're connected to a # Sentry CDU outlines = self.read_output() if any("sentry" in line.lower() and ("cdu" in line.lower() or "pdu" in line.lower()) for line in outlines): self._fetch_prompt() return True raise ValueError("Could not find Sentry PDU on SSH connection") ... def is_connected(self): """Return true if connected via connection_method and False if not. There are different ways of checking if connected via SSH or serial. Use the correct method and allow us to see if we are connected to the PDU. """ if self.connection_method == "ssh": if self._ssh_transport: if self._ssh_transport.is_active(): return True elif self.connection_method == "serial": if self._serial.isOpen(): return True else: raise ValueError("Unknown connection method") return False ... def send_raw(self, raw_data): """Wrapper function to send raw lines to connected PDU via connection_method. This allows us to abstract the read_output a bit more and consolidate down some of the processing for responses. """ assert isinstance(raw_data, str) if self.connection_method == "ssh": self._ssh_send_raw(raw_data) elif self.connection_method == "serial": self._serial_send_raw(raw_data) else: raise ValueError("Unknown connection method") def _ssh_send_raw(self, raw_data): self._ssh_shell.send("{}".format(raw_data))
@Natrinicle - I've found this works for some older Ciena devices as well. I am curious, though. What potential security risks come with this? I would only assume the crypto algorithm is just not as secure, even though you are already going over SSH. Any info would be appreciated.
Running into this with some Linux boxes, but it's only happening for certain users. Trying to nail down what keys they're using, key size, etc. We're also using agent forwarding, so I'm sure that complicates things.
Played with specifying certain versions of cryptography and pycryptodome -- saw a different error with one combination, but after we bumped cryptography to 3.0.0, paramiko to 2.7.1, and pycryptodome to 3.9.8, it was the same result:
Unknown exception: q must be exactly 160 or 256 bits long
Traceback (most recent call last):
ValueError: q must be exactly 160 or 256 bits long
Giving @Natrinicle's temporary fix a try next...
I also had this fix in my code-base for some time and needed to updated it since paramiko changed a bit. At the same time I decided to make it a tiny bit more secure using a context manager. It gives you precise control where/when to apply the patch and restores the original implementation on context-exit.
You can then simply wrap any problematic section using:
with unsafe_dsa_parameters():
do_something()
Here's the implementation:
from contextlib import contextmanager
from typing import Generator
from threading import Lock
from cryptography.hazmat.primitives.asymmetric import dsa
#: A threading lock to allow us to safely patch (and "unpatch") DSA param
#: checks
DSAPatchLock = Lock()
@contextmanager
def unsafe_dsa_parameters() -> Generator[None, None, None]:
"""
Context-Manager to temporarily change DSA parameter checks in a thread-safe
manner
"""
# We use a lock because we need to patch a value in the global name-space
# of an imported module. If another thread would use SSH connections during
# the time we have this patched it would suffer from the same security
# flaw. This lock protects us from this happening and we can safely reset
# the value to the initial value when we're done.
#
# We could not use a reentrant lock because that would potentially lose our
# "backup". Considering that reentrant locks are nested, and trusting
# Python memory frames this might still be okay. But I decided to err on
# the side of caution here. Considering we're doing network I/O using a
# normal lock here will certainly not be the performance bottle-neck
with DSAPatchLock:
# pylint: disable=protected-access
backup = dsa._check_dsa_parameters # type: ignore
dsa._check_dsa_parameters = ( # type: ignore
_override_check_dsa_parameters
)
try:
yield
finally:
dsa._check_dsa_parameters = backup # type: ignore
def _override_check_dsa_parameters(parameters): # type: ignore
"""Override check_dsa_parameters from cryptography's dsa.py
Allows for shorter or longer parameters.p to be returned from the server's
host key.
"""
if parameters.p.bit_length() not in [768, 1024, 2048, 3072]:
raise ValueError(
"p is {}, must be exactly 768, 1024, 2048, or 3072 "
"bits long".format(parameters.p.bit_length())
)
if not 1 < parameters.g < parameters.p:
raise ValueError("g, p don't satisfy 1 < g < p.")
I am running into the same issue and I've absolutely no idea why this isn't working. What's even stranger, a key I've created a few days ago works, but a fresh key doens't.
I created an RSA 4096bit key like this:
ssh-keygen -t rsa -b 4096 -C "Docker SSH key" -f id_rsa # run a few days back
When I'm using that one from a few days back (i.e. id_rsa) it works. Now when I'm creating a new RSA key w/ 4096 bits again and when I want to use that one, it fails:
ssh-keygen -t rsa -b 4096 -C "Docker SSH key" -f id_rsa2 # run now
Error when connecting to it:
Traceback (most recent call last):
…
File "/app/.local/lib/python3.9/site-packages/paramiko/client.py", line 435, in connect
self._auth(
File "/app/.local/lib/python3.9/site-packages/paramiko/client.py", line 680, in _auth
self._transport.auth_publickey(username, key)
File "/app/.local/lib/python3.9/site-packages/paramiko/transport.py", line 1580, in auth_publickey
return self.auth_handler.wait_for_response(my_event)
File "/app/.local/lib/python3.9/site-packages/paramiko/auth_handler.py", line 236, in wait_for_response
raise e
File "/app/.local/lib/python3.9/site-packages/paramiko/transport.py", line 2109, in run
handler(self.auth_handler, m)
File "/app/.local/lib/python3.9/site-packages/paramiko/auth_handler.py", line 298, in _parse_service_accept
sig = self.private_key.sign_ssh_data(blob)
File "/app/.local/lib/python3.9/site-packages/paramiko/dsskey.py", line 108, in sign_ssh_data
key = dsa.DSAPrivateNumbers(
File "/app/.local/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 250, in private_key
return backend.load_dsa_private_numbers(self)
File "/app/.local/lib/python3.9/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 852, in load_dsa_private_numbers
dsa._check_dsa_private_numbers(numbers)
File "/app/.local/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 147, in _check_dsa_private_numbers
_check_dsa_parameters(parameters)
File "/app/.local/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.py", line 139, in _check_dsa_parameters
raise ValueError("q must be exactly 160, 224, or 256 bits long")
ValueError: q must be exactly 160, 224, or 256 bits long
When I check the keys they look alike:
app@b71b0a636cf7:/data$ ls -la id_rsa id_rsa2
-rw------- 1 app app 3381 Nov 30 17:59 id_rsa
-rw------- 1 app app 3381 Nov 30 18:02 id_rsa2
app@b71b0a636cf7:/data$ head -1 id_rsa id_rsa2
==> id_rsa <==
-----BEGIN OPENSSH PRIVATE KEY-----
==> id_rsa2 <==
-----BEGIN OPENSSH PRIVATE KEY-----
app@b71b0a636cf7:/data$ ssh-keygen -l -f id_rsa
4096 SHA256:7gM+CkRIQSomgPwCHWy+2OTVMHalAlesNTSgBVjPAGY Docker SSH key (RSA)
app@b71b0a636cf7:/data$ ssh-keygen -l -f id_rsa2
4096 SHA256:Na9aBvkxwD7m82R2Mn5TZOvqQ6cgV8JrDwxNRO86awI Docker SSH key (RSA)
The keys in question are:
Running into this with some Linux boxes, but it's only happening for certain users. Trying to nail down what keys they're using, key size, etc. We're also using agent forwarding, so I'm sure that complicates things.
Played with specifying certain versions of cryptography and pycryptodome -- saw a different error with one combination, but after we bumped cryptography to 3.0.0, paramiko to 2.7.1, and pycryptodome to 3.9.8, it was the same result:
Unknown exception: q must be exactly 160 or 256 bits long Traceback (most recent call last): ValueError: q must be exactly 160 or 256 bits longGiving @Natrinicle's temporary fix a try next...
So in my case, the issue ended up being similar to what someone else reported -- that the user had the following in their ~/.ssh/config file but the hadn't actually generated keys on that host, so it was failing due to keys being referred to that didn't exist:
IdentityFile ~/.ssh/id_rsa
Removing that config line corrected the problem, in our case.
Similar to previous reports - the fix for me was passing allow_agents=False, look_for_keys=False when creating the connection. Seems the client hoovered up something that it choked on.
Most helpful comment
Hi all,
I also meet the
q must be exactly 160, 224, or 256 bits longissue when I use dvc.I specified the ssh username and password to let dvc access my files on remote server, and bumped into this issue.
It was quite confusing.
It looked like it was break on the connection/auth part, so I decided to follow the related call stack.
At last, I printed the input parameters of
self._authused inclient.py.And I found out that although I already specified the user/pwd, it still collected a private key file (
key_filenames) for building up ssh connection, which was not necessary.Turns out, I had my
.ssh/configset up with using a specified private key to all the hosts like below.And that's why the
self._authused it during auth and failed.After I removed this setting in ssh configuration, everything works.
It is a dumb situation. Write it down just in case if there is someone in the same situation.