getCipherSuite() 返回 SSL_NULL_WITH_NULL_NULL

Posted

技术标签:

【中文标题】getCipherSuite() 返回 SSL_NULL_WITH_NULL_NULL【英文标题】:getCipherSuite() returns SSL_NULL_WITH_NULL_NULL 【发布时间】:2014-08-13 23:41:47 【问题描述】:

我正在尝试使用以编程方式生成的自签名证书在 android 上创建 https 服务器。我觉得我很接近,但我仍然无法连接到 https 服务器。当我尝试使用 openssl 连接到服务器时,我得到以下信息:

openssl s_client -connect 192.168.1.97:8888
CONNECTED(00000003)
2895:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:/SourceCache/OpenSSL098/OpenSSL098-50/src/ssl/s23_clnt.c:602:

代码如下:

public class HttpsHello 
    private static String domainName = "localhost";

    static 
        Security.addProvider(new BouncyCastleProvider());
    

    public static void test(String[] args) 

        try 
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(1024);
            KeyPair KPair = keyPairGenerator.generateKeyPair();

            X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();

            int ran = new SecureRandom().nextInt();
            if (ran < 0) ran = ran * -1;

            BigInteger serialNumber = BigInteger.valueOf(ran);

            v3CertGen.setSerialNumber(serialNumber);
            v3CertGen.setIssuerDN(new X509Principal("CN=" + domainName + ", OU=None, O=None L=None, C=None"));
            v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30));
            v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10)));
            v3CertGen.setSubjectDN(new X509Principal("CN=" + domainName + ", OU=None, O=None L=None, C=None"));


            v3CertGen.setPublicKey(KPair.getPublic());
            //   v3CertGen.setSignatureAlgorithm("MD5WithRSAEncryption");

            v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

            X509Certificate pkcert = v3CertGen.generateX509Certificate(KPair.getPrivate());
            //    FileOutputStream fos = new FileOutputStream("/path/to/testCert.cert");
            //  fos.write(pkcert.getEncoded());
            // fos.close();

            ByteArrayInputStream cert = new ByteArrayInputStream(pkcert.getEncoded());

            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(null);
            ks.setCertificateEntry("localhost", pkcert);

            // ks.load(cert,null);
            KeyManagerFactory kmf =
                    KeyManagerFactory.getInstance("X509");
            kmf.init(ks, null);

            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(kmf.getKeyManagers(), null, null);
            SSLServerSocketFactory ssf = sc.getServerSocketFactory();

            SSLServerSocket s
                    = (SSLServerSocket) ssf.createServerSocket(8888);
            s.setEnabledCipherSuites(s.getSupportedCipherSuites());
            //  s.setEnabledCipherSuites(new String[]"SSL_DH_anon_WITH_RC4_128_MD5");
            //  s.setEnabledCipherSuites(new String[]"SHA1WithRSAEncryption");
            System.out.println("Server started:");
            printServerSocketInfo(s);
            // Listening to the port
            SSLSocket c = (SSLSocket) s.accept();
            printSocketInfo(c);
            BufferedWriter w = new BufferedWriter(
                    new OutputStreamWriter(c.getOutputStream()));
            BufferedReader r = new BufferedReader(
                    new InputStreamReader(c.getInputStream()));
            String m = r.readLine();
            w.write("HTTP/1.0 200 OK");
            w.newLine();
            w.write("Content-Type: text/html");
            w.newLine();
            w.newLine();
            w.write("<html><body>Hello world!</body></html>");
            w.newLine();
            w.flush();
            w.close();
            r.close();
            c.close();
         catch (Exception e) 
            e.printStackTrace();
        
    

    private static void printSocketInfo(SSLSocket s) 
        System.out.println("Socket class: " + s.getClass());
        System.out.println("   Remote address = "
                + s.getInetAddress().toString());
        System.out.println("   Remote port = " + s.getPort());
        System.out.println("   Local socket address = "
                + s.getLocalSocketAddress().toString());
        System.out.println("   Local address = "
                + s.getLocalAddress().toString());
        System.out.println("   Local port = " + s.getLocalPort());
        System.out.println("   Need client authentication = "
                + s.getNeedClientAuth());
        SSLSession ss = s.getSession();
        System.out.println("   Cipher suite = " + ss.getCipherSuite());
        System.out.println("   Protocol = " + ss.getProtocol());
    

    private static void printServerSocketInfo(SSLServerSocket s) 
        System.out.println("Server socket class: " + s.getClass());
        System.out.println("   Socker address = "
                + s.getInetAddress().toString());
        System.out.println("   Socker port = "
                + s.getLocalPort());
        System.out.println("   Need client authentication = "
                + s.getNeedClientAuth());
        System.out.println("   Want client authentication = "
                + s.getWantClientAuth());
        System.out.println("   Use client mode = "
                + s.getUseClientMode());
    

谢谢。

编辑:我查看了两个 keytool 生成的密钥库,一个有效,一个无效。一个有效的密钥库在其中有一个 PrivateKeyEntry 的条目,而那个不起作用的密钥库有一个trustedCertEntry。然后我更改了此代码以打印出“localhost”别名的条目,下面是我得到的,我猜问题是它是受信任的证书条目而不是私钥条目。我该如何改变呢?

可信证书条目: [0] 版本:3 序列号:752445443 IssuerDN: CN=localhost,OU=None,O=None L,C=None 开始日期:2014 年 5 月 26 日星期一 09:17:01 CDT 最终日期:2024 年 6 月 22 日星期六 09:17:01 CDT SubjectDN: CN=localhost,OU=None,O=None L,C=None 公钥:RSA公钥 模量:b75870cd29db79f8c015d440a27cc1e81c9dd829268efa2ce48efc596b33e9c60e1d1621e10aba34472b6f7890b16392db021c0358e665b1bf58a426fbc47e7c135da583e4cd6bb9c69668ee4ff1e05b1de8e7f5fb5604044a1087ac0181ba09f61ab5345d9be5d930889b7c328329d0d18cf53f4c5af6bff1f0e488744ea1fb 公共指数:10001 签名算法:SHA1WITHRSA 签名:83df0e761e9df2e61d5354ca58379975e0d97fcd 5201f8904b695d7bdbe08c5dfdfb8bcd6447657c 19740797a66314b2547a45985166c11ebadc16c6 c24b8e1d3c5de83ec1ac2c1c1092c3d06ed33408 4cf2811c5f9dba8a9d3ef0dcb8fef760e4d1d704 8fbb60eaa83eec23426fb9d8589e859a21a5ecce 951901f8e16ab6cd

【问题讨论】:

SSL_NULL_WITH_NULL_NULL 是从哪里来的? "CN=" + domainName 不太对。 IETF 和 CA/B 论坛均不赞成在 CommonName 中放置 DNS 名称。您仍然可以使用它 - 只需放置一个友好的名称,例如“Internet Widgets”。将 DNS 名称放在 SubjectAltNames 中。 ssl空行来自代码中的这一行`System.out.println(" Cipher suite = " + ss.getCipherSuite());` 啊,好的。这看起来像是握手失败的结果;而不是在协商中使用的密码套件。您可以提供服务器的外部 URL 吗?您CERTAIN是否有一个 SSL/TLS 服务器正在监听 8888? 有一个服务器,我用 curl 试过,握手失败。这是在 Android 手机上运行的,但我会看看是否可以在某个时候将一些端口转发给它。 【参考方案1】:
            s.setEnabledCipherSuites(s.getSupportedCipherSuites());

删除此行。

【讨论】:

如果我这样做,那么我会在 s.accept() 行上得到 javax.net.ssl.SSLException: Could not find any key store entries to support the enabled cipher suites. 那么您的密钥生成有问题。无法立即看到它,但我会将它与通过 keytool 生成密钥时发生的情况进行比较。事实上,我不明白你为什么使用keytool。没有人会信任您在此处生成的证书。 这是供安卓应用内部使用的,我有点希望每个用户都有自己的证书。我愿意使用生成的密钥工具,但我也遇到了麻烦,我认为我得到了错误的密钥库版本错误,即使我使用的是每个人都声称有效的充气城堡版本。 好吧,除非您由服务器信任的颁发者签署客户端证书,否则它将无法正常工作。 我有点迷路了。为什么需要客户证书?我只想拥有一个简单的浏览器,并且浏览器所属的同一个应用程序将拥有自己的 https 服务器。这里的整个问题只是关于 https 服务器。【参考方案2】:

握手失败通常意味着没有共享密码套件:

2895:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

SSL_NULL_WITH_NULL_NULL 看起来像 eNULLaNULL。 BouncyCastle 没有加载任何密码吗?

下面是我用于强化SSLSocketFactoryEx 的代码。它仅提供 TLS(getInstance("TLS") 仍将返回 SSLv3 套接字),并且仅提供经过批准的密码套件(由我批准)。仅提供经过批准的密码是不够的 - 该列表必须与可用的密码相交,否则会有例外。有一些备用密码套件可确保在旧服务器(如 Microsoft 提供的服务器)之间共享密码套件。

import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;

import java.io.IOException;

import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

import java.net.Socket;
import java.net.InetAddress;

import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

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;

【讨论】:

Yuor 代码泄漏了 SSL 套接字,正如他已经在 cmets 中对另一个答案所说的那样,使用所有启用的协议仍然会导致问题,进一步限制它们不可能解决它。 感谢@EJP。我对其他问题一无所知(他需要在这里明确说明)。握手警报指示有 NO 共享密码套件。因此,他被提供代码以根据可用的内容设置密码套件的原因。

以上是关于getCipherSuite() 返回 SSL_NULL_WITH_NULL_NULL的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 协程Flow 异步流 ① ( 以异步返回返回多个返回值 | 同步调用返回多个值的弊端 | 尝试在 sequence 中调用挂起函数返回多个返回值 | 协程中调用挂起函数返回集合 )

使用fiddler修改返回值以及返回状态码

Android开发如何点击物理返回键返回上个界面

Javascript:嵌套返回语句,返回内部返回

Android——关于Activity跳转的返回(无返回值和有返回值)——无返回值

JAVA里的函数返回值,可以返回几个?