JavaによるSocket,SSLSocketの実装

山崎 弘貴,廣安 知之,三木 光範

ISDL Report   No. 20050918004

2005年 10月 4日

Abstract

ソケットは,アプリケーションが通信を行う際にTCP/IPを扱うためのインターフェースで,コンピュータ同士でオブジェクトの送受信を行うことができる.しかし大規模なネットワークサービス上において通信を行うには,セキュリティ機構を整備することが重要であり,鍵・証明書による認証方式であるSSL(Socket Secure Layer)がよく用いられている.本報告では,Javaによるソケットの実装について,一般的なソケット通信とSSLを用いたセキュアなソケット通信の構築について示した.

1  はじめに

ソケットは,アプリケーションが通信を行う際にTCP/IPを扱うためのインターフェースである.ソケット通信では,IPアドレス(またはホスト名)とポート番号を指定し,コンピュータ同士でオブジェクトの送受信を行う.しかしインターネットなど大規模なネットワークサービス上において通信を行うには,セキュリティ機構を整備することが重要である.よく用いられるのが鍵・証明書による認証方式であるSSL(Socket Secure Layer)である.本報告では,Javaによるソケットの実装について,一般的なソケット通信とSSLを用いたセキュアなソケット通信の構築について示す.

2  ソケット通信

2.1  ソケット通信の仕組み

ソケット通信はFig.1に示すようにクライアント・サーバ方式で通信を行っている.クライアントがサーバに接続依頼することにより,両者の通信が成立する.通信手順・処理内容については以下に示す.

non correlation image
Fig.1 : ソケット通信の仕組み(出典:自作)
  • socket : TCPとアプリケーションとの通信路を作成し,ソケットの種類を指定
  • bind : IPアドレス(ホスト名)とポート番号を指定
  • listen : クライアントからの接続要求を準備
  • accept : クライアントからの接続要求を待機
  • connect : サーバへの接続要求を実行
  • send/recv : オブジェクトを送受信
  • close : ソケットの接続を切断

現実にはサーバとクライアントが1対1ということはほとんどなく,1対nといったマルチクライアントの構成をとることが大半である.マルチクライアントにするにはサーバ側は接続してきたクライアントに対しての処理を行うだけでなく,新たなクライアントが接続してくるのを待機する必要があり,この2つの処理を並列に行う方法としてマルチスレッドが用いられる.

2.2  ServerSocket,Socketの実装

2.1節のソケット通信に基づいたJavaによるサンプルプログラムを以下に示す.このプログラムは,サーバがクライアントから渡された文字列を全て大文字にしてクライアントに返すプログラムである.

プログラムの構成を図に示すとFig.2のようになる.ClientクラスがServerクラスに接続依頼を行い,ServerクラスはConnectクラスに対して処理依頼をする.通信はConnectクラスとClientクラスの間で行われる.以下にServerクラス,Connectクラス,Clientクラスのプログラムのソースを示す.

non correlation image
Fig.2 : ソケット通信プログラムの構成(出典:自作)
import java.net.*;

public class Server {

 public static void main(String[] args) {
  try {
   // サーバソケットを生成する
   ServerSocket ssoc = new ServerSocket(51004);
   
   while(true) {
    try {
     System.out.println("waiting for connection from client");
     // クライアントからの接続を待つ
     Socket soc = ssoc.accept();
     System.out.println("receive client!");
     
     // 処理をConnectに任せる
     new Connect(soc);
    } catch(Exception e) {
     e.printStackTrace();
    }
   }
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
}

Serverクラスは以下の処理を行っている.

  1. ポート番号を指定してServerSocketインスタンスを生成する.
  2. acceptメソッドにより,指定したポート番号でクライアントからの接続を待つ.
  3. 文字列変換の処理はConnectクラスに任せる.
  4. Serverクラスはループにより再びクライアントの接続待ち状態となる.
import java.io.*;
import java.net.*;

public class Connect extends Thread {

 private Socket soc = null;
 
 public Connect(Socket soc) {
  this.soc = soc;
  
  // スレッドを開始する
  this.start();
 }
 
 public void run() {
  try {
   // 入出力ストリームを取得する
   ObjectInputStream ois = new ObjectInputStream(
		soc.getInputStream());
   ObjectOutputStream oos = new ObjectOutputStream(
		soc.getOutputStream());
   
   // 入力ストリームからオブジェクトを取得する
   String str = (String)ois.readObject();
   
   // 文字列を大文字に変換する
   str = str.toUpperCase();
   
   // 出力ストリームにオブジェクトを書き出す
   oos.writeObject(str);
   
   // ストリーム・ソケットを閉じる
   ois.close();
   oos.close();
   soc.close();
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
}

Connectクラスは以下の処理を行っている.

  1. オブジェクト(ここでは文字列)の送受信のため入出力ストリームを取得する.
  2. readObjectメソッドを用いて入力ストリームからオブジェクトを取得する.
  3. toUpperCaseメソッドで文字列を全て大文字に変換する.
  4. writeObjectメソッドを用いて出力ストリームにオブジェクトを書き出す.
  5. 入出力ストリーム,ソケットを閉じる.
import java.io.*;
import java.net.*;

public class Client {

 public static void main(String[] args) {
  try {
   // ソケットを生成する
   Socket soc = new Socket("localhost", 51004);
   
   // 入出力ストリームを取得する
   ObjectOutputStream oos = new ObjectOutputStream(
		soc.getOutputStream());
   ObjectInputStream ois = new ObjectInputStream(
		soc.getInputStream());
   
   // 出力ストリームにオブジェクトを書き出す
   oos.writeObject(args[0]);
   
   // 入力ストリームからオブジェクトを取得する
   String str = (String)ois.readObject();
   
   // サーバの処理結果を表示する
   System.out.println("string to uppercase : " + str);
   
   // ストリーム・ソケットを閉じる
   oos.close();
   ois.close();
   soc.close();
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
}

Clientクラスは以下の処理を行っている.

  1. IPアドレス(もしくはホスト名)とポート番号を指定してSocketインスタンスを生成し,指定したポート番号で待機しているサーバに対して接続を行う.
  2. オブジェクト(ここでは文字列)の送受信のため入出力ストリームを取得する.
  3. writeObjectメソッドを用いて出力ストリームにオブジェクトを書き出す.
  4. readObjectメソッドを用いて入力ストリームからオブジェクトを取得する.
  5. サーバ側で処理された文字列を出力する.
  6. 入出力ストリーム,ソケットを閉じる.

まずコマンドプロンプトを立ち上げてServerクラスを実行する.クライアントの接続待ちが表示される.

$ java Server
waiting for connection from client

この状態で新たにコマンドプロンプトを立ち上げてClientクラスを実行する.Connectクラスで処理された文字列が表示される.

$ java Client yamazaki
string to uppercase : YAMAZAKI

するとサーバ側はClientクラスを接続を受理し,Connectクラスは文字列処理を行い,Serverクラスは再びクライアントの接続待ちとなる.

receive client!
waiting for connection from client

実行結果により,ソケット通信の実装が確認できたといえる.

3  SSLソケット通信

3.1  SSLとは

SSL(Secure Socket Layer)[1]とはネットワークを通してデータを送信する時に,データの機密性および整合性を保護するよう設計されたプロトコルである.SSLは鍵暗号,デジタル証明書などの技術を組み合わせて,セキュリティを強化している.

セキュリティを強化する方法として暗号化がある.これはデータの盗聴を防ぐために,ある条件に基づいてデータを変換する処理である.この変換処理に用いるものが「鍵」である.鍵による暗号方式には暗号するための鍵と復号するための鍵が異なる公開鍵暗号方式(PKI)がよく用いられる.この方式は一方の鍵で暗号化したものは他方の鍵でしか復号化できない鍵のペアを作成し,一方の鍵を通信相手に送信し(この鍵を公開鍵という),他方の鍵は個人で厳重に管理する(この鍵を秘密鍵という).たとえ通信中に第三者に公開鍵を盗まれても秘密鍵でしか復号化できないので問題ない.

また認証もセキュリティを強化する方法である.公開鍵暗号の場合,通信相手に対して公開鍵を送信している状況で,秘密鍵でデータを暗号化して通信相手に送信した場合,通信相手がこのデータを公開鍵で復号できるか否かで正しい通信相手であるかを判断できる.これを電子署名を呼ぶ.

しかし公開鍵の受け渡す際に,通信相手が第三者になりすましをされた場合,暗号化を行っても簡単に復号化されてしまい,データの漏洩の危険性がある.そこで公開鍵の正当性を信頼できる認証局(Certificate Authority : CA)によって証明を行ってもらう.

Fig.3にクライアント・サーバ間のSSL通信の流れを示す.SSLServerは予め公開鍵と秘密鍵を作成し,CAから電子証明書を受け取る必要がある.またSSLClientはCAの公開鍵を持っておく必要がある.

non correlation image
Fig.3 : SSL通信の流れ(出典:自作)
  1. SSLClientはSSLServerに対してSSLで通信することを要求する.
  2. SSLServerはSSLClientに対して自分の公開鍵を含んだ電子証明書を送信する.
  3. SSLClientはCAの公開鍵を用いて電子証明書からSSLServerの公開鍵を復号化する.
  4. SSLClientは新たに共通鍵を作成し,SSLServerの公開鍵で共通鍵を暗号化する.
  5. SSLClientはSSLServerに対して暗号化された共通鍵を送信する.
  6. SSLServerはSSLServerの秘密鍵で暗号化された共通鍵を復号化する.
  7. SSLServer,SSLClientともに入手した共通鍵で通信を行う.

3.2  SSL環境の構築手順

SSLを用いたソケット通信[2][3]を行うには,Javaに実装する前にサーバ側,クライアント側両方にKeyStoreとTrustStoreを作成する必要がある.KeyStoreは自らの証明書を保管する場所,TrustStoreはCAもしくは通信相手の証明書を保管する場所である.SSLの通信先が証明書を送信してきた際に,自分のTrustStoreに保管されている証明書によって署名がされているか否かによって認証の可否を判断する.

Fig.4にSSL通信における証明書の流れを示す.CAを用いるか否かによって証明書の獲得方法が違う.CAを用いる場合は,CAに対して証明書を要求し,CAから証明書をもらう.CAを用いない場合は,KeyStoreに既に格納されている証明書を使用する.

non correlation image
Fig.4 : SSL通信における証明書の流れ(出典:自作)

Fig.3の@〜Cの処理はJavaのAPIであるSSLServerSocket,SSLSocketが行うので,SSLを実装する際には気にする必要はない.SSL環境構築で必要な作業は次のとおりである.

以下より,この手順でSSL環境を構築する.

3.3  鍵・証明書の作成

3.3.1  KeyStoreの構築

クライアント側のKeyStoreを作成して,このファイルを解くキーペア(公開鍵・秘密鍵)を作成するには以下のコマンドを入力する.サーバ側も同様に作成する.

-genkey : キーペアを作成(引数なし)
-alias : 証明書・鍵をKeyStore内で識別する名前
-keystore : KeyStoreのファイル名

$ keytool -genkey -alias client -keystore clientKeys

パスワードを聞かれるので入力する.ここではpasswordとする.するとキーペアを作成するのに必要な各種パラメータの入力を求められるので入力していく.入力を求められるパラメータは名前,組織単位(部署)名,組織名,都道府県名,都市名,国名(日本はJP)である.最後にもパスワードの入力を聞かれるので入力してもいいが,KeyStore作成時に入力したパスワードと同じでいい場合はRETURN(ENTER)を押す.

Enter keystore password:  password

What is your first and last name?
   [Unknown]:  Yamazaki
What is the name of your organizational unit?
   [Unknown]:  Mikilab
What is the name of your organization?
   [Unknown]:  Doshisha
What is the name of your City or Locality?
   [Unknown]:  Kyoto
What is the name of your State or Province?
   [Unknown]:  Kyotanabe
What is the two-letter country code for this unit?
   [Unknown]:  JP
Is <CN=Yamazaki, OU=Mikilab, O=Doshisha, L=Kyoto, ST=Kyotanabe, 
C=JP> correct?
   [no]:  yes

Enter key password for  (RETURN if same as keystore password):

クライアント側のKeyStoreが作成されたことを確認したければ,以下のコマンドを入力する.なおKeyStoreが作成された時点で,証明書も作成されている.

-list : 証明書エントリの確認(引数なし)
$ keytool -list -v -keystore clientKeys

次に証明書をTrustStoreに格納するのに証明書を獲得する必要がある.証明書の獲得方法はCAを用いるか否かで違うので,それぞれの方法を示す.

3.3.2  TrustStoreの構築

手に入れた証明書をTrustStoreに格納する.サーバ側の証明書はクライアント側のTrustStoreへ,クライアント側の証明書はサーバ側のTrustStoreへ格納する.格納方法は以下のコマンドを入力する.

-import : 証明書のインポート(引数なし)
$ keytool -import -alias server -keystore clientTrust -file 
   server.cer
$ keytool -import -alias client -keystore serverTrust -file 
   client.cer

証明書を信頼するか尋ねてくるのでyesと入力すればTrustStoreに格納される.

Trust this certificate? [no]: yes

これでKeyStore,TrustStoreの構築は終了である.次はこれらのKeyStore,TrustStoreを用いてSSLをJavaに実装する.

3.4  SSLServerSocket,SSLSocketの実装

KeyStore,TrustStoreを用いてSSLをJavaに実装する[6]2.2節で示した文字列変換プログラムにSSLを実装したプログラムを以下に示す.

プログラムの構成を図に示すとFig.5のようになる.2.2節からはServerクラスをSSLSerevrクラスに,ClientクラスをSSLClientクラスに書き換え,SSLClientクラスはSSLServerクラスにSSLによる接続依頼を行っている.以下にSSLServerクラス,SSLClientクラスのプログラムのソースを示す.

non correlation image
Fig.5 : SSLソケット通信プログラムの構成(出典:自作)
import java.net.*;
import javax.net.ssl.*;

public class SSLServer {

 public static void main(String[] args) {
  try {
  
   // システムプロパティを設定する
   System.setProperty("javax.net.ssl.keyStore", ServerKeys);
   System.setProperty("javax.net.ssl.keyStorePassword", 
	"password");
   
   // KeyStoreを読み込む
   KeyStore key_store = KeyStore.getInstance("JKS");
   char[] key_pass = System.getProperty(
	"javax.net.ssl.keyStorePassword").toCharArray();
   key_store.load(new FileInputStream(ServerKeys), 
	key_pass);
   
   KeyManagerFactory kmf = KeyManagerFactory.getInstance(
	"SunX509");
   kmf.init(key_store, key_pass);
   
   // サーバソケットを生成する
   SSLContext context = SSLContext.getInstance("TLS");
   context.init(kmf.getKeyManagers(), null, null);
   SSLServerSocketFactory ssf = 
	context.getServerSocketFactory();
   SSLServerSocket ssoc = (SSLServerSocket)
	ssf.createServerSocket(51004);
   
   while(true) {
    
    			・・・・・
    
     Socket soc = (SSLSocket)ssoc.accept();
    
    			・・・・・
    
     2.2節のプログラムに同じ
    
   }
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
}

SSLServerクラスとServerクラスの変更点を述べる.

  1. SSLを用いる場合はjavax.net.sslパッケージが必要なので,importする.
  2. KeyStoreのシステムプロパティを設定する.javax.net.ssl.keyStoreにはサーバ側のKeyStoreのファイルを指定し,javax.net.ssl.keyStorePasswordにはKeyStoreのパスワードを指定する.TrustStoreは必要ない.
  3. KeyStoreを読み込み,初期化する.
  4. SSLServerSocketのインスタンスを生成するときはSSLServerSocketFactoryを用いる.
  5. acceptでクライアントを待つSocketをSSLSocketにする.

SSLServerSocketを生成するときにSSLServerSocketFactoryを用いることに注意すれば,あとはServerSocket,SocketをSSLServerSocket,SSLSocketにすればいい.

import java.io.*;
import java.net.*;
import javax.net.ssl.*;

public class SSLClient {

 public static void main(String[] args) {
  try {
   
   // システムプロパティを設定する
   System.setProperty("javax.net.ssl.trustStore", ClientTrust);
   System.setProperty("javax.net.ssl.trustStorePassword", 
	"password");
   
   // TrustStoreを読み込む
   KeyStore trust_store = KeyStore.getInstance("JKS");
   char[] trust_pass = System.getProperty(
	"javax.net.ssl.trustStorePassword").toCharArray();
   trust_store.load(new FileInputStream(ClientTrust), 
	trust_pass);
   
   TrustManagerFactory tmf = TrustManagerFactory.getInstance(
	"SunX509");
   tmf.init(trust_store);
   
   // ソケットを生成する
   SSLContext context = SSLContext.getInstance("TLS");
   context.init(null, tmf.getTrustManagers(), null);
   SSLSocketFactory sf = context.getSocketFactory();
   soc = (SSLSocket)sf.createSocket("localhost", 51004);
   soc.startHandshake();
   
   // 入出力ストリームを取得する
   ObjectOutputStream oos = new ObjectOutputStream(
	soc.getOutputStream());
   ObjectInputStream ois = new ObjectInputStream(
	soc.getInputStream());
   
    2.2節のプログラムに同じ
   
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
}

SSLClientクラスとClientクラスの変更点を述べる.

  1. SSLを用いる場合はjavax.net.sslパッケージが必要なので,importする.
  2. TrustStoreのシステムプロパティを設定する.javax.net.ssl.trustStoreにはクライアント側のTrustStoreのファイルを指定する.
  3. TrustStoreを読み込み,初期化する.
  4. SSLSocketのインスタンスを生成するときはSSLSocketFactoryを用いる.

SSLServerクラスと同様の変更を行えばよい.クライアント側はTrustStoreのファイルを設定するだけでよい.

これでJavaによるSSL環境の実装は終了である.実行結果については2.2節と同様なので省略する.

4  まとめ

本報告ではJavaによるソケットの実装について,一般的なソケット通信とSSLを用いたセキュアなソケット通信の構築について示した.ソケット通信はクライアント・サーバ方式をとり,コンピュータ同士でオブジェクトの送受信を行う.SSLと呼ばれる鍵・証明書による暗号・認証方式を用いることによって,通信機構のセキュリティを強化することができる.

Reference

[1]
SSL-VPN入門
http://primeserver.fujitsu.com/ipcom/catalog/data/2/2.html
[2]
JavaのSSLSocketでSSLクライアントとSSLサーバーを実装する
http://codezine.jp/a/article.aspx?aid=105
[3]
Custom SSL for advanced JSSE developers: Setting up KeyStore and TrustStore files
http://www-128.ibm.com/developerworks/java/library/j-customssl/sidebar.html
[4]
ビートラステッド社
http://www.betrusted.co.jp/
[5]
ベリサイン社
http://www.verisign.co.jp/
[6]
暗号,SSL,SSH
http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2004/2005-02-15/index.html

Copyright (C) 2005 Tomoyuki Hiroyasu, All rights reserved.
Copyright (C) 2005 Mitsunori Miki, All rights reserved.
Copyright (C) 2005 Hirotaka Yamazaki, All rights reserved.

No part of this document may be reproduced, copied, distributed,
transferred, modified, or transmitted, in any form or by any means,
without the prior written permission of the authors.
In no event shall the authors be liable for any damages caused in any way
out of the use of this document.

Back to Top