2011年5月19日木曜日

AndroidのWebViewがhttpsでloadUrlすると真っ白になる件

Froyo以前のバージョンでは信頼できない証明書を受け入れるかどうかを選択する方法が提供されていないらしい。

しかも、警告も何もなしに読み込みを停止しちゃうので真っ白になる。

その解決方法がプライベートAPIを使ったものなのだけど、プライベートAPIの使い方が、「え?それでいいの?」って感じ。

Android WebView with https loadUrl shows blank/empty page

2011年4月11日月曜日

Apache HttpClientでJSONをgzipしてPOSTする


サーバ側はSinatraのview (erb)で書いたらこんな感じ。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>post /</title>
</head>
<body>
<div>post /</div>
<div>Content-Type: <%= request.content_type%></div>
<div>Content-Encoding: <%= request.env["HTTP_CONTENT_ENCODING"]%></div>
<textarea cols="80" rows="20" readonly><%=
  if "gzip" == request.env["HTTP_CONTENT_ENCODING"]
    begin
      Zlib::Inflate.inflate(request.body.read)
    rescue => e
      e.pretty_inspect
    end
  else
    request.body.read
  end
%></textarea>
</body>
</html>

2011年4月8日金曜日

AndroidでSystem.outとSystem.errをLogに転送する

Androidでは、System.outとSystem.errがLogに転送されているけれど、
任意のプライオリティと任意のタグでLogに転送するコードを作ってみた。

ついでに、はじめてGistを使ってみた。

Gistで保存するコードの先頭には、コピーライトとか書かない方がいいのかもなぁ。



おもいっきりプライベートなAPI使ってるから良くない子に分類されるだろう。

2011年3月31日木曜日

JavaでJSONしたい

JavaでJSONを扱うためのライブラリはたくさんあるなぁ

どれを使ったらいいんだろう?

ちょっと古い記事だけど、A Review of 5 Java JSON Libraries が参考になりそう。

この記事で取り上げているのは次の5つ。

  1. org.json
  2. Jackson
  3. XStream
  4. JsonMarshaller
  5. JSON.simple

www.json.orgに18ものライブラリが掲載されていると書いてあるが、
その中からどういう基準で5つを選んだのか気になるところ。

それでも、これらの中のどれかを状況に合わせて選択すれば今を生きていくには十分だろう。

「とりあえずJSON使っておく?」
みたいな状況だったら JSON.simple を使えば良さそう。

ところで、AndroidのSDKにはorg.json.JSONObjectなどが用意されている。
パッケージ名から類推して org.json のコードが使われているのかと思っていたら間違いだった。

[platform/libcore.git] / json / src / main / java / org / json / JSONObject.java
ここのソースのコメントに、

// Note: this class was written without inspecting the non-free org.json sourcecode.

と書いてある。
オリジナルってことかな?

2011年3月1日火曜日

JavaでXPathを使ったことがあった

<?xml version="1.0" encoding="UTF-8"?>
<hello>
    <item>
        <world attr="value1"/>
    </item>
    <item>
        <world attr="value2"/>
    </item>
</hello>
import java.io.File;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XPathSample {
    public static void main(String[] args) throws Exception {
        XPathExpression xpath =
            XPathFactory.newInstance()
            .newXPath()
            .compile("//world[@attr='value2']");

        Document document =
            DocumentBuilderFactory.newInstance()
            .newDocumentBuilder()
            .parse(new File("hello.xml"));

        NodeList nodeList = (NodeList) xpath.evaluate(document, XPathConstants.NODESET);
        for (int i = 0; i < nodeList.getLength(); ++i) {
            Node node = nodeList.item(i);
            NamedNodeMap attributes = node.getAttributes();
            String value = attributes.getNamedItem("attr").getNodeValue();
            System.out.println(value);
        }
    }
}

2011年2月28日月曜日

ついでにAESで暗号化・復号化するサンプルコード書いてみた

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Sample {
    public static class EncryptedData {
        public byte[] iv;
        public byte[] data;
    }

    public static void main(String[] args) throws Exception {
        // 鍵を生成する。
        // この鍵を保存して暗号文を交換する2者間で安全に共有する。
        final byte[] key = generateKey();
        print("[Key]", key);

        // 暗号化対象の平文
        final byte[] input = "hello world".getBytes();
        print("[Input]", input);

        // 鍵で平文を暗号化する。
        // 復号化するためには、「鍵」と「IV(initial vector)」と「暗号文」が必要。
        // 「鍵」は事前に安全に共有されていると仮定して、ここでは「IV」と「暗号文」を求める。
        final EncryptedData encrypted = encrypt(key, input);

        // 試しに暗号化したデータを表示してみる。
        print("[IV]", encrypted.iv);
        print("[Encrypted]", encrypted.data);

        // 「鍵」と「IV」と「暗号文」を渡して復号化する。 
        final byte[] decrypted = decrypt(key, encrypted.iv, encrypted.data);

        // 試しに復号化したデータを表示してみる。
        print("[Decrypted]", decrypted);

        // さらに、同じ鍵で同じ平文を暗号化・復号化してみる。
        final EncryptedData encrypted2 = encrypt(key, input);
        print("[IV(2)]", encrypted2.iv);
        print("[Encrypted(2)]", encrypted2.data);
        final byte[] decrypted2 = decrypt(key, encrypted.iv, encrypted.data);
        print("[Decrypted(2)]", decrypted2);
    }

    public static byte[] generateKey()
            throws NoSuchAlgorithmException
    {
        final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        //random.setSeed(getRandomSeedBytes()); // ここで本物の乱数で初期化する
        final KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256, random);
        return keyGen.generateKey().getEncoded();
    }

    public static EncryptedData encrypt(byte[] key, byte[] input)
            throws
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        //random.setSeed(getRandomSeedBytes()); // ここで本物の乱数で初期化する
        final SecretKey secretKey = new SecretKeySpec(key, "AES");
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
        final EncryptedData result = new EncryptedData();
        result.iv = cipher.getIV();
        result.data = cipher.doFinal(input);
        return result;
    }

    public static byte[] decrypt(byte[] key, byte[] iv, byte[] input)
            throws
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                InvalidAlgorithmParameterException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final SecretKey secretKey = new SecretKeySpec(key, "AES");
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        return cipher.doFinal(input);
    }

    public static void print(String tag, byte[] bs) {
        System.out.print(tag);
        for (int i = 0; i < bs.length; ++i) {
            if (i % 16 == 0) {
                System.out.println();
            }
            System.out.print(String.format(" %02X", bs[i]));
        }
        System.out.println();
    }
}

AndroidでRSAを使ってみた

RSAでの暗号化と復号化のシンプルなサンプルコードを書いてみた。

    public static byte[] encrypt(byte[] data)
            throws
                InvalidKeySpecException,
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final Key publicKey =
            KeyFactory
            .getInstance("RSA")
            .generatePublic(new X509EncodedKeySpec(PUBLIC_KEY_BYTES));
        final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }
    public static byte[] decrypt(byte[] data)
            throws
                InvalidKeySpecException,
                NoSuchAlgorithmException,
                NoSuchPaddingException,
                InvalidKeyException,
                IllegalBlockSizeException,
                BadPaddingException
    {
        final Key privateKey =
            KeyFactory
            .getInstance("RSA")
            .generatePrivate(new PKCS8EncodedKeySpec(PRIVATE_KEY_BYTES));
        final Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

ポイントは、Cipherのインスタンスを取得するときに、
フィードバックモードとパディングをちゃんと指定すること。

Cipher.getInstance("RSA") // これは悪い例!

同じJavaVM環境なら、こうやって書いても期待通りに動いてしまう。

フィードバックモードおよびパディングを省略すると、
プロバイダ固有のデフォルト値が使われると書いてある。

同じJavaVM環境ならプロバイダーが同じだからデフォルト値も同じなので問題がないというわけ。

ところが、
Sun(Oracle)のJREやJDKにバンドルされているJCEプロバイダのデフォルトはSunJCEで、
Androidの場合はBouncyCastleのようだ。

SunJCEの場合
"RSA"は"RSA/ECB/PKCS1Padding"
がデフォルトみたい。

BouncyCastleの場合は
"RSA"は"RSA/None/NoPadding"
がデフォルトみたい。

これらはたまたま手元の環境で動かしたらそうなっただけで、
デフォルト値が未来永劫変わらない保証はない。

というわけで、フィードバックモードとパディングを省略すると
サーバとAndroid端末の間で渡したデータを復号化できなくなると思った方がよい。