●SSLで通信

まず、権限をアプリに追加しないと始まりません。
そこで、インターネットのアクセス権限を与える為に、
MyApplication?\app\src\main\AndroidManifest.xml
この場所にある、AndroidManifest.xmlファイルを開きます。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="mail.myapplication">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>

            </intent-filter>
        </activity>
    </application>
</manifest>
赤文字の部分を追加することによりインターネットのアクセス権限をアプリに追加する事が出来ます。


■クライアント側の通信

androidでSSLで通信するにはjavaのSSLSocketを使います。
ウェブサーバーのSSL通信では、ポート443番に接続します。

相手先のアドレスのポートに接続するソケットを用意して、
javax.net.ssl.SSLSocketFactory sslSocketFactory = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) sslSocketFactory.createSocket("アドレス",ポート番号);

データを取得するストリーム
java.io.InputStream in = sslSocket.getInputStream();

データを送信するストリーム
java.io.OutputStream out = sslSocket.getOutputStream();

により基本的に通信を行います。

しかし、SSLの場合には受け取った証明書が接続先サーバーの物か確認する必要があります。
確認しないのならば、証明書が接続先サーバーの物でなくてもかまいません。
別に確認しなくても暗号化して通信できますが、悪意ある第三者に悪用される恐れがあります。
javax.net.ssl.HostnameVerifier hv = javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier();
javax.net.ssl.SSLSession s = sslSocket.getSession();
if (!hv.verify("証明書に書いてあるはずのアドレス", s)) {
	return (Object)("証明書が一致しません "+s.getPeerPrincipal());
}

次のプログラムではグーグルのhttpsサーバーであるポート443番にアクセスして返ってきた文字を表示します。
インターネットの通信はメインスレッドでは出来ない仕様になっているため、何らかの形でThreadを立てる必要があります。

androidらしく通信をAsyncTaskで行いました。
別に、通常のThreadを立てて通信しても問題なく行けます。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ((android.widget.Button)findViewById(R.id.button))
                .setOnClickListener(new android.view.View.OnClickListener() {
                    public void onClick(android.view.View view) {
                        asyncTask a=new asyncTask();
                        a.execute((Object)"www.google.com",(Object)443,new Object()) ;
                    }
                });
    }
    private class asyncTask extends android.os.AsyncTask{
        @Override
        protected Object doInBackground(Object... obj){
            //この関数は別スレッドで実行されるため、画面周りに関わる処理は出来ない。
            //また、戻り値はonPostExecuteの引数に渡され、そこで画面周りの処理ができる。
            try
            {
                javax.net.ssl.SSLSocket sslSocket = null;
                javax.net.ssl.SSLSocketFactory sslSocketFactory = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
                sslSocket = (javax.net.ssl.SSLSocket) sslSocketFactory.createSocket((String)obj[0],(int)obj[1]);

                //証明書が該当ホストと一致するか確認
                javax.net.ssl.HostnameVerifier hv = javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier();
                javax.net.ssl.SSLSession s = sslSocket.getSession();
                if (!hv.verify((String)obj[0], s)) {
                    return (Object)("証明書が一致しません "+s.getPeerPrincipal());
                }

                java.io.InputStream in = sslSocket.getInputStream();
                java.io.OutputStream out = sslSocket.getOutputStream();
                java.io.BufferedReader br = new java.io.BufferedReader( new java.io.InputStreamReader(in, "UTF-8")  );
                java.io.BufferedWriter bw = new java.io.BufferedWriter( new java.io.OutputStreamWriter(out, "UTF-8")  );

                bw.write("GET / HTTP/1.0\n\n");
                bw.flush();

                String  pos,str="";
                while((pos = br.readLine()) != null){
                    str+=pos;
                }

                in.close();
                out.close();
                sslSocket.close();
                return (Object)str;
            }
            catch( Exception e )
            {
                return (Object)e.toString();
            }
        }
        @Override
        protected void onPostExecute(Object obj) {
            //doInBackgroundの戻り値が引数に渡される
            //メインスレッドで実行されるため、画面周りの処理ができる。

            //画面にメッセージを表示する
            android.content.Context context = getApplicationContext();
            android.widget.Toast t=android.widget.Toast.makeText(context,(String)obj, android.widget.Toast.LENGTH_LONG);
            t.show();
        }
    }
}

画面にボタンを張り付けてこのプログラムを実行すると、次の様になります。



グーグルのサーバーが送り返してくるHTMLコードが表示されているのが見えます。


■しかしながら・・・・・

「レンタルサーバ」 TLS1.0/1.1無効化に関するお知らせ
という悪魔のようなお知らせが来て、私のTLS1.0のみ対応しているAndroid4.4.4のマシンでは突如として自作アプリとレンタルサーバ間の通信が出来なくなってしまいました。

しかしだが、調べてみると、TLS1.1/1.2がAndroid4.1から4.4.4については規定で無効になっているそうなのです。
そこで、ネットを検索しまくって有効にする方法を見つけてきました。

まず、SSLSocketFactoryを拡張して TLS 1.1 and TLS 1.2 を有効にするクラスとやらをネットで拾ってきました。

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

public class TLSSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);
        internalSSLSocketFactory = context.getSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return internalSSLSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return internalSSLSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket() throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
    }

    private Socket enableTLSOnSocket(Socket socket) {
        if(socket != null && (socket instanceof SSLSocket)) {
            ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
        }
        return socket;
    }
}
次に自作アプリの改造です。
とは、言ってもSSLSocketFactoryを上のクラスと入れ替えるだけですけどね。
これで、Android4.4.4の私の古いマシンでもTLS1.1/1.2でレンタルサーバと通信ができるようになりました。

       protected Object doInBackground(Object... obj){
            //この関数は別スレッドで実行されるため、画面周りに関わる処理は出来ない。
            //また、戻り値はonPostExecuteの引数に渡され、そこで画面周りの処理ができる。
            try
            {
                javax.net.ssl.SSLSocket sslSocket = null;
                TLSSocketFactory sslSocketFactory = new TLSSocketFactory();
                sslSocket = (javax.net.ssl.SSLSocket) sslSocketFactory.createSocket((String)obj[0],(int)obj[1]);

                //証明書が該当ホストと一致するか確認


▲トップページ > android