セキュリティ的にクリティカルなデータをクライアントブラウザで暗号化保存するようにしてみます。
通信経路はHTTPSで暗号化されていてもスマホに重要なデータが平文で残っていたら珠に傷です。
目次
環境
- Windows 10
- Eclipse 2019-03
- Java 8
- Maven
鍵ペアを作成
opensslが入っている環境でテキスト、バイナリの2形式で鍵ペアを作っておきます。KeyPairGeneratorで作ってもKeyStoreで作っても勿論OKです。
今回は以下の2ファイルを使います。
- サーバ側:private_key.pk8(バイナリ)
- クライアント側:public_key.pem(テキスト)
サーバ(Spring MVC側)実装
Spring Bootでサクッと雛型を作ります。EclipseでのSpring Bootアプリ作成が初見の場合は以下を参照(JSPは不要です)。
秘密鍵ファイルを配置
今回は検証なのでプロジェクト直下にprivate_key.pk8ファイルを置きます。
秘密鍵が盗まれることは誰かのクビが飛ぶことと同義です(嫌いな人だったら歓迎ですが)。HTTPからはアクセス出来ない安全な場所で管理してください。
入力パラメータクラスを作成
クライアントからリクエストされたJSONをマッピングするPOJOを作っておきます。
package com.example.demo;
public class EchoParam {
private String email;
private String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
コントローラを作成
復号しない/echo、復号する/decrypted_echoを作ります。
復号する方は秘密鍵ファイルを読み、送られてきた暗号化データを復号するようにしておきます。
リクエストされたJSONは引数EchoParamとして処理に入ってきます。@RequestMappingアノテーションの引数に
consumes = MediaType.APPLICATION_JSON_VALUE
などを付けてパラメータのタイプをキャストすることが出来ますが、デフォルトがJSONなので今回は指定は不要です。
package com.example.demo;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.Security;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EchoController {
public EchoController() {
Security.addProvider(new BouncyCastleProvider());
}
// 復号しないエコー
@RequestMapping(path = "/echo")
public EchoParam echo(@RequestBody EchoParam param) throws Exception {
return param;
}
// 復号するエコー
@RequestMapping(path = "/decrypted_echo")
public EchoParam decryptedEcho(@RequestBody EchoParam param) throws Exception {
String email = param.getEmail();
String password = param.getPassword();
System.out.println(email);
System.out.println(password);
byte[] b64DecodedPassword = Base64.getDecoder().decode(password);
byte[] decrypted = decrypt(b64DecodedPassword);
System.out.print("DECRYPTED: " + new String(decrypted));
EchoParam ret = new EchoParam();
ret.setEmail(email);
ret.setPassword(new String(decrypted));
return ret;
}
// 秘密鍵で暗号データを復号
public byte[] decrypt(byte[] source) throws Exception {
byte[] keyData = readKeyFile("private_key.pk8");
KeySpec keyspec = new PKCS8EncodedKeySpec(keyData);
KeyFactory keyfactory = KeyFactory.getInstance("RSA");
Key privateKey = keyfactory.generatePrivate(keyspec);
Cipher cipher = Cipher.getInstance("RSA//None/PKCS1PADDING", "BC");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(source);
}
// 秘密鍵ファイルprivate_key.derを読み込み
private static byte[] readKeyFile(String filename) throws IOException {
byte[] data = null;
FileInputStream in = new FileInputStream(filename);
data = new byte[in.available()];
in.read(data);
in.close();
return data;
}
}
pom.xmlを編集
暗号モードに「RSA/None/PKCS1PADDING」を使うのでBouncyCastleをpom.xmlに追加します。このモードを使う理由は以下の記事の通りです。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
</dependency>
実装完了です。
動作確認
Spring Bootアプリを実行してHTTPサーバを起動します。
クライアントがまだ出来ていないのでChrome拡張の「Advanced Rest Client」を使ってREST APIに接続してみます。
ARCを使っても暗号化は出来ないので復号しないエコー(/echo)に対するリクエストです
Method : POST
Request URL : http://localhost:8080/echo
Body content type : application/json
パラメータ : {“email”:”hoge@hogehoge.com”, “password”;”hogehoge”}
200が返ってくればOKです。/decrypted_echoにもアクセスして、暗号化されてないデータを復号しようとして起こる500 Internal Serverエラーが返ってくることを確認しておきます。
次はフロントエンド作成
HTTP POSTされた暗号化されたデータを含むJSONを復号する準備が出来ました。
次回はデータを公開鍵で暗号化してサーバに送信するAngular側を作っていきます。