java security

ブラウザでRSA暗号化したデータをサーバで復号する(Angular + JSEncrypt、Spring MVC)【前編】

投稿日:2019年7月11日

セキュリティ的にクリティカルなデータをクライアントブラウザで暗号化保存するようにしてみます。

通信経路はHTTPSで暗号化されていてもスマホに重要なデータが平文で残っていたら珠に傷です。

環境

  • Windows 10
  • Eclipse 2019-03
  • Java 8
  • Maven

鍵ペアを作成

opensslが入っている環境でテキスト、バイナリの2形式で鍵ペアを作っておきます。KeyPairGeneratorで作ってもKeyStoreで作っても勿論OKです。

今回は以下の2ファイルを使います。

  1. サーバ側:private_key.pk8(バイナリ)
  2. クライアント側: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側を作っていきます。

-java, security
-,

執筆者:

関連記事

SNS他Webサービスの情報流出、セキュリティ事故に巻き込まれたかどうか調べる

FaceBookやCapital Oneなど、サービスに登録されたユーザ情報が漏洩する事件が取り沙汰されています。 「もしかしたら自分が登録した情報も漏れてるかもしれない」 そう思って気になったら、セ …

電子署名法で求められる「本人性」と「非改ざん性」の実現方法をざっくり理解する

2019年現在ではマイナンバーカードが流通し始め、インターネット上で公的な申請が完了できる体制が整いつつあります。 日本は古式ゆかしい申請制度がしっかりしているのでなかなか諸外国より電子化が進みません …

HTTPS開発環境用、自己署名証明書の作成

(2020/09/18追記) mkcertを使ってより簡単に完璧なSSL証明書を作ることができました。 ささっと開発用のHTTPSサーバを作りたい時はmkcert使った方が良いですね。 One IT …

主要ブラウザに保存させたパスワードの確認方法を比較してみる

アクセスしたサイトのパスワードをブラウザに覚えてもらったけどなんて入れたっけ? なんてことが稀にあったりします。各種ブラウザとも覚えさせたパスワードは後から確認が出来るのでそれぞれの方法を確認します。 …

JavaとPerlでTCPソケット通信

目次1 はじめに2 対象読者3 実装3.1 TCPサーバ(Perl)3.2 TCPクライアント(Java)3.3 実行4 まとめ はじめに 前回の続きです。One IT ThingJavaとPerlで …

 

shingo.nakanishi
 

東京在勤、1977年生まれ、IT職歴2n年、生涯技術者として楽しく生きることを目指しています。デスマに負けず健康第一。