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(); } }
2011年2月28日月曜日
ついでにAESで暗号化・復号化するサンプルコード書いてみた
AndroidでRSAを使ってみた
RSAでの暗号化と復号化のシンプルなサンプルコードを書いてみた。
ポイントは、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端末の間で渡したデータを復号化できなくなると思った方がよい。
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端末の間で渡したデータを復号化できなくなると思った方がよい。
2011年2月2日水曜日
ChromeでDesktop Notificationしてみた
Chromeのみ。
GmailがChrome Extensionなしで新着通知してくれるようになったのでやってみた。
- 許可を得ないと機能しない。
- 許可を得るためにはユーザのアクションが必要。(自動的に許可を求めることはできない)
ということで、初めてボタンをクリックした時は許可を与えてやるべし。
それから、もう一回ボタンをクリックするべし。
<input id="showNotification" type="button" value="show notification" onclick="showNotification()"> <script> if (!window.webkitNotifications) { document.getElementById('showNotification').disabled = "disabled"; } function showNotification() { if (window.webkitNotifications.checkPermission() == 0) { var icon = 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzc7Z4tgITpt9OABl3LvK8zrzmRQPhszoIVx67rc6XsNGSnK3S3ZT9BS-FpJFK8fjNFKprTax2o0-lRRMz6f1nqdlsQ5Lb50aDVQSAc5tW_eIa45kTAggLtD2-2mrkEZDrJDvzT2LSAg/'; var title = 'Chrome Notification Sample'; var message = 'This works only with Google Chrome Web Browser.'; var n = window.webkitNotifications.createNotification(icon, title, message); n.ondisplay = function() { setTimeout(function() { n.cancel(); }, 5000); }; n.show(); } else { window.webkitNotifications.requestPermission(); } } </script>
ラベル:
JavaScript
登録:
投稿 (Atom)