今回は、以前作成したREST API通信のプログラムを、共通鍵暗号方式の1つであるAES(Advanced Encryption Standard)を利用して暗号化するようにしてみたので、そのサンプルプログラムを共有する。
共通鍵暗号方式については、以下のサイトを参照のこと。
https://www.infraexpert.com/study/security4.html
前提条件
下記記事のサンプルプログラムの作成と前提条件のデータ作成が完了していること。
サーバー側のプログラムの作成と実行
作成したサーバー側(demo_server)サンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
build.gradleの内容は以下の通りで、暗号化を行うための設定を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' //lombokの設定を追加 compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' //oracleを利用するための設定を追加 compile files('lib/ojdbc6.jar') implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //暗号化を行うための設定を追加 compile group: 'commons-codec', name: 'commons-codec', version: '1.8' } |
application.ymlの内容は以下の通りで、暗号化に必要な項目を追加している。 暗号化キー(key)は、16バイトの文字列を設定している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # ポート番号:8085で起動 server: port: 8085 # DB接続情報 spring: datasource: url: jdbc:oracle:thin:@localhost:1521:xe username: USER01 password: USER01 driverClassName: oracle.jdbc.driver.OracleDriver # 暗号化項目を設定 encrypt: algorithm: AES charset: utf-8 key: 1234567890abcdef |
コントローラクラスの内容は以下の通りで、encryptメソッドでAESによる暗号化を行っている。なお、暗号化に必要な項目値は、application.ymlから取得している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | package com.example.demo; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Sort; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.List; import static org.springframework.data.domain.Sort.Direction.ASC; @RestController public class DemoController { /** * 暗号化アルゴリズム */ @Value("${encrypt.algorithm}") private String encryptAlgorithm; /** * 暗号化時に利用する文字コード */ @Value("${encrypt.charset}") private String encryptCharset; /** * 暗号化時に利用する秘密鍵 */ @Value("${encrypt.key}") private String encrypyKey; /** * ユーザーデータテーブル(user_data)へアクセスするリポジトリ */ @Autowired private UserDataRepository repository; /** * ユーザーデータを全件取得する * @return ユーザーデータリスト(JSON形式) */ @GetMapping("/getUserDataList") public String getUserDataList(){ List<UserData> userDataList = repository.findAll(new Sort(ASC, "id")); // ユーザーデータが取得できなかった場合は、null値を返す if(userDataList == null || userDataList.size() == 0){ return null; } for(UserData userData : userDataList){ // 性別を表示用(男,女)に変換 userData.setSex("1".equals(userData.getSex()) ? "男" : "女"); // 名前・メモ・性別を暗号化 userData.setName(encrypt(userData.getName())); userData.setMemo(encrypt(userData.getMemo())); userData.setSex(encrypt(userData.getSex())); } // 取得したユーザーデータをJSON文字列に変換し返却 return getJsonData(userDataList); } /** * 引数の文字列を暗号化する * @param data 任意の文字列 * @return 暗号化後の文字列 */ private String encrypt(String data) { if(StringUtils.isEmpty(data)){ return ""; } String retVal = null; try { // 暗号化キーオブジェクトを生成 SecretKeySpec key = new SecretKeySpec( encrypyKey.getBytes(encryptCharset), encryptAlgorithm); // Cipherオブジェクトを生成し、暗号化キーオブジェクトで初期化 // 暗号化キーは、鍵長を16バイト・24バイト・32バイトのいずれかに設定する必要がある Cipher cipher = Cipher.getInstance(encryptAlgorithm); cipher.init(Cipher.ENCRYPT_MODE, key); // 引数の文字列を暗号化 byte[] byteResult = cipher.doFinal(data.getBytes(encryptCharset)); // Base64へエンコードし、暗号化文字列を返却 retVal = Base64.encodeBase64String(byteResult); } catch (Exception e) { e.printStackTrace(); } return retVal; } /** * 引数のオブジェクトをJSON文字列に変換する * @param data オブジェクトのデータ * @return 変換後JSON文字列 */ private String getJsonData(Object data){ String retVal = null; ObjectMapper objectMapper = new ObjectMapper(); try{ retVal = objectMapper.writeValueAsString(data); } catch (JsonProcessingException e) { System.err.println(e); } return retVal; } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-api-aes/demo_server
上記プログラムを作成し、Spring Bootアプリケーションを起動し、「http://localhost:8085/getUserDataList」にアクセスした結果は以下の通り。
名前(name)、性別(sex)、メモ(memo)を暗号化しているので、その内容が暗号化されていることが確認できる。
クライアント側のプログラムの作成と実行
作成したクライアント側(demo)サンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
build.gradleの内容は以下の通りで、暗号化を行うための設定を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' //lombokの設定を追加 compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' //暗号化を行うための設定を追加 compile group: 'commons-codec', name: 'commons-codec', version: '1.8' } |
application.ymlの内容は以下の通りで、暗号化に必要な項目を追加している。 暗号化キー(key)は、サーバー側と同じ文字列を設定している。
1 2 3 4 5 6 7 8 | # ポート番号:8084で起動 server: port: 8084 # 暗号化項目を設定 encrypt: algorithm: AES charset: utf-8 key: 1234567890abcdef |
コントローラクラスの内容は以下の通りで、decryptメソッドでAESで暗号化した内容を復号化している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | package com.example.demo; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.client.RestTemplate; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.List; @Controller public class DemoController { /** * 暗号化アルゴリズム */ @Value("${encrypt.algorithm}") private String encryptAlgorithm; /** * 暗号化時に利用する文字コード */ @Value("${encrypt.charset}") private String encryptCharset; /** * 暗号化時に利用する秘密鍵 */ @Value("${encrypt.key}") private String encrypyKey; /** * RestTemplateオブジェクト */ @Autowired private RestTemplate restTemplate; /** * ユーザーデータを取得し、初期表示画面に遷移する * @param model Modelオブジェクト * @return 初期表示画面へのパス */ @GetMapping("/") public String index(Model model){ // ユーザーデータリストをAPIで取得する ResponseEntity<List<UserData>> response = restTemplate.exchange( "http://localhost:8085/getUserDataList", HttpMethod.GET, null, new ParameterizedTypeReference<List<UserData>>() {}); List<UserData> userDataList = response.getBody(); // 各ユーザーデータを編集し、Modelに設定する for(UserData userData : userDataList){ // 名前・メモ・性別・生年月日を復号化 userData.setName(decrypt(userData.getName())); userData.setMemo(decrypt(userData.getMemo())); userData.setSex(decrypt(userData.getSex())); } model.addAttribute("userDataList", userDataList); return "index"; } /** * 引数の文字列を復号化する * @param data 任意の文字列 * @return 復号化後の文字列 */ private String decrypt(String data){ if(StringUtils.isEmpty(data)){ return ""; } String retVal = null; try { // 引数の文字列をBase64へデコード byte[] byteText = Base64.decodeBase64(data); // 暗号化キーオブジェクトを生成 SecretKeySpec key = new SecretKeySpec( encrypyKey.getBytes(encryptCharset), encryptAlgorithm); // Cipherオブジェクトを生成し、暗号化キーオブジェクトで初期化 // 暗号化キーは、鍵長を16バイト・24バイト・32バイトのいずれかに設定する必要がある Cipher cipher = Cipher.getInstance(encryptAlgorithm); cipher.init(Cipher.DECRYPT_MODE, key); // 引数の文字列を復号化 byte[] byteResult = cipher.doFinal(byteText); // 復号化後の文字列を返却 retVal = new String(byteResult, encryptCharset); } catch (Exception e) { e.printStackTrace(); } return retVal; } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-api-aes/demo
サーバー・クライアント両方のSpring Bootアプリケーションを起動し、「http:// (ホスト名):8084」とアクセスすると、以下の画面が表示されることが確認できる。
要点まとめ
- Cipherクラスを利用すると、共通鍵暗号方式の1つであるAESを利用した処理を簡単に実装できる。
- AESで利用する暗号化キーは、鍵長を16バイト・24バイト・32バイトのいずれかに設定し、暗号化/復号化で同じ暗号化キーを利用する必要がある。