Android 6.0 SSL通信

在Android平台上使用SSL,第一步就是要生成证书。因为JDK自带的keytool工具默认生成的密钥库是JKS类型的,而Android客户端只支持BKS类型的密钥库,所以必须先扩展keytool工具使其生成BKS密钥库。要扩展,则需要下载BouncyCastle库。

JKS和JCEKS是Java密钥库(KeyStore)的两种比较常见类型,JKS的Provider是SUN,在每个版本的JDK中都有。
BKS来自BouncyCastleProvider,它使用的也是TripleDES来保护密钥库中的Key,它能够防止证书库被不小心修改(Keystore的keyentry改掉1个bit都会产生错误),BKS能够跟JKS互操作。

BouncyCastle配置

1.下载 bcprov-ext-jdk15on-156.jar (截止2017/1/19最新)
下载地址:http://www.bouncycastle.org

2.将bcprov-ext-jdk15on-156.jar复制到 jdk_home\jre\lib\ext下

3.在jdk_home\jre\lib\security\目录中找到 java.security 在其中增加一行(xx表示数字)

security.provider.xx=org.bouncycastle.jce.provider.BouncyCastleProvider

配置后如下图:

Paste_Image.png

使用keytool工具生成密钥库、导出证书、导入信任库

1)生成服务器端的密钥库kserver.keystore,采用默认的JKS类型

keytool -genkeypair -v -alias server -keystore kserver.keystore -keyalg ec

※SSLSocket签名算法默认为DSA,Android6.0(API 23)以后KeyStore发生更改,不再支持DSA,但仍支持ECDSA。所以需要指定签名算法:-keyalg ec

longer supports DSA. ECDSA is still supported.
    Keys which do not require encryption at rest will no 
longer be deleted when secure lock screen is disabled or 
reset (for example, by the user or a Device Administrator).
 Keys which require encryption at rest will be deleted during these events.```

执行命令后,然后填写一些必要的信息,就可以生成了,如下图:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-c76e1f50ad92f810.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
生成的密钥库如下图:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-a6be23e75b779cb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
**2)从服务器端密钥库kserver.keystore中导出服务器证书:**

keytool -exportcert -v -alias server -file server.cer -keystore kserver.keystore

执行命令后,填写密钥库密码即可,如下图:
 
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-d25820dd9039cc82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

生成证书文件如下图:
 
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-5248037e0dea287d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**3)将导出的服务器端证书导入到客户端信任密钥库tclient.bks中,其中客户端信任密钥库自动生成,并且此时要特别指明信任密钥库是BKS类型的,使用命令如下:**

keytool -importcert -v -alias server -file server.cer -keystore tclient.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

命令执行后过程截图如下:
 
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-6b0073c9e865f049.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

生成客户端密钥库tclient.bks如下:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-92b6bb394afe18d9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
**※接下来采用同样的方法,生成客户端的密钥库,导出客户端的证书,并且导入到服务端的信任密钥库中 **

**4)现在生成客户端密钥库kclient.bks,使用命令如下:**

keytool -genkeypair -v -alias client -keystore kclient.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider  -keyalg ec 
 
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-f857814e846bdf9b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-20825b5009a21dce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**5)导出客户端证书:**

keytool -exportcert -v -alias client -file client.cer -keystore kclient.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-df042652c07129d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-5c961f34b9f36cad.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**6)导入生成服务器端信任密钥库(JKS类型):**
keytool -importcert -v -alias client -file client.cer -keystore tserver.keystore

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-4740c8f3fcfe0e8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1546230-ba3db4a36372eda0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

至此,我们已经按照自己的要求生成了密钥库和证书文件,我们只需要将需要的文件复制到项目中去使用就可以了。

###SSLSocket使用例

**客户端代码如下:**

public class MainActivity extends Activity {
private static final String KEYSTOREPASSWORD = "123456"; //密钥库密码
private static final String KEYSTOREPATH_CLIENT = "kclient.bks"; //本地密钥库
private static final String KEYSTOREPATH_TRUST = "tclient.bks"; //信任密钥库
private static final String HOST = "192.168.1.196"; //服务器IP地址
private static final int PORT = 7891; //开放端口

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    msgEt = (EditText) findViewById(R.id.msg_et);
}

public void onClickBtn(View v) {
    if (v.getId() == R.id.send_btn) {
        connectAndSend();
    }
}

private void connectAndSend() {
    new Thread(new Runnable() {
        SSLContext sslContext = null;

        @Override
        public void run() {
            try {
                //取得TLS协议的SSLContext实例
                sslContext = SSLContext.getInstance("TLS");
                //取得BKS类型的本地密钥库实例,这里特别注意:手机只支持BKS密钥库,不支持Java默认的JKS密钥库
                KeyStore clientkeyStore = KeyStore.getInstance("BKS");
                //初始化
                clientkeyStore.load(
                        getResources().getAssets().open(KEYSTOREPATH_CLIENT),
                        KEYSTOREPASSWORD.toCharArray());
                KeyStore trustkeyStore = KeyStore.getInstance("BKS");
                trustkeyStore.load(getResources().getAssets()
                        .open(KEYSTOREPATH_TRUST), KEYSTOREPASSWORD.toCharArray());

                //获得X509密钥库管理实例
                KeyManagerFactory keyManagerFactory = KeyManagerFactory
                        .getInstance("X509");
                keyManagerFactory.init(clientkeyStore, KEYSTOREPASSWORD.toCharArray());
                TrustManagerFactory trustManagerFactory = TrustManagerFactory
                        .getInstance("X509");
                trustManagerFactory.init(trustkeyStore);

                //初始化SSLContext实例
                sslContext.init(keyManagerFactory.getKeyManagers(),
                        trustManagerFactory.getTrustManagers(), null);

                Log.i("System.out", "SSLContext初始化完毕...");
                //以下两步获得SSLSocket实例
                SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
                SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(HOST,
                        PORT);
                Log.i("System.out", "获得SSLSocket成功...");
                ObjectInputStream objectInputStream = new ObjectInputStream(sslSocket
                        .getInputStream());
                Log.i("System.out", objectInputStream.readObject().toString());

                ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                        sslSocket.getOutputStream());
                objectOutputStream.writeObject(msgEt.getText().toString());
                objectOutputStream.flush();

                objectInputStream.close();
                objectOutputStream.close();
                sslSocket.close();

            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            } catch (UnrecoverableKeyException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

}


**服务端代码如下:**

public class ServerMain {
private static final String KEYSTOREPASSWORD = "123456"; //密钥库的密码
private static final String KEYSTOREPATH_SERVER = "src/kserver.keystore"; //密钥库存放路径
private static final int PORT = 7891; //端口

public static void main(String[] args) {
    SSLContext sslContext = null;

    try {
        //服务端我们采用java默认的密钥库JKS类型,通过KeyStore类的静态方法获得实例并且指定密钥库类型
        KeyStore serverKeyStore = KeyStore.getInstance("JKS");      
        //利用提供的密钥库文件输入流和密码初始化密钥库实例
        serverKeyStore.load(new FileInputStream(KEYSTOREPATH_SERVER),
                KEYSTOREPASSWORD.toCharArray());
        //取得SunX509私钥管理器
        KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance("SunX509");
        //用之前初始化后的密钥库实例初始化私钥管理器
        keyManagerFactory.init(serverKeyStore, KEYSTOREPASSWORD.toCharArray());
        //获得TLS协议的SSLContext实例
        sslContext = SSLContext.getInstance("TLS");
        //初始化SSLContext实例
        sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
        //以下两步获得SSLServerSocket实例
        SSLServerSocketFactory sslServerSocketFactory = sslContext
                .getServerSocketFactory();
        SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory
                .createServerSocket(PORT);
        System.out.println("SSLServerSocket准备就绪...");
        while (true) {
            SSLSocket socket = (SSLSocket) sslServerSocket.accept();

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                    socket.getOutputStream());
            objectOutputStream.flush();
            objectOutputStream.writeObject("这里是服务器端...");
            objectOutputStream.flush();

            ObjectInputStream objectInputStream = new ObjectInputStream(
                    socket.getInputStream());
            try {
                System.out.println(objectInputStream.readObject().toString());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }

            objectOutputStream.close();
            objectInputStream.close();
            socket.close();
        }

    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (UnrecoverableKeyException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
    System.out.println("服务器端退出了");
}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容