jawiki/latest 20170920/ のページ数の件>2204789

2カ月以上更新サボってたら3万件近くレコード増えてる。
うちのサイトに無数にあるwikipediaへのリンク、大分ずれちゃってるんじゃないかと心配。

自分の近況を少し。メインの「趣味と実益を兼ねたコーディング」は頓挫&迷走中。今一方針が定まらない。一方、完全に遊びでやってるコーディングはチマチマと暇を見つけては進行中。参照:前の前のエントリ
case6まで進んでるんだけど(近日公開)、ActionScriptってweb特化の言語じゃないからRSS拾ってきて表示するのも簡単なのね。怖ーw こっちはweb公開前提で書いてるから「RSS拾って来て表示」がブラウザ内だけで完結しない。サーバ側もcors設定する必要あるらしい。JQuery使ったりして簡単にできる方法もありそうだけど、今回もJavaScriptについては外部ライブラリ使わない方針なのでそっちは追及せず、かつF5アタック食らって大手のサイトさんから怒られるのも嫌なのでサーバ側でデータをキャッシュする方向で実装中。単純なペラ一ページのためだけに今はDB設計してる。
他にcase06で苦労したのはcanvasの「ボカシ」。canvas自体のAPIにはボカシ処理ってのは無いのね。可能にするプラグインはいっぱい有るけど、プラグインも使わない方針なので四苦八苦。アルゴリズム調べて実装いくつか試して、いわゆる多色の画像をぼかす処理はできるんだけど、今回やりたいのは単色の円のボカシ。試行錯誤してるうちに思いついたのが「ガウス分布」。昔Javaの勉強していたころ、教科書のデモでappletでガウス分布やったんだけど、あれは別の見方すると「単色のボカシ」だよなー、って思ってガウス分布のアルゴ思い出すためにググってたら、svgにはfilterってのがあって、その名も「feGaussianBlur」というそのまんまのfilterがあるとのこと。それをどうcanvasに適用するかsvgのお勉強をしてたら、これもやりたいことそのまんま書いてあるページ発見。svgというかベクター画像の処理について調べてると大体この方のページに行きつくなぁ。すごい。

物理デモってやっぱり楽しいな

南アフリカでアプリ1クリックしてもらえて約50円収入。たけー。見も知らぬ遠い国のお客様ありがとうございます。
で、UWPアプリ中断して始めたAndroidアプリも頓挫気味。だって過去8カ月やってきたことの繰り返しなんだもの。いい加減飽きちゃうよ。
あれ?って思ったのは前に開発してた時は皆無だったコードビハインドの自動生成がAndroidでも結構あるようになってきてること。バインディングとか組んでると顕著。コードの自動生成ってなんか生理的に嫌。”What you see is What you wrote” じゃないのかよ。だけどしょうがない。

そんで趣味のアプリ制作の息抜き(?)に、全く金にならない物理デモで遊び始めた。これじゃ宿題から逃げてる小学生だよw 確かに過去には「宿題から逃げてる小学生」を6年ぐらい続けたこともあったけど。
case01

この本を参考に、というかそのまんまの写経。Amazonの中古本で見つけた。送料込みで312円。内容超充実で安すぎ。定価で買ったとしても満足してたと思う。

言語はフラッシュとActionScriptなんだけど、物理表現はプログラム言語関係ないからね。ActionScriptのJavaScriptへの書き換えには苦労しないんだけど、1年離れてるJavaScriptの基本的な書き方で四苦八苦。
↑のデモ書くのに合計6時間かかった。まぁ、JavaScriptのリハビリと息抜きを兼ねて少しづつやっていこう。何よりデモって楽しいんだよね。複雑じゃなく手を動かして物を作ってる感。編み物やパズルみたいな感じ?

8/10追記: case00 case02
8/19追記: case03
8/22追記: case04
9/14追記: case05
10/12追記: case06
10/22追記: case07

UWP(C++/CX)→Java間のRSA+AES暗号化/復号化覚え書き

ここ何日か悪戦苦闘したので覚え書き。我ながら迂遠かつトリッキーな処理してるので、何をやってるのか忘れてしまう可能性が高いし。
なお、ここに記す処理の諸々は本番環境ではそのまま使う気は(当然)無いです。暗号については(も)ど素人なので、セキュリティ上の穴がある可能性が大であることは最初に述べておきます。

まずはRSA鍵生成。最初はJavaで生成するを見つけてその通りやろうとして、鍵データの保存/呼び出し方法で四苦八苦。
でもよく考えたらこの部分は普通にOpenSSL使えばいいんじゃないか?情報も多いし。参考

openssl genrsa -out private_key.pem
openssl rsa -pubout -in private_key.pem -out public_key.pem
openssl rsa -inform pem -outform der -pubin -in public_key.pem  -out public_key.der
openssl pkcs8 -topk8 -in private_key.pem -inform pem -nocrypt -out private_key.der -outform der

まず普通にpem形式で出力、そのあとJavaで扱えるようder形式ファイルも出力。上のコマンドはサーバ上で走らせたもの。Windowsマシンでやる場合はオプション指定が微妙に違うらしいので注意。詳しくは↑の参考サイトを。

次にRSA公開鍵をUWP(C++/CX)で扱える形にする。ここでかなりの試行錯誤。でもまさに欲しかった情報を書いてくださっているブログを発見。「Base64エンコードしたmodulusとpublicExponent」つまり文字列を二つ公開鍵から生成すれば良さそうだと理解。以下のJavaコードを書く。

//定数
private static final String CIPHER_ALGORITHM = "RSA";
private static final String CIPHER_MODE = CIPHER_ALGORITHM + "/ECB/PKCS1PADDING";
//まずキーファイル読み込み
private static byte[] readKeyFile(String path) throws IOException{
		byte[] data = null;
		FileInputStream in = new FileInputStream(path);
		data = new byte[in.available()];
		in.read(data);
		in.close();
		return data;
	}
//ファイルから公開鍵生成
private static RSAPublicKey gen_publicKey_RSA(String publicKeyPath_RSA) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException{
		byte[] keyData = readKeyFile(publicKeyPath_RSA);
		KeySpec keySpec = new X509EncodedKeySpec(keyData);
		KeyFactory keyFactory = KeyFactory.getInstance(CIPHER_ALGORITHM);
		Key publicKey = keyFactory.generatePublic(keySpec);
		return (RSAPublicKey)publicKey;
	}
//↑のブログを参考に先頭の符号ビットを削る処理
public static byte[] stripLeadingZeros(byte[] values) {
		if((values.length > 1) && (0x00 == values[0])){
			byte[] r = new byte[values.length - 1];
			System.arraycopy(values, 1, r, 0, values.length - 1);
                        return r;
		}else{
                        return values;
                }
}
//最後に文字列吐き出す処理。
private static void gen_modulus_and_pe(RSAPublicKey publickey){
	byte[] modulesBytes = Utils.stripLeadingZeros(publickey.getModulus().toByteArray());
	String modules = Base64.encodeBase64String(modulesBytes);
	String pe = Base64.encodeBase64String(publickey.getPublicExponent().toByteArray());
	System.out.println("------------------------------------------");
	System.out.println(modules);
	System.out.println(pe);
	System.out.println("------------------------------------------");
}

ここでいちいちファイルアクセスするのもどうなのと思ったので、Java側でも秘密鍵/公開鍵ともに文字列データとして扱えないかと試行錯誤開始。
stackoverflowを徘徊してると「秘密鍵の文字列化なんてダメ!絶対!!」的なレスがたくさん見られるのだけど、イマイチ意味が分からない。だってpemファイルも平文のファイルでしょ。「秘密鍵データにprivate_key.pemなんてわかりやすいファイル名つけて、中身は平文で保持」と「秘密鍵データをjarファイルの中に文字列定数として(今回は結局16進文字列にした)保持」の二つのやり方にセキュリテイ上の優劣があるのだろうか。
そう思って文字列化処理実装。参考

//文字列化
private static String KeyData_toHexString(String path) throws IOException{
		byte[] keyData = readKeyFile(path);
		return toHexString(keyData);
		
	}
private static String toHexString(byte[] data) {
        StringBuilder buf = new StringBuilder();
        for (byte d : data) {
            buf.append(String.format("%02X", d));
        }
        return buf.toString();
    }
//文字列からキー生成。
private static RSAPrivateKey gen_privateKey_RSA_from_hexString(String hexString) throws InvalidKeySpecException, NoSuchAlgorithmException{
		byte[] keyData = hexToByte(hexString);
		KeySpec keySpec = new PKCS8EncodedKeySpec(keyData);
		KeyFactory keyFactory = KeyFactory.getInstance(CIPHER_ALGORITHM);
		return (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
	}
	private static RSAPublicKey gen_publicKey_RSA_from_hexString(String hexString) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException{
		byte[] keyData = hexToByte(hexString);
		KeySpec keySpec = new X509EncodedKeySpec(keyData);
		KeyFactory keyFactory = KeyFactory.getInstance(CIPHER_ALGORITHM);
		Key publicKey = keyFactory.generatePublic(keySpec);
		return (RSAPublicKey)publicKey;
	}
private static byte[] hexToByte(String hexString){
		int size = hexString.length() / 2;
		byte[] bytes = new byte[size];
		for(int i = 0;i<size;i++){
			String tmp = hexString.substring(i * 2,i * 2 + 2);
			bytes[i] = (byte) Integer.parseInt(tmp,16);
		}
		return bytes;
	}

以降はJava側ではこれで生成した秘密鍵データと公開鍵データの16進文字列を定数として埋め込み利用する。正直、pemファイルでもderファイルでも何でも良かったんだけど、今後色んな環境でテストしていく中で毎回ファイルのパス指定を間違えて躓きそうだったのでこうしました。暗号化/復号化処理に必須の処理ではないです。

次はUWP(C++/CX)側の処理。ここから情報が少なすぎて大変だった。問題は暗号化処理のサンプルコードで頻出する「System.Security.Cryptography」名前空間がUWPでは使えないこと。「Windows::Security::Cryptography」使えとのこと。結果web空間上に山ほどあるサンプルコードが参考にならなくなる。とりあえず↑の参考ブログで使ってたRSACryptoServiceProviderはWindows::Security::Cryptographyにはないので、では何を使うのか?とググったりコードアシストで色々試したり。結果CryptographicBuffer.DecodeFromBase64Stringあたりを使えば行けそうとの感触を得てAPIドキュメントみるも、パラメータは「Base64 encoded input string.」一つだけ。え、modulusとpublicExponentの二つじゃないの?さらにググりまくった結果、回答発見。要するに「modulusとpublicExponentからcspBlobString生成しろ。その処理はUWP(C++/CX)使わなくても構わん。つまりSystem.Security.Cryptography名前空間使って良し」とのこと。よって次の処理部分だけC#でやってみる。初めてのC#。

 static String gen_cspBlobString()
        {
            String MODULUS = "Javaで生成した文字列";
            String PE = "Javaで生成した文字列";
            
            RSAParameters parameters = new RSAParameters();
            parameters.Modulus = System.Convert.FromBase64String(MODULUS);
            parameters.Exponent = System.Convert.FromBase64String(PE);

            System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
            rsa.ImportParameters(parameters);
            String cspBlobString = Convert.ToBase64String(rsa.ExportCspBlob(false));

            return cspBlobString;
        }

これでできた文字列を使い、以降はUWP(C++/CX)でRSA暗号化処理。

using namespace Windows::Storage::Streams;
using namespace Windows::Security::Cryptography;
String^ TEST_RSA_AES::MainPage::encryption_RSA(String^ target)
{
	String^ result = "";
	String^ cspBlobString = "↑のC#スクリプトで吐き出した文字列";
	IBuffer^ keyBlob = CryptographicBuffer::DecodeFromBase64String(cspBlobString);
	Core::AsymmetricKeyAlgorithmProvider^ rsa = Core::AsymmetricKeyAlgorithmProvider::OpenAlgorithm(Core::AsymmetricAlgorithmNames::RsaPkcs1);
	Core::CryptographicKey^ key = rsa->ImportPublicKey(keyBlob, Core::CryptographicPublicKeyBlobType::Capi1PublicKey);
	IBuffer^ plainBuffer = CryptographicBuffer::ConvertStringToBinary(target, BinaryStringEncoding::Utf8);
	IBuffer^ encryptedBuffer = Core::CryptographicEngine::Encrypt(key, plainBuffer, nullptr);


	result = CryptographicBuffer::EncodeToBase64String(encryptedBuffer);

	return result;
}

Java側で復号。web上の情報も多くてすぐ処理が組めるw

private static String decryptRSA(String encrypted_base64) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException{
		byte[] src = Base64.decodeBase64(encrypted_base64);
		Cipher cipher = Cipher.getInstance("RSA");
		byte[] keyData = Utils.hexToByte(privarteKeyText_RSA);
		KeySpec keySpec = new PKCS8EncodedKeySpec(keyData);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		Key privateKey = keyFactory.generatePrivate(keySpec);
		cipher.init(Cipher.DECRYPT_MODE, privateKey);
		byte[] resultBytes = cipher.doFinal(src);
		String result = new String(resultBytes,"UTF-8");
		return result;
	}

暗号化の定石、「平文の暗号化はAES共通鍵方式で行い、添付する共通鍵をRSA方式で暗号化する」処理を実装。試しに長めの平文をRSA暗号化処理させてみたら、開発に使ってるi7搭載機でも引っかかる。
何より今回の暗号化処理は最終的にはwebAPI実装の一部だから、POSTアクセスなら文字数制限ないけれどもあまりに長いクエリになるのは困る。以下UWP(C++/CX)でAES暗号化処理

Platform::Collections::Vector<Platform::String^>^ TEST_RSA_AES::MainPage::encryption_AES(Platform::String ^ target)
{
	
	
	String^  algoName = Core::SymmetricAlgorithmNames::AesCbcPkcs7;
	String^ pass = "パスワード";

	Core::SymmetricKeyAlgorithmProvider^ algo = Core::SymmetricKeyAlgorithmProvider::OpenAlgorithm(algoName);
	
	IBuffer^ salt = CryptographicBuffer::GenerateRandom(32);
	IBuffer^ textBuffer = CryptographicBuffer::ConvertStringToBinary(target, BinaryStringEncoding::Utf8);
	IBuffer^ digestBuffer = gen_digest(algo, pass, salt);
	IBuffer^ ivBuffer = CryptographicBuffer::GenerateRandom(algo->BlockLength);

	IBuffer^ encryptedBuffer = Core::CryptographicEngine::Encrypt(algo->CreateSymmetricKey(digestBuffer), textBuffer, ivBuffer);

	Platform::Collections::Vector<String^>^ result = ref new Platform::Collections::Vector<String^>;
	String^ encrypted = CryptographicBuffer::EncodeToBase64String(encryptedBuffer);
	result->Append(encrypted);
	OutputDebugString(("encrypted: " + encrypted + L"\r\n")->Data());
	String^ iv = CryptographicBuffer::EncodeToBase64String(ivBuffer);
	result->Append(iv);
	OutputDebugString(("iv: " + iv + L"\r\n")->Data());
	String^ digest = encryption_RSA(CryptographicBuffer::EncodeToBase64String(digestBuffer));
	result->Append(digest);
	OutputDebugString(("digest: " + digest + L"\r\n")->Data());
	return result;
}

Windows::Storage::Streams::IBuffer ^ TEST_RSA_AES::MainPage::gen_digest(Windows::Security::Cryptography::Core::SymmetricKeyAlgorithmProvider^ algo, Platform::String ^ pass, Windows::Storage::Streams::IBuffer ^ salt)
{
	uint32 SALT_ITERATION_COUNT = 10000;
	Core::KeyDerivationAlgorithmProvider^ pbkdf2 = Core::KeyDerivationAlgorithmProvider::OpenAlgorithm(Core::KeyDerivationAlgorithmNames::Pbkdf2Sha256);
	IBuffer^ passBuffer = CryptographicBuffer::ConvertStringToBinary(pass, BinaryStringEncoding::Utf8);
	Core::CryptographicKey^ key = pbkdf2->CreateKey(passBuffer);
	Core::KeyDerivationParameters^ parameters = Core::KeyDerivationParameters::BuildForPbkdf2(salt, SALT_ITERATION_COUNT);
	return Core::CryptographicEngine::DeriveKeyMaterial(key, parameters, 32);

}

結果、暗号化データのbase64文字列、ivのbase64文字列、digestのbase64文字列をRSA暗号化した文字列が生成される。
それをJava側で復号。

private static String decryptAES(String encrypted_base64,String iv_base64,String digest_base64) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, BadPaddingException{
		byte[] digest = Base64.decodeBase64(digest_base64);
		byte[] iv = Base64.decodeBase64(iv_base64);
		byte[] encryptedAES = Base64.decodeBase64(encrypted_base64);
		SecretKey secretKey = new SecretKeySpec(digest, "AES");
		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		cipher.init(Cipher.DECRYPT_MODE, secretKey,new IvParameterSpec(iv));

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		bos.write(cipher.update(encryptedAES));
		bos.write(cipher.doFinal());
		byte[] bytes = bos.toByteArray();
		return new String(bytes,0,bytes.length,"UTF-8");
	}
//こんな感じで使う
String dec = decryptAES(ENCRYPTED_TEXT_AES64, IV_64, decryptRSA(DIGEST_RSA64));
System.out.println(dec);

おそらく初回はこのAES復号処理はInvalidKeyExceptionで落ちる。JavaにはAES鍵長の制限があるらしい。参考
以上、今回実装した処理のすべて。このエントリ書くのに2時間かかったw リンクした参考先サイトのすべての皆さんに感謝。こんなコピペプログラマのやっつけで書いてるコードでない、理論に裏付けされた美しいコードが並んでます。暗号化について調べていてこのエントリにたどり着いた人がもしいたら、是非リンク先へ行ってみてください。