如何列出静态链接的python版本中可用的所有openssl密码?

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' ) 

我想知道这是如何影响实际的“有序的SSL密码偏好列表”,当在Windows上使用我的python安装来build立SSL / TLS连接时,会被使用。

例如,要弄清楚一个密码列表扩展到的“有序SSL密码优先列表”,我通常会使用openssl ciphers命令行(参见手册页 ),例如openssl v1.0.1k我可以看到那个默认的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! 

这在Python中dynamic加载openssl ciphers使用的OpenSSL库的Linux上效果很好:

 $ 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被用来轻松地构build两个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 

但是,即使我可以find并下载1.0.1h和1.0.1j版本的openssl命令行版本,我也不能确定它们是用与Python中内置的lib相同的选项编译的, 页面我们知道

一些编译版本的OpenSSL可能不包括这里列出的所有密码,因为在编译时排除了一些密码。

那么,有没有办法让python的ssl模块给我输出类似于openssl ciphers -v命令的输出?

Solutions Collecting From Web of "如何列出静态链接的python版本中可用的所有openssl密码?"

你可能想看看openssl cipher的源代码https://github.com/openssl/openssl/blob/master/apps/ciphers.c

关键的步骤似乎是:

  1. meth = SSLv23_server_method();
  2. ctx = SSL_CTX_new(meth);
  3. SSL_CTX_set_cipher_list(ctx, ciphers) ,而ciphers是你的字符串
  4. ssl = SSL_new(ctx);
  5. sk = SSL_get1_supported_ciphers(ssl);
  6. for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) { print SSL_CIPHER_get_name(sk_SSL_CIPHER_value(sk, i)); }

SSL_CTX_set_cipher_list函数在Python 3.4中用于上下文的_ssl的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() ,不幸的是,它并没有在Python的_ssl.c 。 您可以得到的最接近的是SSLSocket实例的shared_ciphers()方法。 (当前)的实现是

 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实现非常相似,并且返回一个Python列表的密码,与ciphers.c循环的顺序相同。

继续上面的sslsock = SSLSocket(...)示例,在套接字连接之前,不能调用sslsock.shared_ciphers() 。 否则,Python的_ssl模块不会创建读取密码所需的低级别OpenSSL SSL对象。 这与ciphers.c的实现不同,后者创建低级别的SSL对象而不需要连接。

这是我得到了多少,我希望有所帮助,也许你可以根据这些调查结果找出你需要的。

Jan-Philip Gehrcke的回答要求一个尚未发布的python版本是有用的(参见注释),这使得回答有关老版本python的问题变得不实际。 但是这段话激励了我:

…在套接字连接之前,您不能调用sslsock.shared_ciphers()。 否则,Python的_ssl模块不会创建读取密码所需的低级别OpenSSL SSL对象。

这让我想到了一个可能的解决方案。 所有在同一个python程序:

  • 创建一个接受任何密码的服务器套接字( ciphers='ALL:aNULL:eNULL' )。
  • 连接到服务器套接字与客户端套接字配置与我们要检查的密码列表(说'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2'如果我们要测试默认从Python 2.7.8 )
  • 建立连接后,检查客户端选择的密码,然后打印出来,例如'AES256-GCM-SHA384' 。 客户端将从其配置的密码列表中选择与服务器提供的密码匹配的最高优先级密码。 服务器接受任何密码,并运行在相同的OpenSSL库的python程序,所以服务器的列表保证是客户端列表的超集。 因此,使用的密码必须是提供给客户端套接字的扩展列表中的最高优先级。 万岁。
  • 现在重复一次,再次连接到服务器套接字, 但是这次排除了在前一轮中选择的密码,通过将它的否定附加到客户端套接字的密码列表,例如'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!AES256-GCM-SHA384'
  • 重复,直到SSL握手失败,因为我们已经用完了密码。

这里是代码(也可作为github的要点 ):

 """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("listning 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) 

我认为这回答了我的问题。 除非有人可以看到这种方法的问题?