为 SSL 套接字启用哪些密码套件?

Posted

技术标签:

【中文标题】为 SSL 套接字启用哪些密码套件?【英文标题】:Which Cipher Suites to enable for SSL Socket? 【发布时间】:2010-11-05 11:21:57 【问题描述】:

我正在使用 Java 的 SSLSocket 来保护客户端和服务器程序之间的通信。服务器程序还提供来自网络浏览器的 HTTPS 请求。

根据第 371 页的“Beginning Cryptography with Java”,您应该始终在您的 SSLSocket/SSLServerSocket 上调用 setEnabledCipherSuites,以确保最终协商的密码套件对于您的目的来说足够强大。

话虽如此,调用我的SSLSocketFactorygetDefaultCipherSuites 方法会产生一些180 选项。这些选项的范围从TLS_RSA_WITH_AES_256_CBC_SHA(我认为相当安全)到SSL_RSA_WITH_RC4_128_MD5(不太确定这是否安全,鉴于MD5 的当前状态)到SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA(不完全确定它的作用)。

将套接字限制到的密码套件的合理列表是什么?

请注意,客户端和服务器可以访问Bouncy Castle 服务提供者,并且它们可能安装了也可能没有安装无限的加密策略文件。

【问题讨论】:

TLS_RSA_WITH_AES_256_CBC_SHA ... - 您可能应该避免使用 RSA 密钥传输方案(或将它们放在广告列表的底部)。相反,支持临时密钥交换,如DHE,以实现前向保密。事实上,TLS 1.3 正在讨论删除它们,因为它们缺少该属性。 SSL_RSA_WITH_RC4_128_MD5 ... - RC4 现在是问题孩子。见On the Security of RC4 in TLS and WPA。攻击者可能无法在网络的 2MSL 时间窗口中伪造HMAC-MD5 签名。然而,攻击者可以统计地关联密码流中的比特。 (而且 MD5 不适合长期使用,例如证书和数字签名)。 SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA ... - 烧掉它。像瘟疫一样避免它。 【参考方案1】:

下面是我用来执行密码套件和协议的 Java 类。在SSLSocketFactoryEx 之前,当我可以访问它们时,我正在修改SSLSocket 上的属性。 Stack Overflow 上的 Java 人员对此提供了帮助,因此很高兴能够将其发布在这里。

SSLSocketFactoryEx 更喜欢更强大的密码套件(如ECDHEDHE),它忽略了弱密码套件(如RC4MD5)。当 TLS 1.2可用时,它确实必须启用四个 RSA 密钥传输密码才能与 Google 和 Microsoft 互操作。他们是TLS_RSA_WITH_AES_256_CBC_SHA256TLS_RSA_WITH_AES_256_CBC_SHA 和两个朋友。如果可能,您应该删除 TLS_RSA_* 密钥传输方案。

使密码套件列表尽可能小。如果您宣传所有个可用密码(类似于 Flaschen 的列表),那么您的列表将是 80+。这在ClientHello 中占用了 160 个字节,并且可能导致某些设备发生故障,因为它们有一个小的、固定大小的缓冲区来处理ClientHello。损坏的设备包括 F5 和 Ironport。

实际上,一旦首选列表与 Java 支持的密码套件相交,下面代码中的列表就会配对到 10 或 15 个密码套件。例如,这是我在准备连接或 microsoft.com 或 google.com 时获得的列表,其中包含无限制的 JCE 政策:

TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA256 TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA256 TLS_RSA_WITH_AES_128_CBC_SHA

该列表省略了弱/受伤的算法,例如 RC4 和 MD5。如果它们被启用,那么您有时可能会收到Obsolete cryptography warning from Browser。

使用默认 JCE 策略时,列表会更小,因为该策略删除了 AES-256 和其他一些。我认为它大约有 7 个具有受限策略的密码套件。

SSLSocketFactoryEx 类还确保使用 TLS 1.0 及更高版本的协议。 Java 8 之前的 Java 客户端禁用 TLS 1.1 和 1.2。 SSLContext.getInstance("TLS") 也会潜入 SSLv3(即使在 Java 8 中),因此必须采取措施将其删除。

最后,下面的类是 TLS 1.3 感知的,所以当提供者提供它们时它应该可以工作。如果可用,*_CHACHA20_POLY1305 密码套件是首选,因为它们比当前的一些套件快得多,并且具有更好的安全属性。谷歌已经在其服务器上推出了它。我不确定 Oracle 何时会提供它们。 OpenSSL 将为他们提供 OpenSSL 1.0.2 1.1.0.

你可以这样使用它:

URL url = new URL("https://www.google.com:443");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

SSLSocketFactoryEx factory = new SSLSocketFactoryEx();
connection.setSSLSocketFactory(factory);
connection.setRequestProperty("charset", "utf-8");

InputStream input = connection.getInputStream();
InputStreamReader reader = new InputStreamReader(input, "utf-8");
BufferedReader buffer = new BufferedReader(reader);
...

class SSLSocketFactoryEx extends SSLSocketFactory

    public SSLSocketFactoryEx() throws NoSuchAlgorithmException, KeyManagementException
    
        initSSLSocketFactoryEx(null,null,null);
    

    public SSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException
    
        initSSLSocketFactoryEx(km, tm, random);
    

    public SSLSocketFactoryEx(SSLContext ctx) throws NoSuchAlgorithmException, KeyManagementException
    
        initSSLSocketFactoryEx(ctx);
    

    public String[] getDefaultCipherSuites()
    
        return m_ciphers;
    

    public String[] getSupportedCipherSuites()
    
        return m_ciphers;
    

    public String[] getDefaultProtocols()
    
        return m_protocols;
    

    public String[] getSupportedProtocols()
    
        return m_protocols;
    

    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
    
        SSLSocketFactory factory = m_ctx.getSocketFactory();
        SSLSocket ss = (SSLSocket)factory.createSocket(s, host, port, autoClose);

        ss.setEnabledProtocols(m_protocols);
        ss.setEnabledCipherSuites(m_ciphers);

        return ss;
    

    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
    
        SSLSocketFactory factory = m_ctx.getSocketFactory();
        SSLSocket ss = (SSLSocket)factory.createSocket(address, port, localAddress, localPort);

        ss.setEnabledProtocols(m_protocols);
        ss.setEnabledCipherSuites(m_ciphers);

        return ss;
    

    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException
    
        SSLSocketFactory factory = m_ctx.getSocketFactory();
        SSLSocket ss = (SSLSocket)factory.createSocket(host, port, localHost, localPort);

        ss.setEnabledProtocols(m_protocols);
        ss.setEnabledCipherSuites(m_ciphers);

        return ss;
    

    public Socket createSocket(InetAddress host, int port) throws IOException
    
        SSLSocketFactory factory = m_ctx.getSocketFactory();
        SSLSocket ss = (SSLSocket)factory.createSocket(host, port);

        ss.setEnabledProtocols(m_protocols);
        ss.setEnabledCipherSuites(m_ciphers);

        return ss;
    

    public Socket createSocket(String host, int port) throws IOException
    
        SSLSocketFactory factory = m_ctx.getSocketFactory();
        SSLSocket ss = (SSLSocket)factory.createSocket(host, port);

        ss.setEnabledProtocols(m_protocols);
        ss.setEnabledCipherSuites(m_ciphers);

        return ss;
    

    private void initSSLSocketFactoryEx(KeyManager[] km, TrustManager[] tm, SecureRandom random)
    throws NoSuchAlgorithmException, KeyManagementException
    
        m_ctx = SSLContext.getInstance("TLS");
        m_ctx.init(km, tm, random);

        m_protocols = GetProtocolList();
        m_ciphers = GetCipherList();
    

    private void initSSLSocketFactoryEx(SSLContext ctx)
    throws NoSuchAlgorithmException, KeyManagementException
    
        m_ctx = ctx;

        m_protocols = GetProtocolList();
        m_ciphers = GetCipherList();
    

    protected String[] GetProtocolList()
    
        String[] preferredProtocols =  "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" ;
        String[] availableProtocols = null;

        SSLSocket socket = null;

        try
        
            SSLSocketFactory factory = m_ctx.getSocketFactory();
            socket = (SSLSocket)factory.createSocket();

            availableProtocols = socket.getSupportedProtocols();
            Arrays.sort(availableProtocols);
        
        catch(Exception e)
        
            return new String[] "TLSv1" ;
        
        finally
        
            if(socket != null)
                socket.close();
        

        List<String> aa = new ArrayList<String>();
        for(int i = 0; i < preferredProtocols.length; i++)
        
            int idx = Arrays.binarySearch(availableProtocols, preferredProtocols[i]);
            if(idx >= 0)
                aa.add(preferredProtocols[i]);
        

        return aa.toArray(new String[0]);
    

    protected String[] GetCipherList()
    
        String[] preferredCiphers = 

            // *_CHACHA20_POLY1305 are 3x to 4x faster than existing cipher suites.
            //   http://googleonlinesecurity.blogspot.com/2014/04/speeding-up-and-strengthening-https.html
            // Use them if available. Normative names can be found at (TLS spec depends on IPSec spec):
            //   http://tools.ietf.org/html/draft-nir-ipsecme-chacha20-poly1305-01
            //   http://tools.ietf.org/html/draft-mavrogiannopoulos-chacha-tls-02
            "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
            "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
            "TLS_ECDHE_ECDSA_WITH_CHACHA20_SHA",
            "TLS_ECDHE_RSA_WITH_CHACHA20_SHA",

            "TLS_DHE_RSA_WITH_CHACHA20_POLY1305",
            "TLS_RSA_WITH_CHACHA20_POLY1305",
            "TLS_DHE_RSA_WITH_CHACHA20_SHA",
            "TLS_RSA_WITH_CHACHA20_SHA",

            // Done with bleeding edge, back to TLS v1.2 and below
            "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
            "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",

            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
            "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
            "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",

            // TLS v1.0 (with some SSLv3 interop)
            "TLS_DHE_RSA_WITH_AES_256_CBC_SHA384",
            "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
            "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
            "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",

            "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
            "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
            "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA",
            "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA",

            // RSA key transport sucks, but they are needed as a fallback.
            // For example, microsoft.com fails under all versions of TLS
            // if they are not included. If only TLS 1.0 is available at
            // the client, then google.com will fail too. TLS v1.3 is
            // trying to deprecate them, so it will be interesteng to see
            // what happens.
            "TLS_RSA_WITH_AES_256_CBC_SHA256",
            "TLS_RSA_WITH_AES_256_CBC_SHA",
            "TLS_RSA_WITH_AES_128_CBC_SHA256",
            "TLS_RSA_WITH_AES_128_CBC_SHA"
        ;

        String[] availableCiphers = null;

        try
        
            SSLSocketFactory factory = m_ctx.getSocketFactory();
            availableCiphers = factory.getSupportedCipherSuites();
            Arrays.sort(availableCiphers);
        
        catch(Exception e)
        
            return new String[] 
                "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
                "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
                "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
                "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
                "TLS_RSA_WITH_AES_256_CBC_SHA256",
                "TLS_RSA_WITH_AES_256_CBC_SHA",
                "TLS_RSA_WITH_AES_128_CBC_SHA256",
                "TLS_RSA_WITH_AES_128_CBC_SHA",
                "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"
            ;
        

        List<String> aa = new ArrayList<String>();
        for(int i = 0; i < preferredCiphers.length; i++)
        
            int idx = Arrays.binarySearch(availableCiphers, preferredCiphers[i]);
            if(idx >= 0)
                aa.add(preferredCiphers[i]);
        

        aa.add("TLS_EMPTY_RENEGOTIATION_INFO_SCSV");

        return aa.toArray(new String[0]);
    

    private SSLContext m_ctx;

    private String[] m_ciphers;
    private String[] m_protocols;

【讨论】:

感谢分享代码。我有两个问题。首先,我不明白为什么代码包含 2 个 createSocket 函数,每个函数都有不同的参数?因为这个,我得到了一个错误。如果我更改了其中一个的名称,我不知道代码内调用实际上是针对其中的哪一个。其次,我在List&lt;String&gt; aa = new ArrayList&lt;String&gt;(); 中收到一个错误,说“List 类型不是通用的;它不能用参数 . 参数化 另外,这段代码是否使用了 Bouncy Castle 提供程序?因为 Java Crypto 库不包含诸如 CHACHA20 之类的算法,所以我看不到在我的代码中应该在哪里调用 Bouncy Castle 提供程序。另一个问题,我在代码密码中看到:“TLS_ECDHE_ECDSA_WITH_CHACHA20_SHA”和“TLS_ECDHE_RSA_WITH_CHACHA20_SHA”,这是 SHA256 吗?因为我真正需要的是 CHACHA256。 @user2192774 - “为什么代码包含 2 个 createSocket 函数,每个函数都有不同的参数?” - 实际上有 4 或 5 个。因为该类是 Java 的 SSLSocketFactory 的替代品,所以它必须提供原来的所有方法。 @jww : 很好的解释,@user2192774 : “我看不到在我的代码中应该在哪里调用 Bouncy Castle 提供程序...”,我已经导入了 @ 987654345@;在我的类文件中,在项目中使用 jar 并在构建自定义 sslContext 之前包含此行 Security.addProvider(new BouncyCastleProvider()); 这有助于我获得所需的密码,但要排除不必要的密码,以上是很好的解释/示例!! 谢谢你救了我!【参考方案2】:

不要使用任何带有导出功能的东西。由于强密码学的出口限制,那是残废软件。

编辑:改为使用 2009 文档。

2009 NIST recommendation 列出了以下内容,包括 TLS_RSA_WITH_AES_256_CBC_SHA(您提到的):

TLS_RSA_WITH_NULL_SHA(除非您确定不需要任何隐私/机密性,否则请勿使用此功能)。

TLS_RSA_WITH_3DES_EDE_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA
TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
TLS_DH_DSS_WITH_AES_128_CBC_SHA
TLS_DH_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DH_DSS_WITH_AES_256_CBC_SHA
TLS_DH_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_DSS_WITH_AES_256_CBC_SHA
TLS_DHE_RSA_WITH_AES_256_CBC_SHA
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_PSK_WITH_3DES_EDE_CBC_SHA
TLS_PSK_WITH_AES_128_CBC_SHA
TLS_PSK_WITH_AES_256_CBC_SHA
TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA
TLS_DHE_PSK_WITH_AES_128_CBC_SHA
TLS_DHE_PSK_WITH_AES_256_CBC_SHA
TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA
TLS_RSA_PSK_WITH_AES_128_CBC_SHA
TLS_RSA_PSK_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 

【讨论】:

我对 TLS_RSA_WITH_NULL_SHA 有点困惑——这不是指定不加密吗?在这种情况下,这意味着 no 还是 any 加密? Zarkonnen,我认为 TLS_RSA_WITH_NULL_SHA 表示不加密是正确的。页。我链接的草案的第 34 条说 NULL 加密是针对“需要完整性保护但不需要加密的情况”。 我怀疑他需要像 TLS_PSK_WITH_3DES_EDE_CBC_SHA 这样的预共享密钥 (PSK) 密码。 TLS_RSA_WITH_AES_256_CBC_SHA 使用 RSA 密钥传输。他可能应该使用DHEECDHE 进行前向保密。 对密码套件规范的子部分以及您应该避免的内容的一个很好的解释是:blog.eveoh.nl/2014/02/tls-ssl-ciphers-pfs-tomcat

以上是关于为 SSL 套接字启用哪些密码套件?的主要内容,如果未能解决你的问题,请参考以下文章

TLS(SSL)

未启用 PHP 中的套接字传输“ssl”

启用 SSL 的网站无法连接到本地 C# Web 套接字服务器

SSL证书的作用有哪些

ECDHE密码套件在环回连接上失败握手?

详解SSL证书中的SSL握手协议