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

執筆者:

関連記事

pgcryptoの有効化と共通鍵暗号の動作確認

PostgreSQLで「pgcrypto」拡張機能を有効にすると、PostgreSQLに保存するデータを暗号化出来るようになります。 INSERTする際、共通鍵暗号はpgp_sym_encrypt関数 …

JavaとPerlでTCPソケット通信

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

インターネット上に流れる通信をTSUBAMEで俯瞰する

目次1 TSUBAMEとは1.1 四半期ごとに統計を公開してくれる1.2 週間レポートもしてくれる TSUBAMEとは JPCERTコーディネーションセンター TSUBAME(インターネット定点観測シ …

Javascript(暗号化JSライブラリ「Forge」)とp12ファイルで署名値を作成、Javaで検証する

前回、送信データの改ざんを検知する為、簡易的なセキュリティトークンであるPKCS#12形式のファイルを作成しました。 One IT Thing  10 Pockets開発用のPKCS#12フ …

IVS、サロゲートペアが混じった文字列をJavaのWebアプリでワードカウントする

(結論:java.text.BreakIteratorは「サロゲートペア文字 + IVS」の8バイト文字も1文字としてカウントしてくれる) 先日、IPAmj明朝フォントを使ってIVSを含んだUnico …

 

shingo.nakanishi
 

東京在勤、1977年生まれ、IT職歴2n年、生涯現役技術者を目指しています。健康第一。