2013年6月21日金曜日

tomcatでwebsocket

Tomcat7.0.27からwebsocketが使えるようになりました。

早速使ってみましょう。

今回はtomcat7.0.41を使用しています。

手始めにチャットを作ってみます。

①WebSocketServlet
 tomcatと言えばHttpServletを継承してサーブレットを作成しますが、
 websocketは「WebSocketServlet」を継承してサーブレットを作ります。

 public class ChatServlet extends WebSocketServlet {

②クライアントからの接続要求
 接続が来たらcreateWebSocketInboundが呼ばれます。

 @Override
 protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
  return new EchoMessageInbound(byteBufSize,charBufSize);
 }

③上記で生成するEchoMessageInboundクラスを定義します
 private static final class EchoMessageInbound extends MessageInbound {

  private String name = null;

  public EchoMessageInbound(int byteBufferMaxSize, int charBufferMaxSize) {

   super();

   setByteBufferMaxSize(byteBufferMaxSize);

   setCharBufferMaxSize(charBufferMaxSize);

  }

  @Override

  protected void onOpen(WsOutbound outbound) {

   super.onOpen(outbound);

   peers.add(this);

  }

  @Override

  protected void onClose(int status) {

   super.onClose(status);

   peers.remove(this);

  }

  @Override

  protected void onBinaryMessage(ByteBuffer message) throws IOException {

   System.out.println("onBinaryMessage");

   getWsOutbound().writeBinaryMessage(message);

  }

  @Override

  protected void onTextMessage(CharBuffer message) throws IOException {

   System.out.println("onTextMessage[" + message + "]");

   if( message.toString().indexOf("joined") > 0 ) {

    name = message.toString().split(" ")[0];

   }

   for( EchoMessageInbound u: peers ) {

    u.getWsOutbound().writeTextMessage(CharBuffer.wrap(""+message.toString()));

   }

  }

 }


これだけです。

④あとはクライアント側

websocket.js
※これはJavaDayTokyoでデモで使用されたファイルです。
/*

 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.

 *

 * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved.

 *

 * The contents of this file are subject to the terms of either the GNU

 * General Public License Version 2 only ("GPL") or the Common Development

 * and Distribution License("CDDL") (collectively, the "License").  You

 * may not use this file except in compliance with the License.  You can

 * obtain a copy of the License at

 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html

 * or packager/legal/LICENSE.txt.  See the License for the specific

 * language governing permissions and limitations under the License.

 *

 * When distributing the software, include this License Header Notice in each

 * file and include the License file at packager/legal/LICENSE.txt.

 *

 * GPL Classpath Exception:

 * Oracle designates this particular file as subject to the "Classpath"

 * exception as provided by Oracle in the GPL Version 2 section of the License

 * file that accompanied this code.

 *

 * Modifications:

 * If applicable, add the following below the License Header, with the fields

 * enclosed by brackets [] replaced by your own identifying information:

 * "Portions Copyright [year] [name of copyright owner]"

 *

 * Contributor(s):

 * If you wish your version of this file to be governed by only the CDDL or

 * only the GPL Version 2, indicate your decision by adding "[Contributor]

 * elects to include this software in this distribution under the [CDDL or GPL

 * Version 2] license."  If you don't indicate a single choice of license, a

 * recipient has the option to distribute your version of this file under

 * either the CDDL, the GPL Version 2 or to extend the choice of license to

 * its licensees as provided above.  However, if you add GPL Version 2 code

 * and therefore, elected the GPL Version 2 license, then the option applies

 * only if the new code is made subject to such option by the copyright

 * holder.

 */



var wsUri = "ws://" + document.location.host + "/Chat/websocket";

var websocket = new WebSocket(wsUri);



var username;

websocket.onopen = function(evt) { onOpen(evt) };

websocket.onmessage = function(evt) { onMessage(evt) };

websocket.onerror = function(evt) { onError(evt) };

var output = document.getElementById("output");



function join() {

    username = textField.value;

    websocket.send(username + " joined");

}



function send_message() {

    websocket.send(username + ": " + textField.value);

}



function onOpen() {

    writeToScreen("Connected to " + wsUri);

}



function onMessage(evt) {

    console.log("onMessage");

    writeToScreen("RECEIVED: " + evt.data);

    if (evt.data.indexOf("joined") != -1) {

        userField.innerHTML += evt.data.substring(0, evt.data.indexOf(" joined")) + "\n";

    } else {

        chatlogField.innerHTML += evt.data + "\n";

    }

}



function onError(evt) {

    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);

}



function writeToScreen(message) {

    output.innerHTML += message + "<br>";

}


⑤最後にhtml index.html
※これはJavaDayTokyoでデモで使用されたファイルを参考にしています。
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>WebSocket Chat</title>
    </head>
    <body>
        <h1>Chat!</h1>
        <div style="text-align: center;">
            <form action="">

                <table>
                    <tr>
                        <td>
                            Users<br/>
                            <textarea readonly="true" rows="6" cols="20" id="userField"></textarea>
                        </td>
                        <td>
                            Chat Log<br/>
                            <textarea readonly="true" rows="6" cols="50" id="chatlogField"></textarea>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="2">
                            <input id="textField" name="name" value="Duke" type="text"><br>
                            <input onclick="join();" value="Join" type="button">
                            <input onclick="send_message();" value="Chat" type="button">
                        </td>
                    </tr>
                </table>

            </form>
        </div>
        <div id="output"></div>
        <script language="javascript" type="text/javascript" src="websocket.js">

        </script>

    </body>
</html>


⑤完成です

これだけではwebsocketの威力は感じにくいかもしれません。
このチャットアプリを少し応用して複数の人が1つのcanvasに絵が書けるアプリを作ってみました。
とても簡単に作る事ができ、websocketの威力を味わえた気がします。


2013年6月7日金曜日

アンドロイドでアプリ内課金(v3)のテスト環境




アプリ内課金のテストを行うまでの手順を簡単にまとめます。

①googleアカウントを作成

  普通のgoogleアカウントです。


②googleウォレットの登録

  クレジットカード情報等を登録しましょう。


③販売・配布事業者登録

  Developer Console ( https://play.google.com/apps/publish )
  話を進めていき登録手数料$25支払う
  ※これをしないとアプリが公開できません


④google checkoutの登録

  google checkoutの登録(②のウォレット登録していれば入力が楽)
  ※これをしないと有料アプリやアプリ内課金ができません。


⑤Developer Consoleへ新規のアプリを登録する

  apkのアップロードを行います。
  ※特にこのアップロードしたapkを使わないとアプリ内課金のテストができないという事ではないので
    形式的なものです。未完成のapkをアップロードするのでも全然かまいません。
  ※apkを上げても「公開」されるわけではありません。


⑥Developer Console-アプリ内アイテム-新しいサービスを追加

サービスタイプは以下の3つ
1.管理対象の商品
2.管理対象外の商品
3.定期購入

1.はアカウント単位で1回購入できるアイテム(電子書籍とか)。appleでいうNon-consumable
2.は同一アカウントで何回も購入できるアイテム(回復系アイテムとか)。appleでいうConsumable
3.これはappleのAuto-renewable subscriptionsかな?

今回は何度も購入できるアイテム「2」を選択しました。
サービスID⇒アイテムに一意のIDを振ります。appleのproductID相当です。
後は、タイトルと説明と値段を書く。


⑦上記で作成したアイテムのステータスを「有効」にする



⑧テストアカウント作成

  テストアカウントとはアイテムを購入しても実際に課金される事は無いアカウントです。

  左のメニューバーから「設定」をクリック。
  [アカウントの詳細]ページで少し下にスクロールすると
  [テスト用のアクセス権があるGmailアカウント]という項目があるので
  テスト用のgoogleアカウントを設定する。

  複数のテストアカウントを登録する場合は , で区切ります
  例 test201306071014@gmail.com,test201306071015@gmail.com


⑨アイテム取得のサンプルアプリ

  実装はいろんなサイトで紹介されていますのでそちらを参照下さい。
  例えば http://y-anz-m.blogspot.jp/2013/02/android-api-version3.html

  まずはアイテム情報を取得するプログラムから作成し、それが成功したら
  購入の処理を追加すると良いと思います。

  appleの課金と違って消耗型は明示的に消耗した事をgoogle pleyに連絡しなければならない。
  consumePurchaseしないで再度購入しようとするとエラーになります

  [参考]
  http://catsdallas.blogspot.jp/2012/12/3-in-app-billing-version-3.html


【様々なエラーを体験したので記録しておきます】


(■エラー1)aidlのexception
05-28 11:42:53.068: W/Parcel(709): **** enforceInterface() expected 'com.android.vending.billing.IInAppBillingService' but read 'jp.co.arise.smp.rd002_inapppurchase.purchase.IInAppBillingService'
05-28 11:42:53.068: W/System.err(9859): java.lang.SecurityException: Binder invocation to an incorrect interface
05-28 11:42:53.076: W/System.err(9859):  at android.os.Parcel.readException(Parcel.java:1425)
05-28 11:42:53.076: W/System.err(9859):  at android.os.Parcel.readException(Parcel.java:1379)
05-28 11:42:53.076: W/System.err(9859):  at jp.co.arise.smp.rd002_inapppurchase.purchase.IInAppBillingService$Stub$Proxy.getSkuDetails(IInAppBillingService.java:251)
05-28 11:42:53.076: W/System.err(9859):  at jp.co.arise.smp.rd002_inapppurchase.MainActivity$2.run(MainActivity.java:62)
         ・
         ・
         ・
  ⇒プロジェクトに com.android.vending.billing というパッケージを作って IInAppBillingService.aidl を
    格納しました。勝手にパッケージ名を変えたら上記の様に怒られます。

(■エラー2)「このバージョンのアプリは、Google Playを通じたお支払いはご利用になれません。
    詳しくはヘルプセンターをご覧ください。」
  ⇒署名されたapkファイルを作り(⑤でアップロードしたAPKファイルを作成するときに
     使ったキーで署名しましょう)

    adb.exeを使ってインストールする。
    例:
        (a) [プロジェクト右クリック]-[android tools]-[export signed application package...]で
            apkを作成。
            例 C:\home\RD002_InAppPurchase.apk を作成
        (b) コマンドプロンプトを開く
        (c) cd c:\developAndroid\sdk\platform-tools (SDKの場所は環境により異なります)
        (d) adb install C:\home\RD002_InAppPurchase.apk
            少しすると Successs とでればOK 自動起動しないので実機のアプリ一覧から起動する。
        ※アンインストール adb uninstall jp.co.arise.smp.rd002_inapppurchase
           (マニフェストに書かれているパッケージ)

(■エラー3)「このIDでは課金できません的なエラー」
  ⇒テストアカウントではないから。
     ⑧で登録したテストアカウントがandroid端末に設定されていますか?

(■エラー4)サービスにバインドできない。(以下の部分がfalseになる)
boolean b = bindService(
new Intent("com.android.vending.billing.InAppBillingService.BIND"),
            mServiceConn, Context.BIND_AUTO_CREATE);

  ⇒サービスにバインドできないケースはTabActivityの子Activity内でbindServiceした場合に
    発生するようです。
    解決方法は bindService()を getApplicationContext().bindService() にすればうまくいきます。
boolean b = getApplicationContext().bindService(
new Intent("com.android.vending.billing.InAppBillingService.BIND"),
            mServiceConn, Context.BIND_AUTO_CREATE);


(■エラー5)startIntentSenderForResult()でonActivityResult()がコールバックされない
  ⇒tabactivityを使って startChildActivity() を呼んだ時に onActivetyResultがコールバック
    されない動きになるようです。例えばTab2SubActivityに課金処理を記述した場合

    【コールバックされない呼び方】
Intent intentTicket = new Intent(getParent(), Tab2SubActivity.class);
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.startChildActivity("Tab2SubActivity", intentTicket);

    【コールバックされた呼び方】
Intent intentTicket = new Intent(getParent(), Tab2SubActivity.class);
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.startActivity(intentTicket);

  こういうのは当たり前なのですかね?Activityのメカニズムとか知識が乏しくてすみません・・・


成功するとこんな感じのポップアップが表示されます。

-------------------------
| アプリ内課金チケット               ¥170
| 1枚(test)                                    
|                                                
| これはテスト用の注文です。課金は発生しません。
| お支払い方法を追加して購入を完了するには、[続行]
| をタップしてください。                        
|                                                
| ▲Google play                   [   続行   ]  
-------------------------


■1.続行を押してみる。

■2.「クレジット」か「キャリア課金」の選択を求められる。

■3.とりあえずキャリア課金を選択してみた。

■4.NTT DOCOMOの課金に関する利用規約が表示された。

■5.同意した。

■6.Googleアカウントに保存する請求先住所を入力して下さいと。。。

■7.恐る恐る入力。

すると違う画面が出てきた。
-------------------------
| アプリ内課金チケット               ¥170
| 1枚(test)
| NTT DOCOMO利用料金と一緒に支払い
|                          
| これはテスト用の注文です。課金は発生しません。
| [購入]をタップすると、Googleウォレット利用規約に
| 同意したことになります。          
|                                  
| ▲Google play                   [   購入   ]  
-------------------------

■8.SPモードのパスワードを聞いてきた。

■9.「お支払いが完了しました」という画面が・・・本当に大丈夫だよね。


以上です。


課金周りは実装もそうですが環境で大変な思いをする事があるかと思います。
少しでも助けになればと思い今回はお恥ずかしながらエラーもさらけ出しました。
役に立てば良いなと思います。ありがとうございました。

2013年5月28日火曜日

「条件によってボタンを差し替える」…ように見せる

入社2年目、システム開発部 中嶋です。

今回は「条件によってボタンを差し替える」…ように見せる実装方法です。
ボタンのStyleリソースを用意していれば、「setBackgroundResource」で条件によってスタイルを変更することができます。
しかし、Styleリソースを使わずに行う方法もあります。
用意するのが面倒くさい時など使える場面は限られるかもしれませんが、知っておいて損はないと思うのでご紹介します。

■動作イメージ
・「中①」ボタンを押下すると、「中①」ボタンが「中②」ボタンに変化
・「中②」ボタンを押下すると、「中②」ボタンが「中①」ボタンに変化
・「A」ボタンを押下すると、「A」ボタンが「B」ボタンに変化
・「B」ボタンを押下すると、「B」ボタンが「A」ボタンに変化

■XMLレイアウト

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/buttonLeft"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="左" />

        <Button
            android:id="@+id/buttonCenter1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="中①" />

        <Button
            android:id="@+id/buttonCenter2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="中②" />

        <Button
            android:id="@+id/buttonRight"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="右" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center" >

        <Button
            android:id="@+id/buttonA"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="A" />

        <Button
            android:id="@+id/buttonB"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="B" />
    </LinearLayout>

</LinearLayout>

■Javaソース(一部)
※setVisibilityのパラメタの説明
VISIBLE:表示
INVISIBLE:非表示
GONE:無かったことにする

onCreate内
  // 初期は「中②」を非表示にする
  buttonCenter2.setVisibility(View.GONE);

  // 初期は「B」を非表示にする
  buttonB.setVisibility(View.GONE);

onClick内
  // 「中①」押下時
  if (v == buttonCenter1) {
   buttonCenter1.setVisibility(View.GONE);
   buttonCenter2.setVisibility(View.VISIBLE);
  }

  // 「中②」押下時
  if (v == buttonCenter2) {
   buttonCenter1.setVisibility(View.VISIBLE);
   buttonCenter2.setVisibility(View.GONE);
  }

  // 「A」押下時
  if (v == buttonA) {
   buttonA.setVisibility(View.GONE);
   buttonB.setVisibility(View.VISIBLE);
  }

  // 「B」押下時
  if (v == buttonB) {
   buttonA.setVisibility(View.VISIBLE);
   buttonB.setVisibility(View.GONE);
  }
■もう少し説明
【View.GONE】で片方が消えた後、
【layout_weight】の動きによって幅が自動的に調整されます。
ユーザーは片方のボタンずつしか目にしないので、
「同じ位置にあるボタン」が「刺し変わっているように見える」はずです。

2013年5月24日金曜日

Java Day Tokyo 2013



Java Day Tokyo 2013 に行ってきました。
その時のレポートを書きたいと思います。




まずはキーノートです。
これからはJava ME Embedded。小型の組み込み市場への導入を加速させる。
それは昔、サーバーの市場をJavaEEで満たしたように。とおっしゃっていました。
期待しましょう。

それとJDK8の話をしていました。
正式版のリリース予定は2014年2月で、今後は2年サイクルでメジャーバージョンを
上げていく予定との事です。JDK9は2016年2月ということになります。

JDK8に組み込まれた主要なものとしてラムダ(Lambda)式の導入がありました。
ラムダ式というとコードをより簡素にスマートに記述できるのがメインであると感じられるが
実際はもっと重要な効果があるようです。
それは今時のコンピューターにはマルチコアなCPUが当たり前で、同時に複数の処理をこなす
事があたりまえの時代なのですが、ラムダ式の効果は「もっと効率的にCPUを使おうよ」
ということらしいです。それもThreadのような大きな単位ではなくてもっと細かい処理の単位で
パラレル処理が行えるようです。

ラムダ式については試してみたいと思っています。
JDK8のearly releaseが以下からダウンロードできます。
https://jdk8.java.net/download.html


あとは、JavaFX。
JavaFXがJavaの標準GUIライブラリとして採用されましたので、デスクトップアプリケーション等の
UIはJavaFXで書くことになっていくのかなと思いました。
数年前に名前を聞いたっきりあまり聞かなかったのですが、JDKの標準に組み込まれ更に進化
していたようでこれも少し興味がわいてきました。
JavaFXスクリプトで記述する必要があったのですが、もうそのスクリプトは廃止されJavaのAPIとしてJavaFXが使えるようになったようです。

・多彩なGUI部品を持っています。例えばチャートなど
・ハードウェアアクセラレーションを使った高速なレンダリング
・H.264のサポート、動画再生のAPI
面白そうですね。


最後にJaveEEでWebSocketに対応する(JavaEE7 finalリリースはあと少し)との事です。
今までサーバーから情報をクライアントに渡す場合(PUSH)、クライアントからしか接続
できないので、ポーリングやロングポーリング等で「ごまかしていた」わけです。
それがwebSocketにより完全な全二重通信が可能になるわけですね。
webSocketは当然httpとは別のプロトコルなので、http通信をトリガとしてプロトコルを
upgradeしてあげる必要があります。
httpヘッダの最初の要求には

Upgrade: websocket
Connection: Upgrade

と書かれてます。これがhttpプロトコルをwebsocketプロトコルにupgradeするということです。
upgradeされたらhttpのプロトコルに従う必要はなくなるわけです。
例えばhttp requestに対して必ずhttp responseを返す等というルールに従う必要はありません。
サーバーが一方的にクライアントに対してデータを定期的に送りつけるという事ができます。

websocketを使うと通信のオーバーヘッドがとても少なくすみ効率的です。
Arun Guptaさんは実際にエコーサーバーを使ってどれだけの違いがあるか試して頂けました。

100byteのペイロードを1000回エコー要求した場合
http req/resp では8198msec
websocket では      248msec
8.2秒と0.2秒。ここまで差がついてしまうとは驚きでした。

glassFish4.0等で試す事ができますので興味ある方はどうぞ。


最後にJJUGグループに参加して皆でjavaを創っていきましょう!とのことでした。

日本JJUGグループ
http://www.java-users.jp/


それでは失礼致します。

2013年5月17日金曜日

Android Studio を使ってみる


Android Studio は、JetBrains 社の IntelliJ IDEA をベースに作られた、 Android のための新しい開発環境で、「Google I/O 2003」の基調講演で発表され、現在、early access preview がダウンロード可能になっています。

早速使ってみます。
 ( インストールする端末: Windows 7 64bit )

■ インストール
  1. http://developer.android.com/sdk/installing/studio.html より EXE ファイルをダウンロードする。
  2. EXE ファイルを実行し、setup wizard にしたがってインストールする
■ Android Studio 起動
    前のバージョンをインストールしている場合、設定をインポートできるようです。今回初めてインストールしたので、↓の選択肢を選びます。









    ■ Eclipse のプロジェクトを移植
    1. Eclipse から Export する
      1. Eclipse ADT Plugin をアップデート ( Ver. 22.0 以上にする )
      2. Eclipse のメニュー「File」 ⇒ 「Export」 を選択
      3. Android を展開し、Generate Gradle build files を選択








      4. Export したいプロジェクトにチェックを入れて「Finish」 をクリック
      5. ※ Export したプロジェクトは、同じ場所に残ったまま、build.gradle ファイルが作成される

    2. Android Studio へ Import する
      1. Android Studio のメニュー「File」 ⇒ 「Import Project」 を選択
      2. Eclipse から Export したプロジェクトのルートディレクトリを選択し、「OK」をクリック
      3. 「Create project from existing sources」を選択し、「Next」をクリック
      4. 「Next」を押し続ける

    ■ テーマを黒にする方法
    1. メニュー「File」 ⇒ 「Settings」 を選択
    2. 「Appearance」を選択 ⇒ 「Theme」を Darcula に変更











    少し使ってみて便利な機能がいくつかありました。

    ・ Preview で、「Preview All Screen Sizes」を選択するいろいろな画面でのレイアウトのプレビューがライブで表示できる。





















    ・ レイアウト xml で、レンダリング時の問題点が表示され、クリックすると補完できる。
     下記のように、<Button/> と入力すると、必須のattributes が抜けているので、設定するための選択肢が表示される。















    ・ コード内で画像を使用していると、左側に表示される。








    ショートカット一覧
















    参考

    http://developer.android.com/sdk/installing/studio.html
    http://developer.android.com/sdk/installing/migrate.html
    http://developer.android.com/sdk/installing/studio-tips.html
    http://android-developers.blogspot.jp/2013/05/android-studio-ide-built-for-android.html

    - 追記
    Java 7だとインストールに失敗するという話があったので調べました。
    JDK_HOME, JAVA_HOME を設定するとよいそうです。
    http://stackoverflow.com/questions/16574189/android-studio-installation-on-windows-7-fails-no-jdk-found


    2013年5月10日金曜日

    公開鍵暗号方式によるセキュリティ基盤(PKI)



    最近「公開鍵とかって何なんですか?」と質問されたのでそれについて書きたいと思います。
    どちらかというとインフラ系の話になってしまいます。

    ■PKIとは何でしょうか?

     Public Key Infrastructure
     公開鍵暗号方式によるセキュリティ基盤
     ~~~~~~~~~~

     ⇒なんだか難しそうですが名前からして「公開鍵」がキーワードなんでしょうか。


    ■PKIでどの様な効果を得る事ができるのか?

     PKIによって得られる効果は

     (a)データの暗号化
       ●機密性…データの暗号化を行うことで盗聴の脅威を防ぐ。

     (b)デジタル署名
       ●認証…その人が誰であるかを証明する。(なりすましを防ぐ)
       ●完全性…データが変更(改ざん)されていない事を証明する。
       ・否認防止…自分が行った行為を否認できなくする。(行動を証明する)
        (否認とは例えば、ネットショッピングしたのにそんなの買ってないと言い張る等)

     ⇒データの暗号化はイメージが湧きますがデジタル署名の
      なりすましを防ぐとか、データの改ざんを防ぐとか、行動の証明
      ってどういう事なんでしょうかね?


    ■データの暗号化方式

     暗号化方式は大別すると2種類ある

     (a)共通鍵暗号方式

      通信し合う者同士が同じ鍵を持ち暗号化する方式。

      ⇒例えばお客様に設計書ファイルをメールに添付して渡す際は
       設計書ファイルを圧縮してパスワードをかけたりします。
       そしてパスワードのかかった設計書とパスワードをお客様に渡して
       お客様にパスワードを解除してもらいます。

       これで安全に設計書がお客様の手元に渡ると思っていたのですが
       いろいろ調べていくと本当に安全なのかと思いました。
       もし、パスワードのかかった設計書とパスワード両方を悪人にとられたら
       もうアウトです。

       ※共通鍵暗号化方式の弱点が見えてきました。
       ネットワーク越しに鍵(パスワード)を配送すると、その鍵がとられたら終わりという事です。

       どうしましょうか。。そこで公開鍵の登場です。


     (b)公開鍵暗号方式 ※PKIは公開鍵暗号方式によるセキュリティなのでこの方式は重要です。

      この方式は共通鍵暗号化方式の弱点を克服しています。
      どういう方式かというと、特殊な鍵を使います。
      それは「公開鍵(PublicKey)」「秘密鍵(PrivateKey)」という2種の鍵を使用するということです。
      「片方の鍵で暗号化したデータを、もう片方の鍵で復号化する」これ以上の性質はない。

      ○○○ & 公開鍵 = ▼◎■×● <============> ▼◎■×● & 秘密鍵 = ○○○
      (双方向で可能です)

      ※この鍵の凄いところは「鍵Aで暗号化したデータを鍵Aで複合化できない」というところです。
       「鍵Aで暗号化したデータは鍵Bでしか複合化できません」凄い鍵が発明されました!

       この2つの鍵は「キーペア」と呼ばれます。

       例えばサーバーに対して安全にデータを送信したい場合、サーバーに2つの鍵を
       格納しておきます。
       クライアントはサーバーに暗号化されたデータを渡す為に、まずサーバーから鍵を
       1つもらいます。
       クライアントは自分が持っているデータをサーバーからもらった鍵で暗号化します。
       この暗号化されたデーターをネットワークに流してサーバーに届けます。

       もし悪人がネットワークを盗聴していて、サーバーからもらった鍵と、暗号化されたデータが
       盗まれた場合どうなりますか?
       答えは全く問題ありません。何故なら盗んだ鍵では暗号化されたデータを
       複合化できないからです。

       サーバーに渡ってきた暗号化されたデータはもう一方の鍵で複合化され、
       安全にデータを渡す事ができます。

       もしサーバーに格納されているもう一方の鍵が盗まれたら完全にアウトなのでこれは
       厳重に管理する必要があります。

       サーバーに格納しているキーペアの内、クライアントに渡す用の鍵を「公開鍵」と呼びます。
       公開なので誰でもその鍵を入手できるという事です。
       もう一方の鍵は「秘密鍵」と呼びます。これは絶対に誰にも渡してはいけません。


     ※ちなみに馴染みの深いSSL。例えばブラウザでネットバンキング等を利用する時に
      URLの頭にhttpsという文字列がついていますよね。
      このhttpsはネットワークに流すデータを安全に通信するため暗号化を行っている。
      というところまではご存知かと思いますが、どういう仕組みなのかはよくわかりません。

      答えはもう皆さまご存じの「共通鍵暗号方式」と「公開鍵暗号方式」を組み合わせて
      使っています。
      「公開鍵暗号方式」だけでは不十分で「共通鍵暗号方式」の要素も必要なのです。

      何故、公開鍵暗号化方式だけでは駄目なのか。じっくり考えてみて下さい。

      答えは・・・この辺に分かり易く書かれています。
      http://itpro.nikkeibp.co.jp/article/COLUMN/20071012/284424/


    ■セキュアハッシュ関数

     公開鍵暗号方式を用いたデジタル署名の実現のための重要な関連技術に
     「セキュアハッシュ関数」があります。

     [ポイント]
      ①可変長のデータから固定長のビット列を生成する。
      ②その固定長のビット列を「ダイジェスト」という。(ダイジェスト=ハッシュ値)
      ③ダイジェストから元のメッセージを算出することはできない(不可逆)。
      ④2つの異なるデータから、同じダイジェストが生成されることがある。
       これをコリジョン(衝突)と言う。

     [ハッシュ方式]
      ①MD5…128byteのダイジェストを生成する。
      ②SHA1…160byteのダイジェストを生成する。


    ■デジタル署名

     [目的]
      ・データの完全性(改ざんされた場合の検知)
      ・データ作成者の認証

     [流れ]
      【送信者A】
       送信者は予めAの公開鍵をCAに提出し、Aの情報にCAの署名が付いた「証明書」を
       発行してもらいます。
       ①Aは、データのダイジェスト(=ハッシュ値)を生成する。
       ②Aは、ダイジェストをAの秘密鍵で暗号化する。
       ③Aは、データ(平文)にデジタル署名を付与し相手に送信する。
        恐らく、デジタル署名=公開鍵証明書(X.509形式)のことで、
        公開鍵証明書とはCAから署名されたAの公開鍵証明書である。
        (公開鍵証明書の最後の項目に署名アルゴリズムと署名値を設定するところがある。)
        (署名値とは、②でダイジェストを暗号化した値のこと)
        (詳しくは、http://www.ipa.go.jp/security/pki/033.htm)
       ※注意! デジタル署名でデータが暗号化されるわけではありません!

      【受信者B】
       ①Bは、Aからデジタル署名されたデータを受信する。
       ②Bは、デジタル署名から「Aの公開鍵」と「暗号化されたダイジェスト」を取り出す。
       ③Bは、暗号化されたダイジェストをAの公開鍵で復号し、「ダイジェスト」を取得する。
       ④Bは、Aから受信したデータ(平文)のダイジェストを生成し、③のダイジェストと
        一致しているか検証する。

       一致していればデータの改ざんは無いという事がいえます。

       ポイントはAがデータのダイジェストを秘密鍵で暗号化して、Bに対して
       「データ(平文)」「秘密鍵で暗号化されたダイジェスト」「Aの公開鍵」を渡すところです。
       ん?「秘密鍵で暗号化されたダイジェスト」「Aの公開鍵」を渡すと言う事は
       暗号化されたデータとパスワードを一緒に渡している様なもので、
       そもそも暗号化する必要ないのでは?
       と思いがちですがこれは重要な意味を持っています。

       この本来の目的はデータの改ざんがないよね?ということを検証する。
       では「データ(平文)」と「暗号化していないダイジェスト」を送った場合、
       「データ(平文)」と同時に「暗号化していないダイジェスト」も一緒に改ざんされたら
       終わりです。
       それを防止するために「秘密鍵で暗号化されたダイジェストを公開鍵で複合できれば
       ダイジェストは改ざんされていないと言えます」

       ここで更に、全く別の秘密鍵と公開鍵ですり替えられた場合どうなるのか?

       そんな不安を解消するためにBは公開鍵が本当にAの公開鍵である事を
       検証する必要があります。
       Aの公開鍵がBの信頼している人に署名されていれば安心です。
       信頼している人は有名なベリサイン等です。


     [署名アルゴリズム]
       署名アルゴリズムで代表的なものは「公開鍵暗号化をRSAで行いSHA1でハッシュする」
       です。公開鍵暗号化はRSA以外にもDSAやECDSAがあり、MD5のハッシュも有名です。


    ■認証モデル

     相手の公開鍵が本物であるか判断するための認証について2つのモデルがある。
     (a)PGPモデル
      公開鍵の所有者を信頼できる人物に保証してもらう方法です。
      Aさんが未知のCさんの公開鍵を取得したい場合、AさんとCさんの双方と信頼関係にある
      Bさんが、Cさんの公開鍵に署名を付けてAさんに送付します。
      主体が存在しないため企業や組織向けではありません。

     (b)認証局モデル
      信頼できる第三者機関(TTP:TrustedThirdParty)に公開鍵の所有者を保証してもらう方法。
      認証局はパブリックCAとプライベートCAがあります。
      パブリックCAとはベリサインやグローバルサインの有名どころです。
      企業の独自CAはプライベートCA(おれおれ認証局)と言われます。

      ブラウザでhttps通信するとたまに
     このサイトのセキュリティ証明書は信頼できません
      と言われる事があります。これは、プライベートCAが署名した証明書をもつサイトであり、
      ブラウザがこのCAを信用できないためユーザに警告しているのです。
      ユーザは、信用できるCAであるとわかっているのに毎度警告されるのが嫌であれば
      プライベートCAのCA証明書をもらってブラウザにインポートして下さい。
      するとブラウザはこのCAが署名した証明書を全て信頼します。



    ■最後に

     公開鍵による暗号化とデジタル署名の理解が少しでも深まればより安全にネットを
    利用できるのではないかと思います。


    参考
    http://www.ipa.go.jp/security/pki/032.html
    http://www.ipa.go.jp/security/pki/033.html
    http://www.techscore.com/tech/J2EE/Servlet/11-4.html
    http://itpro.nikkeibp.co.jp/article/COLUMN/20071012/284424/
    http://www.net.c.dendai.ac.jp/~kousu/sotsuken.htm

    2013年4月26日金曜日

    Android GCM を使う

    サーバからAndroid 端末へメッセージをデータ送信できる GCM を使いました。(無料です)
    GCM: Getting Started の手順に沿って「サーバからメッセージを送って、Logcatに表示する」仕組みを作ります。

    ■Google API Project の作成:
    1. Google APIs Console page へアクセス
    2. まだAPI Projectを作成していない場合、下記のような画面が現れます。
      API Projectを既に作成している場合、DashBoard が表示されます。この画面の API Projectドロップダウンメニュー(画面左上)から、Other Projects > Create. で、新規プロジェクトを作成できます。
    3. Create Project ボタンをクリックすると、ブラウザのURLが下記のように変わります。
      https://code.google.com/apis/console/#project:4815162342:services
    4. #project:以降の値をメモしておきます(3.の場合、4815162342 )。この値がプロジェクトIDで、今後 GCM のsender ID として使われます。

    ■GCM サービスを有効にする:
    1. 左側のメニューより、Services を選択します。
    2. Google Cloud Messaging for Android を on にします。
    3. 利用規約が表示されるので、I agreeにチェックを入れ、Accept をクリックします。

    ■API キーの取得:
    1. 左側のメニューより、API Access を選択すると、下記のような画面が表示されます。
      IP アドレス制限をかけない場合は、このAPI キーを使用する。制限をかける場合は2へ
    2. Create new Server key ボタンをクリックする。下記ダイアログが表示されるので、
    3. サーバのIPアドレスを入力し、Create をクリックすると、下記のように Key for server apps (with IP locking)  が追加される。

    ■Helperライブラリのインストール:
    • SDK Manager を起動し、Extras > Google Cloud Messaging for Android をインストールする。Android SDKのディレクトリ内は下記のようになります。

    ■Androidアプリの作成:
    1. 新規プロジェクトを作成 ( minSdkVersionは、8以上に設定 )し、プロジェクトのビルドパスへ、gmc.jar を追加します。
    2. AndroidManifest.xmlを次のようにします。
      1. sdkの設定 (この通りになっているはず)
        <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="xx" />
      2. パーミッションの設定を追加
        <permission android:name="my_app_package.permission.C2D_MESSAGE" android:protectionLevel="signature" />
        <uses-permission android:name="my_app_package.permission.C2D_MESSAGE" /> 
        my_app_package は、マニフェストのpackage名を入れます。
      3. 以下のパーミッションも追加
        <!-- App receives GCM messages. -- >
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
        <!-- GCM connects to Google Services. -- >
        <uses-permission android:name="android.permission.INTERNET" />
        <!-- GCM requires a Google account. -- >
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <!-- Keeps the processor from sleeping when a message is received. -- >
        <uses-permission android:name="android.permission.WAKE_LOCK" />
      4. 以下の broadcast receiver を追加
        <receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
          <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="my_app_package" />
          </intent-filter>
        </receiver>
      5. 以下のサービスを追加
        <service android:name=".GCMIntentService" />

    ■サービスクラスの作成:
    import android.content.Context;
    import android.content.Intent;
    import android.util.Log;
    
    import com.google.android.gcm.GCMBaseIntentService;
    
    public class GCMIntentService extends GCMBaseIntentService {
     private static final String TAG = GCMIntentService.class.getSimpleName();
    
     @Override
     protected void onError(Context arg0, String arg1) {
      Log.d(TAG, "onError");
     }
    
     @Override
     protected void onMessage(Context context, Intent intent) {
      Log.d(TAG, "onMessage");
     }
    
     @Override
     protected void onRegistered(Context context, String regId) {
      Log.d(TAG, "regId registered:" + regId);
     }
    
     @Override
     protected void onUnregistered(Context context, String regId) {
      Log.d(TAG, "regId not registered:" + regId);
     }
    
     @Override
     protected boolean onRecoverableError(Context context, String errorId) {
      // TODO Auto-generated method stub
      return super.onRecoverableError(context, errorId);
     }
    }
    
    

    ■MainActivityの修正:

    import文追加
    import com.google.android.gcm.GCMRegistrar;

    onCreate() メソッド内に以下を追加
    SENDER_ID は、Google API Project の作成 の 4 でメモした値を定数定義します。
    端末のデバイスIDを知りたいので、LogcatにregIdを出力します。
    GCMRegistrar.checkDevice(this);
    GCMRegistrar.checkManifest(this);
    final String regId = GCMRegistrar.getRegistrationId(this);
    Log.d(TAG, "regId :" + regId);
    
    if (regId.equals("")) {
      GCMRegistrar.register(this, SENDER_ID);
    } else {
      Log.v(TAG, "Already registered");
    }

    ■サーバ側のプログラム作成:
    1. GCM SDK の gcm-server.jar をサーバのクラスパスが通っているところへコピーします。
    2. GCMServer.java 作成(サンプルとほぼ変わりません)
      デバイスID・・・作成したAndroidアプリを端末へインストールして、Logcatに出力されたID
      Google API Key ・・・APIプロジェクトを登録したときに取得したもの
      import java.util.ArrayList;
      import com.google.android.gcm.server.*;
      
      public class GCMServer {
        
        public static void main(String[] args) {
        try{
          String sendMessage = "say hello from my server!";
          ArrayList devices = new ArrayList();
          devices.add( デバイスID );
          Sender sender = new Sender( Google API Key );
          Message message = new Message.Builder().addData("message", sendMessage).build();
          MulticastResult result = sender.send(message, devices, 5);
        } catch (Exception e) {
          System.out.println(e);
        }
        }
      }
      
      

      コンパイルは通りましたが、実行時にエラーが発生しました。
      Exception in thread "main" java.lang.NoClassDefFoundError: org/json/simple/parser/ParseException

      StackOverFlow に載っていました。 json-simple-1.1.1.jar を追加し、再度挑戦したら、動きました。

    ■GCMIntentService.javaを修正:

    サーバからのメッセージをログに出力したいので、GCMIntentService.java の onMessage() メソッドを修正します。
    String str = intent.getStringExtra("message");
    Log.d(TAG, "onMessage str:" + str);

    再度端末へインストールして、サーバ側のプログラムを実行すると、Logcatにでました。
     onMessage str:say hello from my server!

    日本語も表示できるか確認します。
    GCMServer.java のメッセージ文字列に日本語を追加し、実行します。
    String sendMessage = "say hello from my server! 日本語いけるかな?";

    無事でました。
     onMessage str:say hello from my server! 日本語いけるかな?

    2013年4月19日金曜日

    AndroidとELM327で車と会話(OBD2)



    「車と会話できる方法がある」という噂を聞いてそれについて調べてみました。


    ■準備

    1.ELM327 OBD2 スキャンツール
     ⇒amazon等で2000円程度で購入できます。

    2.android端末(SO-02C)
     ⇒bluetooth使えればOKです。

    3.車(2004年式Suzuki Swift)
     ⇒ELM327に対応していない車も結構あります。


    ■構成

    イメージ的にはこんな感じです。

    このandroidで車の情報を収集できます。


    ELM327の実物です



    ELM327スキャンツールを車に差し込んでアンドロイド端末とbluetoothで通信します。


    ■OBDプロトコル

    OBDとはOn Board Diagnostic Systemの略で、車に搭載されたコンピューターが故障の診断を行います。
    そして今回やりたい事は車に搭載されているコンピューターに対してELM327経由で問い合わせをし情報を収集する事です。

    故障の診断を行うのですからandroidから「故障情報」が取得できるのだろうと思うのですが、
    実は故障情報だけでなく走行中の車のスピードやエンジンの回転数等がとれます。

    私はどちらかと言うと故障情報よりはスピードやエンジンの回転数を取得して、androidでスピードメーターアプリなんかが興味あります。


    ■ペアリング

    まずはELM327とペアリングを行う必要があります。流れとしては以下の通りです。
    1.車にELM327を差し込みます。
      ⇒車ごとに差し込み口は異なり、見え難いところだったりしますので
       注意が必要です。私は20分位探しまくってやっと見つけました。。
      ⇒正しく差し込むと赤いランプが点灯します。(エンジンかけなくとも)

    2.android端末でペアリング操作
      ⇒[設定]-[無線とネットワーク]-[Bluetooth設定]・・・
       ELM327のデバイスを選択する。私の場合は[CHX]でした。pinコードは6789。


    ■プログラミング

    1.権限
      <uses-permission android:name="android.permission.BLUETOOTH" />
      <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

    2.UUID
      bluetoothで使用するUUIDは
      "00001101-0000-1000-8000-00805F9B34FB"とします。
      これは、Bluetooth SPP(Serial Port Profile)のUUIDです。

    3.bluetoothでコネクト
    UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    socket = device.createRfcommSocketToServiceRecord(uuid);
    socket.connect(); // この処理は最大12秒位かかるので非同期処理が良さそうです。
    

      ※deviceはELM327のBluetoothDeviceのインスタンスです。
       bluetoothの接続についての詳細は割愛します。

    4.初期化コマンド
      いよいよコマンドをELM327に送ります。
      "atz", "ate0", "atl0" という3つのATコマンドを送ります。

    OutputStream outputStream = socket.getOutputStream();
    outputStream.write("atz\r".getBytes()); // ★コマンドの最後は\r
    outputStream.flush(); // 送信
    // 送信直後に応答を待ちます
    // 応答は '>'(プロンプト) を受けるまで読み続けます
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    while( true ) {
     InputStream in = socket.getInputStream();
     byte[] buffer = new byte[1];
     if( in.read(buffer) > 0 ) {
      buf.write(buffer);
      if( new String(buffer).indexOf(">") >= 0 ) {
       break; // ★プロンプトを終端とする。
      }
     }
    }
    
    
    ※ATコマンド
        http://elmelectronics.com/ELM327/AT_Commands.pdf

      以下の通り結果は以下の通りです。

      送信[atz]
      受信[0d 0d 45 4c 4d 33 32 37 20 76 31 2e 35 0d 0d 3e](hex)
         \r \r  E  L  M  3  2  7 △  v  1  .  5 \r \r  >
      送信[ate0]
      受信[61 74 65 30 0d 4f 4b 0d 0d 3e](hex)
         a  t  e  0 \r  O  K \r \r  >
      送信[atl0]
      受信[4f 4b 0d 0d 3e]
         O  K \r \r  >

    5.走行自動車のスピードを発行
      4のコマンドを"01 0D"とします。

      結果は以下の通りです。

      送信[010D]
      受信[42 55 53 20 49 4e 49 54 3a 20 4f 4b 0d 37 46 20 30 31 20 31 31 20 0d 0d 3e](hex)
         B  U  S △  I  N  I  T  : △  O  K \r  7  F △  0  1 △  1  1 △ \r \r  >

      BUS INIT:OK までは良かったのですがその後の
      7F 01 11 という回答はどうもスピード情報ではないようです。
      何度"01 0D"コマンドを発行しても 7F 01 11 でした。
      この結果は残念ながら「失敗」の様です。

      ※コマンドのPID一覧
        http://en.wikipedia.org/wiki/OBD-II_PIDs


    ■最後に

      スピードの情報は一番とりたかった情報なのに残念です。
      もしかして試した車が2004 Suzuki Swift 15で、対応していないのかもしれません。

      進展があったらブログを更新したいと思います。