Android 上的自签名 SSL 接受
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1217141/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Self-signed SSL acceptance on Android
提问by Faisal Abid
How do I accept a self-signed certificate in Java on Android?
如何在 Android 上接受 Java 中的自签名证书?
A code sample would be perfect.
代码示例将是完美的。
I've looked everywhere on the Internet and while some people claim to have found the solution, it either does not work or there is no sample code to back it up.
我在互联网上到处查看,虽然有些人声称找到了解决方案,但它要么不起作用,要么没有示例代码来支持它。
采纳答案by Brian Yarger
I have this functionality in exchangeIt, which connects to Microsoft exchange via WebDav. Here's some code to create an HttpClient which will connect to self signed cert's via SSL:
我在 exchangeIt 中有这个功能,它通过 WebDav 连接到 Microsoft Exchange。这是创建 HttpClient 的一些代码,它将通过 SSL 连接到自签名证书:
SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
The EasySSLSocketFactory is here, and the EasyX509TrustManager is here.
EasySSLSocketFactory 在这里,EasyX509TrustManager 在这里。
The code for exchangeIt is open source, and hosted on googlecode here, if you have any issues. I'm not actively working on it anymore, but the code should work.
exchangeIt 的代码是开源的,如果您有任何问题,请在此处托管在 googlecode 上。我不再积极地工作了,但代码应该可以工作。
Note that since Android 2.2 the process has changed a bit, so check thisto make the code above work.
请注意,自 Android 2.2 以来,该过程发生了一些变化,因此请检查此项以使上述代码正常工作。
回答by Chris Boyle
As EJP correctly commented, "Readers should note that this technique is radically insecure. SSL is not secure unless at least one peer is authenticated. See RFC 2246."
正如 EJP 正确评论的那样,“读者应该注意这种技术根本不安全。除非至少对一个对等点进行身份验证,否则 SSL 是不安全的。请参阅 RFC 2246。”
Having said that, here's another way, without any extra classes:
话虽如此,这是另一种方式,没有任何额外的类:
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
private void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session) {
return true;
}});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
回答by Frederic Yesid Pe?a Sánchez
I faced this issue yesterday, while migrating our company's RESTful API to HTTPS, but using self-signed SSL certificates.
我昨天在将我们公司的 RESTful API 迁移到 HTTPS 时遇到了这个问题,但使用了自签名 SSL 证书。
I've looking everywhere, but all the "correct" marked answers I've found consisted of disabling certificate validation, clearly overriding all the sense of SSL.
我到处寻找,但我发现的所有“正确”标记的答案都包括禁用证书验证,显然覆盖了 SSL 的所有意义。
I finally came to a solution:
我终于找到了解决办法:
Create Local KeyStore
To enable your app to validate your self-signed certificates, you need to provide a custom keystore with the certificates in a manner that Android can trust your endpoint.
创建本地密钥库
要使您的应用能够验证您的自签名证书,您需要以 Android 可以信任您的端点的方式提供带有证书的自定义密钥库。
The format for such custom keystores is "BKS" from BouncyCastle, so you need the 1.46 version of BouncyCastleProvider that you can download here.
此类自定义密钥库的格式是BouncyCastle 的“BKS” ,因此您需要 BouncyCastleProvider 的 1.46 版本,您可以在此处下载。
You also need your self-signed certificate, I will assume it's named self_cert.pem
.
您还需要您的自签名证书,我假设它名为self_cert.pem
.
Now the command for creating your keystore is:
现在创建密钥库的命令是:
<!-- language: lang-sh -->
$ keytool -import -v -trustcacerts -alias 0 \
-file *PATH_TO_SELF_CERT.PEM* \
-keystore *PATH_TO_KEYSTORE* \
-storetype BKS \
-provider org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
-storepass *STOREPASS*
PATH_TO_KEYSTORE
points to a file where your keystore will be created. It MUST NOT EXIST.
PATH_TO_KEYSTORE
指向将创建密钥库的文件。它不能存在。
PATH_TO_bcprov-jdk15on-146.jar.JAR
is the path to the downloaded .jar libary.
PATH_TO_bcprov-jdk15on-146.jar.JAR
是下载的 .jar 库的路径。
STOREPASS
is your newly created keystore password.
STOREPASS
是您新创建的密钥库密码。
- Include KeyStore in your Application
- 在您的应用程序中包含 KeyStore
Copy your newly created keystore from PATH_TO_KEYSTORE
to res/raw/certs.bks
(certs.bksis just the file name; you can use whatever name you wish)
将新创建的密钥库复制PATH_TO_KEYSTORE
到res/raw/certs.bks
(certs.bks只是文件名;您可以使用任何您想要的名称)
Create a key in res/values/strings.xml
with
创建一个关键res/values/strings.xml
与
<!-- language: lang-xml -->
<resources>
...
<string name="store_pass">*STOREPASS*</string>
...
</resources>
Create a this class that inherits
DefaultHttpClient
import android.content.Context; import android.util.Log; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import java.io.IOException; import java.io.InputStream; import java.security.*; public class MyHttpClient extends DefaultHttpClient { private static Context appContext = null; private static HttpParams params = null; private static SchemeRegistry schmReg = null; private static Scheme httpsScheme = null; private static Scheme httpScheme = null; private static String TAG = "MyHttpClient"; public MyHttpClient(Context myContext) { appContext = myContext; if (httpScheme == null || httpsScheme == null) { httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); } getConnectionManager().getSchemeRegistry().register(httpScheme); getConnectionManager().getSchemeRegistry().register(httpsScheme); } private SSLSocketFactory mySSLSocketFactory() { SSLSocketFactory ret = null; try { final KeyStore ks = KeyStore.getInstance("BKS"); final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs); ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray()); inputStream.close(); ret = new SSLSocketFactory(ks); } catch (UnrecoverableKeyException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyStoreException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyManagementException ex) { Log.d(TAG, ex.getMessage()); } catch (NoSuchAlgorithmException ex) { Log.d(TAG, ex.getMessage()); } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } catch (Exception ex) { Log.d(TAG, ex.getMessage()); } finally { return ret; } } }
创建一个继承的此类
DefaultHttpClient
import android.content.Context; import android.util.Log; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import java.io.IOException; import java.io.InputStream; import java.security.*; public class MyHttpClient extends DefaultHttpClient { private static Context appContext = null; private static HttpParams params = null; private static SchemeRegistry schmReg = null; private static Scheme httpsScheme = null; private static Scheme httpScheme = null; private static String TAG = "MyHttpClient"; public MyHttpClient(Context myContext) { appContext = myContext; if (httpScheme == null || httpsScheme == null) { httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); } getConnectionManager().getSchemeRegistry().register(httpScheme); getConnectionManager().getSchemeRegistry().register(httpsScheme); } private SSLSocketFactory mySSLSocketFactory() { SSLSocketFactory ret = null; try { final KeyStore ks = KeyStore.getInstance("BKS"); final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs); ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray()); inputStream.close(); ret = new SSLSocketFactory(ks); } catch (UnrecoverableKeyException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyStoreException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyManagementException ex) { Log.d(TAG, ex.getMessage()); } catch (NoSuchAlgorithmException ex) { Log.d(TAG, ex.getMessage()); } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } catch (Exception ex) { Log.d(TAG, ex.getMessage()); } finally { return ret; } } }
Now simply use an instance of **MyHttpClient**
as you would with **DefaultHttpClient**
to make your HTTPS queries, and it will use and validate correctly your self-signed SSL certificates.
现在只需使用的实例**MyHttpClient**
,你会与**DefaultHttpClient**
让你的HTTPS查询,它将使用和验证正确的自签名的SSL证书。
HttpResponse httpResponse;
HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...
MyHttpClient myClient = new MyHttpClient(myContext);
try{
httpResponse = myClient.(peticionHttp);
// Check for 200 OK code
if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
... do whatever you want with your response ...
}
}catch (Exception ex){
Log.d("httpError", ex.getMessage());
}
回答by dgatwood
Unless I missed something, the other answers on this page are DANGEROUS, and are functionally equivalent to not using SSL at all. If you trust self-signed certificates without doing further checks to make sure the certificates are the ones that you are expecting, then anyone can create a self-signed certificate and can pretend to be your server. At that point, you have no real security.
除非我遗漏了什么,否则此页面上的其他答案是危险的,并且在功能上等同于根本不使用 SSL。如果您信任自签名证书而不进行进一步检查以确保证书是您期望的证书,那么任何人都可以创建自签名证书并伪装成您的服务器。那个时候,你没有真正的安全感。
The onlylegitimate way to do this (without writing a full SSL stack) is to add an additional trusted anchor to be trusted during the certificate verification process. Both involve hard-coding the trusted anchor certificate into your app and adding it to whatever trusted anchors that the OS provides (or else you won't be able to connect to your site if you get a real certificate).
执行此操作的唯一合法方法(无需编写完整的 SSL 堆栈)是在证书验证过程中添加一个额外的受信任锚点。两者都涉及将可信锚点证书硬编码到您的应用程序中,并将其添加到操作系统提供的任何可信锚点中(否则,如果您获得真正的证书,您将无法连接到您的站点)。
I'm aware of two ways to do this:
我知道有两种方法可以做到这一点:
Create a custom trust store as described at http://www.ibm.com/developerworks/java/library/j-customssl/#8
Create a custom instance of X509TrustManager and override the getAcceptedIssuers method to return an array that contains your certificate:
public X509Certificate[] getAcceptedIssuers() { X509Certificate[] trustedAnchors = super.getAcceptedIssuers(); /* Create a new array with room for an additional trusted certificate. */ X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1]; System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length); /* Load your certificate. Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs for this bit. */ InputStream inStream = new FileInputStream("fileName-of-cert"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); /* Add your anchor cert as the last item in the array. */ myTrustedAnchors[trustedAnchors.length] = cert; return myTrustedAnchors; }
按照http://www.ibm.com/developerworks/java/library/j-customssl/#8 中所述创建自定义信任库
创建 X509TrustManager 的自定义实例并覆盖 getAcceptedIssuers 方法以返回包含您的证书的数组:
public X509Certificate[] getAcceptedIssuers() { X509Certificate[] trustedAnchors = super.getAcceptedIssuers(); /* Create a new array with room for an additional trusted certificate. */ X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1]; System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length); /* Load your certificate. Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs for this bit. */ InputStream inStream = new FileInputStream("fileName-of-cert"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); /* Add your anchor cert as the last item in the array. */ myTrustedAnchors[trustedAnchors.length] = cert; return myTrustedAnchors; }
Note that this code is completely untested and may not even compile, but should at least steer you in the right direction.
请注意,此代码完全未经测试,甚至可能无法编译,但至少应该引导您朝着正确的方向前进。
回答by Dmitry S.
Brian Yarger's answer works in Android 2.2 as well if you modify the bigger createSocket method overload as follows. It took me a while to get self-signed SSLs working.
如果您按如下方式修改更大的 createSocket 方法重载,Brian Yarger 的答案也适用于 Android 2.2。我花了一段时间才使自签名 SSL 生效。
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
回答by James
On Android, HttpProtocolParams
accepts ProtocolVersion
rather than HttpVersion
.
在 Android 上,HttpProtocolParams
接受ProtocolVersion
而不是HttpVersion
.
ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
回答by Yaakov
@Chris - Posting this as an answer since I can't add comments (yet). I'm wondering if your approach is supposed to work when using a webView. I can't get it do so on Android 2.3 - instead I just get a white screen.
@Chris - 将此作为答案发布,因为我无法添加评论(还)。我想知道您的方法在使用 webView 时是否应该有效。我无法在 Android 2.3 上这样做 - 相反,我只会得到一个白屏。
After some more searching, I came across this simple fix for handling SSL errors in a webViewwhich worked like a charm for me.
经过更多的搜索,我发现了这个简单的修复方法,用于处理 webView 中的 SSL 错误,这对我来说就像一个魅力。
In the handler I check to see if I'm in a special dev mode and call handler.proceed(), otherwise I call handler.cancel(). This allows me to do development against a self-signed cert on a local website.
在处理程序中,我检查我是否处于特殊的开发模式并调用 handler.proceed(),否则调用 handler.cancel()。这使我可以针对本地网站上的自签名证书进行开发。