在python 2.7.8到2.7.9升级中,ssl模块从使用
更改_DEFAULT_CIPHERS = 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'
到
_DEFAULT_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:ECDH+RC4:'
'DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5'
)
我想知道这会如何影响在Windows上使用我的python安装建立SSL / TLS连接时使用的实际“有序SSL密码首选项列表”。
例如,要找出密码列表扩展到的“有序SSL密码首选项列表”,我通常使用openssl ciphers
命令行(参见man page),例如使用openssl v1.0.1 k我可以看到默认的python 2.7.8密码列表扩展为:
$ openssl ciphers -v 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384
ECDHE-RSA-AES256-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA1
ECDHE-ECDSA-AES256-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA1
SRP-DSS-AES-256-CBC-SHA SSLv3 Kx=SRP Au=DSS Enc=AES(256) Mac=SHA1
SRP-RSA-AES-256-CBC-SHA SSLv3 Kx=SRP Au=RSA Enc=AES(256) Mac=SHA1
...
snip!
在Linux上,python动态加载openssl ciphers
使用的同一个OpenSSL库时,这很有用:
$ ldd /usr/lib/python2.7/lib-dynload/_ssl.x86_64-linux-gnu.so | grep libssl
libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007ff75d6bf000)
$ ldd /usr/bin/openssl | grep libssl
libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007fa48f0fe000)
但是,在Windows上,Python构建似乎静态链接OpenSSL库。这意味着openssl ciphers
命令无法帮助我,因为它使用了不同版本的库,它可能支持不同的密码,而不是python中内置的库。
我可以很容易地找到使用哪个版本的OpenSSL来构建两个python版本中的每一个:
$ python-2.7.8/python -c 'import ssl; print ssl.OPENSSL_VERSION'
OpenSSL 1.0.1h 5 Jun 2014
$ python-2.7.9/python -c 'import ssl; print ssl.OPENSSL_VERSION'
OpenSSL 1.0.1j 15 Oct 2014
但即使我可以找到并下载1.0.1h和1.0.1j版本的openssl
命令行的构建,我也不能确定它们是否使用与lib构建的相同选项进行编译进入python,从我们知道的man page
OpenSSL的某些编译版本可能不包括此处列出的所有密码,因为在编译时排除了某些密码。
那么,有没有办法让python的ssl模块给我类似于openssl ciphers -v
命令的输出?
答案 0 :(得分:5)
您可能希望查看https://github.com/openssl/openssl/blob/master/apps/ciphers.c
上的openssl cipher
源代码
关键步骤似乎是:
meth = SSLv23_server_method();
ctx = SSL_CTX_new(meth);
SSL_CTX_set_cipher_list(ctx, ciphers)
,而ciphers
是您的字符串ssl = SSL_new(ctx);
sk = SSL_get1_supported_ciphers(ssl);
for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) {
print SSL_CIPHER_get_name(sk_SSL_CIPHER_value(sk, i));
}
在{3.4}的_ssl&#39; s SSL_CTX_set_cipher_list
方法中调用set_ciphers
函数用于上下文。您可以使用以下方法实现相同目标:
import socket
from ssl import SSLSocket
sslsock = SSLSocket(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
sslsock.context.set_ciphers('DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2')
下一步是调用SSL_get1_supported_ciphers()
,遗憾的是,shared_ciphers()
没有使用SSLSocket
。您可以获得的最接近的是static PyObject *PySSL_shared_ciphers(PySSLSocket *self)
{
[...]
ciphers = sess->ciphers;
res = PyList_New(sk_SSL_CIPHER_num(ciphers));
for (i = 0; i < sk_SSL_CIPHER_num(ciphers); i++) {
PyObject *tup = cipher_to_tuple(sk_SSL_CIPHER_value(ciphers, i));
[...]
PyList_SET_ITEM(res, i, tup);
}
return res;
}
实例的ciphers.c
方法。 (当前)实现是
ciphers.c
也就是说,这个循环与上面的sslsock = SSLSocket(...)
实现非常相似,并返回一个密码的Python列表,其顺序与sslsock.shared_ciphers()
中的循环相同。
继续上面的ciphers.c
示例,在连接套接字之前无法调用{{1}}。否则,Python的_ssl模块不会创建低级OpenSSL SSL对象,这是读取密码所必需的。这与{{1}}中的实现不同,后者在不需要连接的情况下创建低级SSL对象。
这就是我有多远,我希望有所帮助,也许你可以根据这些发现弄清楚你需要什么。
答案 1 :(得分:3)
Jan-Philip Gehrcke's answer需要一个尚未发布的python版本才有用(请参阅注释),这使得回答有关旧版本python的问题变得不切实际。但这一段激励了我:
...在连接套接字之前,你不能调用sslsock.shared_ciphers()。否则,Python的_ssl模块不会创建低级OpenSSL SSL对象,这是读取密码所必需的。
这让我想到了一个可能的解决方案。所有这些都在同一个python程序中:
ciphers='ALL:aNULL:eNULL'
)的服务器套接字。'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'
)'AES256-GCM-SHA384'
。客户端将从其配置的密码列表中选择与服务器提供的密码相匹配的最高优先级密码。服务器接受任何密码,并在同一个具有相同OpenSSL库的python程序中运行,因此服务器列表保证是客户端列表的超集。因此,使用的密码必须是提供给客户端套接字的扩展列表中的最高优先级。万岁。'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!AES256-GCM-SHA384'
)以下是代码(也可用a github gist):
"""An attempt to produce similar output to "openssl ciphers -v", but for
python's built-in ssl.
To answer https://stackoverflow.com/q/28332448/445073
"""
from __future__ import print_function
import argparse
import logging
import multiprocessing
import os
import socket
import ssl
import sys
def server(log_level, queue):
logging.basicConfig(level=log_level)
logger = logging.getLogger("server")
logger.debug("Creating bind socket")
bind_sock = socket.socket()
bind_sock.bind(('127.0.0.1', 0))
bind_sock.listen(5)
bind_addr = bind_sock.getsockname()
logger.debug("Listening on %r", bind_addr)
queue.put(bind_addr)
while True:
logger.debug("Waiting for connection")
conn_sock, fromaddr = bind_sock.accept()
conn_sock = ssl.wrap_socket(conn_sock,
ssl_version=ssl.PROTOCOL_SSLv23,
server_side=True,
certfile="server.crt",
keyfile="server.key",
ciphers="ALL:aNULL:eNULL")
data = conn_sock.read()
logger.debug("Read %r", data)
conn_sock.close()
logger.debug("Done")
def parse_args(argv):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--verbose", "-v", action="store_true",
help="Turn on debug logging")
parser.add_argument("--ciphers", "-c",
default=ssl._DEFAULT_CIPHERS,
help="Cipher list to test. Defaults to this python's "
"default client list")
args = parser.parse_args(argv[1:])
return args
if __name__ == "__main__":
args = parse_args(sys.argv)
log_level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(level=log_level)
logger = logging.getLogger("client")
if not os.path.isfile('server.crt') or not os.path.isfile('server.key'):
print("Must generate server.crt and server.key before running")
print("Try:")
print("openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -nodes -days 365 -subj '/CN=127.0.0.1'")
sys.exit(1)
queue = multiprocessing.Queue()
server_proc = multiprocessing.Process(target=server, args=(log_level, queue))
server_proc.start()
logger.debug("Waiting for server address")
server_addr = queue.get()
chosen_ciphers = []
try:
cipher_list = args.ciphers
while True:
client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_sock = ssl.wrap_socket(client_sock,
ssl_version=ssl.PROTOCOL_SSLv23,
ciphers=cipher_list)
logger.debug("Connecting to %r", server_addr)
client_sock.connect(server_addr)
logger.debug("Connected")
chosen_cipher = client_sock.cipher()
chosen_ciphers.append(chosen_cipher)
client_sock.write("ping")
client_sock.close()
# Exclude the first choice cipher from the list, to see what we get
# next time.
cipher_list += ':!' + chosen_cipher[0]
except ssl.SSLError as err:
if 'handshake failure' in str(err):
logger.debug("Handshake failed - no more ciphers to try")
else:
logger.exception("Something bad happened")
except Exception:
logger.exception("Something bad happened")
else:
server_proc.join()
finally:
server_proc.terminate()
print("Python: {}".format(sys.version))
print("OpenSSL: {}".format(ssl.OPENSSL_VERSION))
print("Expanding cipher list: {}".format(args.ciphers))
print("{} ciphers found:".format(len(chosen_ciphers)))
print("\n".join(repr(cipher) for cipher in chosen_ciphers))
注意它默认是如何测试内置于python的默认密码列表:
day@laptop ~/test
$ python --version
Python 2.7.8
day@laptop ~/test
$ python ssltest.py -h
usage: ssltest.py [-h] [--verbose] [--ciphers CIPHERS]
optional arguments:
-h, --help show this help message and exit
--verbose, -v Turn on debug logging (default: False)
--ciphers CIPHERS, -c CIPHERS
Cipher list to test. Defaults to this python's default
client list (default:
DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2)
所以我们可以很容易地看到默认的客户端密码列表扩展到什么,以及它如何从python 2.7.8更改为2.7.9:
day@laptop ~/test
$ ~/dists/python-2.7.8-with-pywin32-218-x86/python ssltest.py
Python: 2.7.8 (default, Jun 30 2014, 16:03:49) [MSC v.1500 32 bit (Intel)]
OpenSSL: OpenSSL 1.0.1h 5 Jun 2014
Expanding cipher list: DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2
12 ciphers found:
('AES256-GCM-SHA384', 'TLSv1/SSLv3', 256)
('AES256-SHA256', 'TLSv1/SSLv3', 256)
('AES256-SHA', 'TLSv1/SSLv3', 256)
('CAMELLIA256-SHA', 'TLSv1/SSLv3', 256)
('DES-CBC3-SHA', 'TLSv1/SSLv3', 168)
('AES128-GCM-SHA256', 'TLSv1/SSLv3', 128)
('AES128-SHA256', 'TLSv1/SSLv3', 128)
('AES128-SHA', 'TLSv1/SSLv3', 128)
('SEED-SHA', 'TLSv1/SSLv3', 128)
('CAMELLIA128-SHA', 'TLSv1/SSLv3', 128)
('RC4-SHA', 'TLSv1/SSLv3', 128)
('RC4-MD5', 'TLSv1/SSLv3', 128)
day@laptop ~/test
$ ~/dists/python-2.7.9-with-pywin32-219-x86/python ssltest.py
Python: 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)]
OpenSSL: OpenSSL 1.0.1j 15 Oct 2014
Expanding cipher list: ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:ECDH+RC4:DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5
18 ciphers found:
('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256)
('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128)
('ECDHE-RSA-AES256-SHA384', 'TLSv1/SSLv3', 256)
('ECDHE-RSA-AES256-SHA', 'TLSv1/SSLv3', 256)
('ECDHE-RSA-AES128-SHA256', 'TLSv1/SSLv3', 128)
('ECDHE-RSA-AES128-SHA', 'TLSv1/SSLv3', 128)
('ECDHE-RSA-DES-CBC3-SHA', 'TLSv1/SSLv3', 112)
('AES256-GCM-SHA384', 'TLSv1/SSLv3', 256)
('AES128-GCM-SHA256', 'TLSv1/SSLv3', 128)
('AES256-SHA256', 'TLSv1/SSLv3', 256)
('AES256-SHA', 'TLSv1/SSLv3', 256)
('AES128-SHA256', 'TLSv1/SSLv3', 128)
('AES128-SHA', 'TLSv1/SSLv3', 128)
('CAMELLIA256-SHA', 'TLSv1/SSLv3', 256)
('CAMELLIA128-SHA', 'TLSv1/SSLv3', 128)
('DES-CBC3-SHA', 'TLSv1/SSLv3', 112)
('ECDHE-RSA-RC4-SHA', 'TLSv1/SSLv3', 128)
('RC4-SHA', 'TLSv1/SSLv3', 128)
我认为这回答了我的问题。除非有人能看到这种方法的问题吗?