Android 6.0 SSL 通信

1,685 阅读4分钟
原文链接: www.jianshu.com

在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最新)
下载地址: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
With this release, the Android Keystore provider no 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

生成的密钥库如下图:


Paste_Image.png

2)从服务器端密钥库kserver.keystore中导出服务器证书:

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

执行命令后,填写密钥库密码即可,如下图:


Paste_Image.png

生成证书文件如下图:


Paste_Image.png

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

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


Paste_Image.png

※接下来采用同样的方法,生成客户端的密钥库,导出客户端的证书,并且导入到服务端的信任密钥库中

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

keytool -genkeypair -v -alias client -keystore kclient.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -keyalg ec


Paste_Image.png

Paste_Image.png

5)导出客户端证书:

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


Paste_Image.png

Paste_Image.png

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


Paste_Image.png

Paste_Image.png

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

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("服务器端退出了");
    }
}