読者です 読者をやめる 読者になる 読者になる

JavaでSSL通信したいのに、手元にはPEMファイルしか無い時の解決方法

PHPでは適当にPEMファイルをcontextに含めてやりゃー、それでSSL通信ができます。お手軽!

ですが、JavaではPEMファイルだけでは、SSL通信ができません!!(ババーン)

そうは言っても、手元にはPEMファイルしかねーよ!! という方もいるでしょう。そう、私です!!(ババーン)

色々、調べてみた結果、PEMファイルからPKCS#12形式の証明書を作成して、SSL通信をするしか無いようだ。

PEMファイルは200個以上あるんだけど……大丈夫かな。

OpenSSLを使って、PEMからPKCS#12を生成する

この方法が一番メジャーな方法のようだ。SSL接続に使用する証明書が少ない時は、この方法でも良いのだろう。

# certificateとprivate keyが一つのファイルにある場合
openssl pkcs12 -export -in certkey.pem -out certkey.p12 -passsout pas:newpassword -name "newname"

# certificateとprivate keyが別々のファイルにある場合
openssl pkcs12 -export -in cert.pem -inkey key.pem -out certkey.p12 -passsout pas:newpassword -name "newname"

この方法をとる場合は、事前にPEMファイルから、全てPKCS#12形式のファイルに変換しておく必要がある。(もしくは、JavaからRuntimeのexecを使って起動するか。)

PEMファイルは多いし、これからも増えるだろうから、動的にPKCS#12を生成したいにゃー。

外部プログラムの起動には色々問題があるしのー。(そもそも外部プログラム叩いていいなら、PHP叩くわ)

BouncyCastleを使って、PEMからPKCS#12を生成する

BoucyCastleオープンソースの暗号化ライブラリです。

The Legion of the Bouncy Castle
http://www.bouncycastle.org/

これを使って、PEMファイルを読み込んでPKCS#12を生成できる。

PEMReader pr = new PEMReader(new FileReader("cert.pem"));
X509Certificate cert = (X509Certificate) pr.readObject();
PEMReader kr = new PEMReader(new FileReader("privkey.pem"),
        new PasswordFinder() {
    public char[] getPassword() {
        return "passphase".toCharArray();
    }
});
KeyPair key = (KeyPair) kr.readObject();
KeyStore ksKeys = KeyStore.getInstance("JKS");
ksKeys.load(null, "passphase".toCharArray());
ksKeys.setCertificateEntry("MyCert", cert);
ksKeys.setKeyEntry("Mykey", key.getPrivate(),
        "passphase".toCharArray(), new Certificate[]{cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
        KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ksKeys, "passphase".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ksKeys);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

Socket socket = sslContext.getSocketFactory().createSocket(
        "localhost", 4433);
BufferedReader in = new BufferedReader(new InputStreamReader(
        socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(
        socket.getOutputStream()));
out.println("Hello World");
System.out.println(in.readLine());
out.close();
in.close();

とまぁ、こんな感じらしい。

結論

めんどい。

参考

術/Security/PKI,SSL,TLS/メモ01_拡張子の迷宮(pem,der,crt,cer,csr,...)
http://www.glamenv-septzen.net/view/1058

PKCS12 形式のキーストアの作成
http://docs.oracle.com/cd/E19416-01/820-5959/ggfhb/

SSL socket php code needs to be converted to Java
http://stackoverflow.com/questions/722931/ssl-socket-php-code-needs-to-be-converted-to-java

Convert SSL .pem to .p12 with or without OpenSSL
http://stackoverflow.com/questions/9711173/convert-ssl-pem-to-p12-with-or-without-openssl

PHPでphpQueryを使ってWebスクレイピングしてみる

Webスクレイピングだなんて、あんまり大きい声じゃ言えない。
結局のところ、他所様のコンテンツを勝手に拝借してゴニョゴニョするわけで、良いことではないわ。

だが、人間生きていれば、Webスクレイピングをしなければいけない場面くらいある。

そう、今回はそういう場面なのだ。

Webスクレイピングとは?

プログラムで、WebページのHTMLを取得し、解析して、必要なデータだけを抽出することを指す用語。
ウェブスクレイピング(Wikipedia)

例えば、Amazonなどの通信販売系のWebサイトにスクレイピングをかけると、少年漫画の新刊情報だけ抽出できたりするのだ。

だが、プログラムが自動的に、必要なデータだけを抽出するということは、広告やら何やら余計な情報が表示されない。という意味になる。

Webサイトを開いている個人はともかく、営利目的である企業のWebサイトやサービスでは、そんなことをされると、広告収入が減ったり、プログラムで大量に連続的にアクセスされることで、Webサーバーに負荷がかかったりと、あんまり良いことはない。

明確に規約で禁止されていることもある。もしWebスクレイピングに手を出すなら、充分に気を付けなくてはいけない。

ISBNコードから書籍情報を検索したい

友人の蔵書管理システムのデータ復旧を手伝っている内に、どうしてもISBNコードから書籍情報を検索する必要が出てきた。

一応、書籍データベースが利用できないかと思って調べてみたのだが、書籍データベースというものは有料で高価な物ばかりで、個人的に利用するには年収が 500,000 USDくらいないときつい。

一件ずつ手動でAmazonで検索すれば良いのだろうけど、4005件もデータがあるので、手動でやっていた日には検索が完了する頃には、人類が火星に移住してしまう。

そういうわけで、Webスクレイピングをするのだ。目指すはISBNコードから書籍情報を検索して、CSVの一覧にすること。(自動で、お手軽に!)

まずはWebページをダウンロードしてみよう

Webスクレイピングの手順は以下の様になる。

  1. URLを特定する。
  2. 特定できたURLからHTMLを取得する。
  3. HTMLを解析して必要なデータを抽出する。

とまぁ、このような手順である。

URLを特定するのと、HTMLを取得する部分は、PHPなので適当に書けるだろう。

// ISBNコードが掲載されているテキストファイルを読み込む
$isbn_file = file("isbn.txt");
// 1行ずつループを回す
foreach($isbn_file as $isbn) {
    // urlを特定
    $url = sprintf("http://example.com/isbn/%s/", $isbn);
    // HTMLをダウンロード
    $html = get_webpage($url);
    var_dump($html);
    // サーバー側にDDos攻撃だと思われないように、充分なスリープを取る
    sleep(10);
}

※ Webスクレイピングの対象になるWebサイトは、動的にページを生成することが多く、ほとんどの場合、リダイレクトへの対処が必要になるのだが、上記コードはリダイレクトへの対処をしていない。リダイレクトが発生するWebページを上記コードでダウンロードしようとしても、うまくいかないので注意。(この件については、別記事として書くことにする。)

※ リダイレクトへの対応が面倒な場合は、PEARHTTP_Clientなどを使うと簡単に実装できるかも。

phpQueryを使ってHTMLを解析するのだ!

あ、やっとタイトルに出てきたphpQueryが出てきた。
はい。

phpQueryとはjQueryというJavaScriptと同一の感覚でDOM操作が行えるPHP用のライブラリです。jQueryでのDOM操作に慣れていれば、簡単に扱えます。

大体、PHPを触るような人なら、jQueryくらい使えるので、phpQueryは非常に便利なライブラリですね! PHP使ってるけどjQuery使えないという人は、まぁ覚えてください。

phpQueryのプロジェクトページ
https://code.google.com/p/phpquery/

phpQueryのダウンロードページ
https://code.google.com/p/phpquery/downloads/list

phpQueryをダウンロードする際は、onefile とついたファイルをダウンロードすると良い。一個のファイルに、phpQueryの実装がまとめられていて、requireやincludeする際に面倒が無くて良い。

では、早速使ってみよう。以下の様なHTMLをダウンロードしてきたとする。

...
<div class="book">
	Authors: 藤崎 聖人<br>
	Publisher: 小学館<br>
	Pages: 135<br>
	Published: 2006-04-18<br>
	Language: Japanese<br>
	Category: コミック, コミック・ラノベ・bl, <br>
	Binding: コミック<br>
	List Price: 4.10 JPY<br>
</div>
...

上記は、実際にHTMLから抜き出したい箇所である。このデータを抽出したいのだ。
phpQueryでの書き方はこう。

        $doc = phpQuery::newDocument($html);
        $book = $doc['.book'];
        echo $book;

なお、bookというクラスは、HTML中に一回しか出てこないものとする。
複数回、出てくる場合は、$bookという変数の中に複数要素が入る。

ちなみに

Webスクレイピングしようとしていたページは、詳細ページでテキストファイルがダウンロードできる。
URL中のISBNコードを書きかえるだけで、対象のテキストファイルがダウンロードできたので、Webスクレイピングは実は全く必要なかったのだ。

やっぱりWebスクレイピングだなんて、やらないほうがいい。迷惑だからな。皆もやるんじゃないぞ。

JavaでProducer-Consumerパターンを実践!

やりたいこと

Javaでマルチスレッドな処理を各必要が出来た。

外部サーバーとやりとりするのだが、自サーバーから外部サーバーへの同時接続は15本に限定されている。逆を言えば15本までは同時接続できるわけだ。

ならば、15本のスレッドを走らせて逐次処理を行っていくのが良い。

スレッドの数が15本と決まっているのならば、プログラムの起動時に、スレッドを15本作って、それを使いまわすのが最も良いだろう。いちいち、スレッドを作ったり破棄したりする手間が減るってものだ。

と、ここまで考えて、そんなデザインパターンなかったかなー。と思って、検索。

デザインパターンなんてGoFデザインパターンですら、記憶が曖昧なので、毎回検索して調べてる。)

マルチスレッドのデザインパターンは数あれど、今回の仕様に当てはまるのは、Producer-Consumerパターンかな。と思った。

Producer-Consumerパターン

簡単に図示すると以下のようになる。(図示ってずじじゃなくて、ずしなんだな。いつもずじずじ言ってたわ)

f:id:omiya6048:20130529133257p:plain

Producerは仕事に必要なデータを集めて、せっせとQueueに詰めていく。ConsumerはQueueからデータを取り出して、処理を行う。

Queueは複数スレッドであるから、Queueはスレッドセーフである必要がある。

と、まぁここまでは簡単に理解できた。あとは実際にサンプルコードを見れば実装可能だ。

というわけで、Javaでのサンプルコード探し。

Javaでのサンプルが掲載された良い記事

すぐにサンプルコードは何個か見つかったが、以下のページが整理されていて、とても分かりやすい。

Producer Consumer Design Pattern with Blocking Queue Example in Java
http://javarevisited.blogspot.jp/2012/02/producer-consumer-design-pattern-with.html

特に、この記事ではProducer-Consumer問題についての解決策について、Javaでの具体的で簡単な解決策が書かれていたのが秀逸。

Producer-Consumer問題(有限バッファ問題)

ConsumerはQueueが空の場合は、Queueにデータが入ってくるまで待機しなくてはいけない。
ProducerはQueueがいっぱいの場合は、Queueが空くまで待機しなくてはいけない。
とまぁ、これが問題らしいんだけど、何が問題なのか恥ずかしながら理解できない。

単純にQueueを各スレッドが監視して、Queueが空の場合は、ProducerがQueueにデータを入れて、Consumerは待機。

Queueが満杯の場合は、Consumerが処理を頑張って、ProducerはQueueが空くまで待機してれば良いんじゃないの?

とりあえず、僕は理解できていないので、詳しいことは英語版Wikipediaの該当記事を見てもらいたい。日本語版Wikipediaでは、セマフォの項で、ちらっと触れられてるレベル。

Producer–consumer problem
http://en.wikipedia.org/wiki/Producer-consumer_problem

セマフォ(生産者/消費者問題
http://ja.wikipedia.org/wiki/%E3%82%BB%E3%83%9E%E3%83%95%E3%82%A9#.E4.BE.8B:_.E7.94.9F.E7.94.A3.E8.80.85.2F.E6.B6.88.E8.B2.BB.E8.80.85.E5.95.8F.E9.A1.8C

日本語でも解説しているページはあるんだけど、どうもピンと来ない。

有限バッファ問題
http://besttseb99.exblog.jp/5512020

うまく同期させないと駄目なのかー。レベルの理解度なんだけど……。もしくは上手く実装しないと無駄なスリープが発生しまくって、実行効率が落ちまくるぜ! って問題なのかなあ。よく分からんね。

でまぁ、先に紹介したJavaのサンプルコードのページでは、色々面倒な実装が必要なんだけど、実はJavaにはBlockingQueueっていうのがあって、これを使えば簡単に実装できるんだぜ!! って書いてある。

実際に、英語版WikipediaにあるJavaのサンプルコードと比較すると、BlockingQueueを使っている方が明らかにコード量が少ない。簡単だ。

問題がよく理解できないまま、僕はBlockingQueueを使えば大丈夫という情報を鵜呑みにすることにした。

とりあえず、Producer-Consumer問題については、宿題とする。

サンプルコード

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ProducerConsumerPattern {

    public static void main(String args[]){
  
     //Creating shared object
     BlockingQueue sharedQueue = new LinkedBlockingQueue();
 
     //Creating Producer and Consumer Thread
     Thread prodThread = new Thread(new Producer(sharedQueue));
     Thread consThread = new Thread(new Consumer(sharedQueue));

     //Starting producer and Consumer thread
     prodThread.start();
     consThread.start();
    }
 
}

//Producer Class in java
class Producer implements Runnable {

    private final BlockingQueue sharedQueue;

    public Producer(BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }

    @Override
    public void run() {
        for(int i=0; i<10; i++){
            try {
                System.out.println("Produced: " + i);
                sharedQueue.put(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

}

//Consumer Class in Java
class Consumer implements Runnable{

    private final BlockingQueue sharedQueue;

    public Consumer (BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
  
    @Override
    public void run() {
        while(true){
            try {
                System.out.println("Consumed: "+ sharedQueue.take());
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
  
  
}


Read more: http://javarevisited.blogspot.com/2012/02/producer-consumer-design-pattern-with.html#ixzz2Uep1FION