前文Android HTTPS实战1说过,如果服务器部署的是知名CA签发的证书,我们无需做什么工作,Android就可以直接访问了。但如果服务器部署的是自签名的证书呢,我们该如何处理?
大概思路我们是有的,就是在这个方法中处理:
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException;
就像Android HTTPS基础里面说的, 我们要进行证书的有效日期检测,证书颁发者的检测,签名的检测等等。
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAUtils {
public static final String RSA = "RSA";// 非对称加密密钥算法
public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";//加密填充方式
public static final int DEFAULT_KEY_SIZE = 2048;//秘钥默认长度
public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes(); // 当要加密的内容超过bufferSize,则采用partSplit进行分块加密
public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;// 当前秘钥支持加密的最大字节数
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* 随机生成RSA密钥对
*
* @param keyLength
* 密钥长度,范围:512~2048
* 一般1024
* @return
*/
public static KeyPair generateRSAKeyPair(int keyLength)
{
try
{
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
kpg.initialize(keyLength);
KeyPair keyPair = kpg.genKeyPair();
return keyPair;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
/**
* 用公钥对字符串进行加密
*
* @param data 原文
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey)
throws Exception {
// 得到公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 加密数据
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.ENCRYPT_MODE, keyPublic);
return cp.doFinal(data);
}
/**
* 使用私钥进行解密
*/
public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey)
throws Exception {
// 得到私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 解密数据
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.DECRYPT_MODE, keyPrivate);
byte[] arr = cp.doFinal(encrypted);
return arr;
}
/**
* 加密
* 用私钥加密
*
* @param data
* @param privateKey
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey)
throws Exception {
// 对密钥解密
// 取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
Key keyPrivate = keyFactory.generatePrivate(pkcs8KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
}
/**
* 解密
* 用公钥解密
*
* @param data
* @param publicKey
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey)
throws Exception {
// 取得公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
Key keyPublic = keyFactory.generatePublic(x509KeySpec);
// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, keyPublic);
return cipher.doFinal(data);
}
/**
* 用私钥对信息生成数字签名
*
* @param data
* 加密数据
* @param privateKey
* 私钥
*
* @return
* @throws Exception
*/
public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
// 构造PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 取私钥匙对象
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 用私钥对信息生成数字签名
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(priKey);
signature.update(data);
return signature.sign();
}
/**
* 校验数字签名
*
* @param data
* 加密数据
* @param publicKey
* 公钥
* @param sign
* 数字签名
*
* @return 校验成功返回true 失败返回false
* @throws Exception
*
*/
public static boolean verify(byte[] data, byte[] publicKey, byte[] sign)
throws Exception {
// 构造X509EncodedKeySpec对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 取公钥匙对象
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
// 验证签名是否正常
return signature.verify(sign);
}
}
RSA工具类的方法:
MD5工具类:
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
public static final String KEY_SHA = "SHA";
public static final String KEY_MD5 = "MD5";
/**
* MAC算法可选以下多种算法
*
*
* HmacMD5
* HmacSHA1
* HmacSHA256
* HmacSHA384
* HmacSHA512
*
*/
public static final String KEY_MAC = "HmacMD5";
/**
* BASE64解密
*
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/**
* BASE64加密
*
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
public static String getMD5(byte[] source) {
String md5 = null;
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
java.security.MessageDigest messageDigest = java.security.MessageDigest.getInstance("MD5");
messageDigest.update(source);
byte bytes[] = messageDigest.digest();
char str[] = new char[16 * 2];
int k = 0;
// 把密文转换成十六进制的字符串形式
for (int i = 0; i < 16; i++) {
byte theByte = bytes[i];
str[k++] = hexDigits[theByte >>> 4 & 0xf];
str[k++] = hexDigits[theByte & 0xf];
}
md5 = new String(str);
} catch (NoSuchAlgorithmException e) {
}
return md5;
}
}
测试加密/解密代码:
public class Main {
public static void main(String[] args) {
String content="我是需要加密的数据内容";
KeyPair keyPair = RSAUtils.generateRSAKeyPair(1024);
byte[] encryptData = RSAUtils.encryptByPrivateKey(content.getBytes(), MD5Utils.decryptBASE64(base4PrivateKey));
String base64PublicKey = MD5Utils.encryptBASE64(keyPair.getPublic().getEncoded());
System.out.println("公钥Base64:" + base64PublicKey);
String base4PrivateKey = MD5Utils.encryptBASE64(keyPair.getPrivate().getEncoded());
System.out.println("私钥Base64:" + base4PrivateKey);
System.out.println("加密后:" + new String(encryptData));
byte[] decryptData = RSAUtils.decryptByPublicKey(encryptData, MD5Utils.decryptBASE64(base64PublicKey));
System.out.println("解密后:" + new String(decryptData));
}
}
测试签名/校验签名的代码:
public class Main {
public static void main(String[] args) {
String content="我是需要加密的数据内容";
KeyPair keyPair = RSAUtils.generateRSAKeyPair(1024);
byte[] sign = RSAUtils.sign(content.getBytes(), keyPair.getPrivate().getEncoded());
boolean flag = RSAUtils.verify(content.getBytes(), keyPair.getPublic().getEncoded(), sign);
System.out.println("flag:"+flag);
}
}
用KEYTOOL生成数字证书
keytool -genkey -alias zhy_server -keyalg RSA -keystore zhy_server.jks -validity 3600 -storepass 123456
生成的.jks(或者.keystore)文件,实际是包含了一对私有密钥/公共密钥在里面,然后我们通过以下命令
keytool -export -alias zhy_server -file zhy_server.cer -keystore zhy_server.jks -storepass 123456
导出得到.cer文件,相当于导出来一个服务器证书(Android HTTPS实战1提到),里面含有公开密钥, 不含私有密钥。由于使用 KEYTOOL 等工具无法直接导出公钥和私钥,所以必须通过代码进行导出。
把.cer文件放到Android项目assets目录下,或者任意能读取到的地方,总之你会拿到一个InputStream
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("xxx.cer"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
当客户端进行SSL连接时,就可以根据我们设置的证书(里面含有公钥,签名等)去决定是否信任服务端的证书。或者使用下面的方式,实现X509TrustManager:
private final class HelloX509TrustManager implements X509TrustManager {
private X509TrustManager mSystemDefaultTrustManager;
private X509Certificate mCertificate;
private HelloX509TrustManager() {
mCertificate = loadRootCertificate();
mSystemDefaultTrustManager = systemDefaultTrustManager();
}
private X509Certificate loadRootCertificate() {
String certName = "xxx.cer";
X509Certificate certificate = null;
InputStream certInput = null;
try {
certInput = new BufferedInputStream(MainActivity.this.getAssets().open(certName));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
certificate = (X509Certificate) certificateFactory.generateCertPath(certInput).getCertificates().get(0);
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} finally {
if (certInput != null) {
try {
certInput.close();
} catch (IOException e) {
}
}
}
return certificate;
}
private X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
mSystemDefaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509Certificate certificate : chain) {
try {
certificate.verify(mCertificate.getPublicKey());
return;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
mSystemDefaultTrustManager.checkServerTrusted(chain, authType);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return mSystemDefaultTrustManager.getAcceptedIssuers();
}
}
public abstract class Coder {
/**
* BASE64解密
*
* @param key
* @return
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/**
* BASE64加密
*
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key).replace("\r", "").replace("\n", "");
}
}
public class KeyStoreTool{
/**
* Java密钥库(Java Key Store,JKS)KEY_STORE
*/
public static final String KEY_STORE = "JKS";
public static final String X509 = "X.509";
/**
* 获得KeyStore
*
* @version 2016-3-16
* @param keyStorePath
* @param password
* @return
* @throws Exception
*/
public static KeyStore getKeyStore(String keyStorePath, String password)
throws Exception {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore ks = KeyStore.getInstance(KEY_STORE);
ks.load(is, password.toCharArray());
is.close();
return ks;
}
/**
* 由KeyStore获得私钥
* @param keyStorePath
* @param alias
* @param storePass
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(String keyStorePath, String alias, String storePass, String keyPass) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, storePass);
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
return key;
}
/**
* 由Certificate获得公钥
* @param keyStorePath
* KeyStore路径
* @param alias
* 别名
* @param storePass
* KeyStore访问密码
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(String keyStorePath, String alias, String storePass) throws Exception {
KeyStore ks = getKeyStore(keyStorePath, storePass);
PublicKey key = ks.getCertificate(alias).getPublicKey();
return key;
}
/**
* 从KeyStore中获取公钥,并经BASE64编码
* @param keyStorePath
* @param alias
* @param storePass
* @return
* @throws Exception
*/
public static String getStrPublicKey(String keyStorePath, String alias,String storePass) throws Exception{
PublicKey key = getPublicKey(keyStorePath, alias, storePass);
String strKey = Coder.encryptBASE64(key.getEncoded());
return strKey;
}
/*
* 获取经BASE64编码后的私钥
* @param keyStorePath
* @param alias
* @param storePass
* @param keyPass
* @return
* @throws Exception
*/
public static String getStrPrivateKey(String keyStorePath, String alias,String storePass, String keyPass) throws Exception{
PrivateKey key = getPrivateKey(keyStorePath, alias, storePass, keyPass);
String strKey = Coder.encryptBASE64(key.getEncoded());
return strKey;
}
public static void main(String args[]){
// 公钥
String strPublicKey = "";
// 私钥
String strPrivateKey = "";
try {
strPublicKey = KeyStoreCoder.getStrPublicKey("d://leslie.keystore", "everygold", "123456");
System.out.println("公钥 = 【" + strPublicKey + "】");
strPrivateKey = KeyStoreCoder.getStrPrivateKey("d://leslie.keystore", "everygold", "123456", "123456");
System.out.println("\n私钥 = 【" + strPrivateKey + "】");
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
KEYTOOL这个工具只能生成自签名的数字证书。所谓自签名就是指证书只能保证自己是完整的,没有经过非法修改的。但是无法保证这个证书是属于谁的(一句话:keytool没办法签发证书,而openssl能够进行签发和证书链的管理)。其实用这种自签名的证书也是可以进行双向验证的(用keytool生成的自签名证书进行双向验证请看这里:http://www.blogjava.net/stone2083/archive/2007/12/20/169015.html),但是这种验证有一个缺点:对于每一个要链接的服务器,都要保存一个证书的验证副本。而且一旦服务器更换证书,所有客户端就需要重新部署这些副本。对于比较大型的应用来说,这一点是不可接受的。所以就需要证书链进行双向认证。证书链是指对证书的签名有一个预先部署的,众所周知的签名方签名完成,这样每次需要验证证书时只要用这个公用的签名方的公钥进行验证就可以了。比如我们使用的浏览器就保存了几个常用的CA_ROOT。每次连接到网站时只要这个网站的证书是经过这些CA_ROOT签名过的。就可以通过验证了。
但是这些共用的CA_ROOT的服务不是免费的。而且价格不菲。所以我们有必要自己生成一个CA_ROOT的密钥对,然后部署应用时,只要把这个CA_ROOT的私钥部署在所有节点就可以完成验证了。要进行CA_ROOT的生成,需要OpenSSL(http://www.openssl.org/)。你也可以在http://www.slproweb.com/products/Win32OpenSSL.html找到Windows下的版本
安装好OpenSSL以后就可以生成证书链了
2.使用openssl生成根证书,服务端证书,客户端证书的方法:https://blog.csdn.net/u012333307/article/details/21597101
参考:
数字证书应用综合揭秘
Android Https相关完全解析 当OkHttp遇到Https
通过 HTTPS 和 SSL 确保安全
浅析HTTPS中间人攻击与证书校验
Android证书有效性验证方案
https证书有效性验证引发的安全问题
数字证书管理工具openssl和keytool的区别
用Keytool和OpenSSL生成和签发数字证书
Keytool和OpenSSL生成和签发数字证书