编辑:我修复了代码。请参阅我的答案以获得解释。
我正在尝试创建一个使用SSLSocket将数据发送到服务器的Android应用程序。证书是自签名的,我正在尝试使用SSLContext在服务器KeyManager和客户端TrustManager中添加它,按照我在其他线程上找到的说明。
但是,当我尝试发送数据时,客户端和服务器都返回异常。
请记住,这是我第一次处理SSL连接。我使用Java keytool创建并签署了'server.crt'证书。
客户端例外
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:390)
at com.android.org.conscrypt.OpenSSLSocketImpl.waitForHandshake(OpenSSLSocketImpl.java:623)
at com.android.org.conscrypt.OpenSSLSocketImpl.getOutputStream(OpenSSLSocketImpl.java:609)
...
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x73c6ac00: Failure in SSL library, usually a protocol error
服务器异常
Exception in thread "main" javax.net.ssl.SSLHandshakeException: no cipher suites in common
...
at java.io.InputStream.read(InputStream.java:101)
FIXED客户端代码(在android上运行)
AssetManager assetManager = getAssets();
InputStream keyStoreInputStream = assetManager.open("keystore.bks");
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null,null);
keyStore.load(keyStoreInputStream, KEY_PASSWORD);
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
SSLSocketFactory sslsocketfactory = sslContext.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("192.168.1.212", 4444);
InputStream inputStream = assetManager.open(MainActivity.FILE_NAME);
OutputStream outputStream = sslsocket.getOutputStream();
int count;
byte[] buffer = new byte[12 * 1024];
while ((count = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, count);
}
FIXED服务器代码(在Windows上运行)
FileInputStream keyStoreInputStream = new FileInputStream(KEYSTORE_PATH);
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
keyStore.load(keyStoreInputStream, KEY_PASSWORD);
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, KEY_PASSWORD);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket sslServerSocket =
(SSLServerSocket) sslServerSocketFactory.createServerSocket(4444);
while (true) {
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
InputStream inputStream = sslSocket.getInputStream();
FileOutputStream outputStream = new FileOutputStream(OUTPUT_PATH,false);
byte[] bytes = new byte[12 * 1024];
int count;
while ((count = inputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, count);
}
}
答案 0 :(得分:1)
似乎问题出在我使用的证书上。 我尝试导入使用keytool创建的'* .crt'文件,然后以编程方式将其添加到密钥库。 即使我仍然不确定之前为什么它不起作用,我通过跳过证书导入并直接导入密钥库(Windows服务器上的'keystore.jks'和Android客户端上的'keystore.bks')来修复此问题。
这就是我创建keystore.jks的方式:
keytool -genkeypair -alias serveralias -keyalg RSA -keysize 4096 -keystore keystore.jks
-keypass changeit -storepass changeit -validity 10950
由于Android上不支持JKS,我制作了密钥库的副本,并将扩展名重命名为.bks。 然后我使用一个名为'KeyStore Explorer'的程序将keystore.bks转换为'BKS-V1'格式,以便在android上运行。
我更新了原始问题以包含工作代码。
答案 1 :(得分:0)
我认为关键信息是服务器端异常:Exception in thread "main" javax.net.ssl.SSLHandshakeException: no cipher suites in common
。您的自签名证书可能没问题;看起来你的密码配置不匹配。您需要确保客户端和服务器都有一个共同的密码套件。 SSLSocket in Android支持的默认套件取决于版本。请参阅release notes for Android 5.0
答案 2 :(得分:0)
出于测试目的,我需要启动HTTPS服务器。由于证书并不重要,我需要创建一个。当我们在java中完成所有工作时,为什么要离开JVM并使用keytool。
这需要BouncyCastle。
static SSLContext createSSLContext() throws Exception {
final KeyStore ks = createSelfSignedKeyStore();
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, new char[0]);
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext;
}
private static KeyStore createSelfSignedKeyStore() throws Exception {
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null);
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
final KeyPair keyPair = keyGen.generateKeyPair();
final Certificate selfSignedCert = generate(InetAddress.getLocalHost().getCanonicalHostName(), keyPair);
ks.setCertificateEntry("alias.cert", selfSignedCert);
ks.setKeyEntry("alias.key", keyPair.getPrivate(), new char[0], new Certificate[] { selfSignedCert });
return ks;
}
private static Certificate generate(final String fqdn, final KeyPair keypair) throws Exception {
final Instant now = Instant.now();
final Date notBefore = Date.from(now.minus(24, ChronoUnit.HOURS));
final Date notAfter = Date.from(now.plus(24, ChronoUnit.HOURS));
final ContentSigner selfSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
.build(keypair.getPrivate());
final X500Name owner = new X500Name("CN=" + fqdn);
final X509CertificateHolder certHolder = new JcaX509v3CertificateBuilder(
/*issuer*/owner,
/*serial*/new BigInteger(64, new SecureRandom()),
/*cert start*/notBefore,
/*cert end*/notAfter,
/*subject*/owner,
/*subject public key*/keypair.getPublic())
.build(selfSigner);
final X509Certificate selfSignedCert = new JcaX509CertificateConverter()
.getCertificate(certHolder);
selfSignedCert.verify(keypair.getPublic()); // Certificate is good!
return selfSignedCert;
}