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
-,

執筆者:

関連記事

CentOSで暗号鍵用のパスワードを生成

そこそこ長くて文字種の入り混じった強度の高いものを自分で考えるのは面倒です。 暗号化処理を使う際に必要なパスワード文字列を、mkpasswdコマンドでいい感じに生成出来るようにしておきます。 目次1 …

Jerseyで開発したRESTで任意リファラからのCORSアクセスを許可する

package jp.hoge.filter; import java.util.Properties; import javax.servlet.http.HttpServletRequest; i …

JUnitコードの自動生成も出来る古の神Eclipseプラグイン「CodeProAnalytix」は今でも使える

デスマーチPM「各社のPG進捗が遅れていてユニットテストを書く暇が全く無いんですよ、なにかいい手は有りませんかね・・・? 因みに本プロジェクトはカバレッジ100%で請け負っています!(キリッ)」 何故 …

pgcryptoで公開鍵暗号の動作確認

共通鍵暗号で暗号化されたデータはパスワードが漏洩すると復号される危険が高まるのに対し、公開鍵暗号で暗号化されたデータは秘密鍵とパスワードの二つが漏洩しないと復号できません。 APサーバとDBサーバ通信 …

Stripe+Java+Payment Request APIでApple Pay、Google Payを使ったテストWeb決済をしてみる

自分で作ったサービスを運用してチャリンチャリンしたい・・・エンジニアならこんな夢、一度は見たことがあるんじゃないでしょうか。 夢を実現する為、以前Stripeのcheckout.jsを使ったテストWe …

 

shingo.nakanishi
 

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