このエントリは2019/09/25現在の情報を基にしています。将来の機能追加・廃止に伴い記載内容との乖離が発生する可能性があります。
このエントリはAzure Key Vaultの3エントリの2回目である。
Azure Key Vault (1) 基礎
https://logico-jp.io/2019/09/19/azure-key-vault-1/
Azure Key Vault (2)キーを使って暗号・復号化する
https://logico-jp.io/2019/09/25/azure-key-vault-2/
Azure Key Vault (3) SDKのメソッド呼び出し回数の制限
https://logico-jp.io/2019/09/25/azure-key-vault-3/
上記エントリを踏まえて、Key Vaultで管理しているキーを使ってフレーズを暗号化・復号化してみる。Key Vaultを使うと、暗号に必要な鍵を露出させる必要なく、暗号化・復号化できる。なお、サンプルコードの言語はいつも通りJavaである。
Key ContainerとKeyの作成
キーコンテナを作成した上で、キーを作成する。キーコンテナの価格レベル(SKU)はどちらでもかまわない(今回はプレミアムを選択)。キーもまた、HSMで保護されたキーでも、ソフトウェア的に保護されたキーでもどちらでもかまわない(今回はHSM-RSAを選択)。
作成したら、キー識別子を確認しておく(この識別子はSDKを呼び出すときに必要)。今回は「許可された操作」として全ての操作を選択しているが、実際には必要最低限の操作のみ許可すべきである。

Azure Active Directory (以下、Azure AD) でサービスプリンシパルを作成
CLIやPowerShellなどで作成すると、クライアントシークレットまで一気に作成できるが、今回はAzure Portalで作成する。Azure ADのページで、管理 > アプリの登録をクリックし、表示された画面で[新規登録]をクリック。

[アプリケーションの登録]では、名前とサポートされているアカウントの種類を指定(リダイレクトURIは省略可能で、今回は指定しない)し、[登録]をクリック。

クライアントシークレットの作成
登録したアプリの画面で、管理 > 証明書とシークレット を選択すると、クライアントシークレットを追加できる画面が現れるので、[+新しいクライアントシークレット]をクリックして追加する。指定する項目は説明と有効期限。今回は1年にしておく。


画面が戻ると、値の場所にランダムな文字が入っているが、これがクライアントシークレット。この値は赤下線部の注意書きの通り、後から取得できないので、この時点でメモしておく。ここまでがAzure ADでの操作。

登録アプリのKey Vaultへのアクセス設定
Key Vaultの画面に戻って、設定 > アクセスポリシーをクリックして、画面に現れる[+アクセスポリシーの追加]をクリックして、先ほど登録したアプリに対するアクセスポリシーを設定する。

今回はキーのアクセス許可だけでよいので、暗号化操作 > 暗号化解除(つまり復号化、decrypt)と暗号化 (encrypt)のみを選択する。

[プリンシパルの選択]では、登録したアプリ(サービスプリンシパル)を検索して[選択]をクリック(今回の場合はkeyvault4logico)。選択後、[追加]をクリック。

アプリケーションの欄に追加されていることを確認し、[保存]をクリック。

アプリケーションの作成 (1.1.2までの場合)
以下はSDK 1.1.2までの場合であるがコードの流れは以下の通り。
- KeyVaultCredentials#doAuthenticate()を実装
- 認証の上、KeyVaultClientインスタンスを作成
- 暗号化対象のフレーズをKeyVaultClient#encryptもしくはencryptAsyncで暗号化
- 復号化対象のフレーズをKeyVaultClient#decryptもしくはdecryptAsyncで復号化
encryptに類似したメソッドにwrapKey (wrapKeyAsync) 、decryptに類似したメソッドにunwrapKey (unwrapKeyAsync) があるが、wrap/unrapは対称キー (symmetric key)を使うことに注意。
依存関係
pom.xmlには以下の依存関係を設定しておく。
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-keyvault</artifactId>
<version>1.1.2</version>
</dependency>
KeyVaultCredentials#doAuthenticate()を実装
これは以下のSDKリファレンスにサンプルコードがある。
KeyVaultCredentials Class
https://docs.microsoft.com/java/api/com.microsoft.azure.keyvault.authentication.keyvaultcredentials?view=azure-java-legacy&viewFallbackFrom=azure-java-stable
package kvsdk4java;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials;
import java.net.MalformedURLException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ClientSecretKeyVaultCredential extends KeyVaultCredentials {
private String clientId;
private String clientKey;
public ClientSecretKeyVaultCredential(String clientId, String clientKey) {
this.clientId = clientId;
this.clientKey = clientKey;
}
@Override
public String doAuthenticate(String authorization, String resource, String scope) {
AuthenticationResult token = getAccessTokenFromClientCredentials(authorization, resource, clientId, clientKey);
return token.getAccessToken();
}
private AuthenticationResult getAccessTokenFromClientCredentials(String authorization, String resource, String clientId, String clientKey) {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authorization, false, service);
ClientCredential credentials = new ClientCredential(clientId, clientKey);
Future<AuthenticationResult> future = context.acquireToken(resource, credentials, null);
result = future.get();
} catch (MalformedURLException | InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
service.shutdown();
}
if (result == null) {
throw new RuntimeException("Authentication results were null.");
}
return result;
}
}
認証の上、KeyVaultClientインスタンスを作成
上記サンプルコードのClientSecretKeyVaultCredentialのコンストラクタを呼び出してKeyVaultCredentialsインスタンスを取得し、KeyVaultClientインスタンスを作成する際にパラメータとして指定する。
KeyVaultCredentials credential = new ClientSecretKeyVaultCredential( AZURE_CLIENT_ID, AZURE_CLIENT_SECRET );
KeyVaultClient client = new KeyVaultClient(credential);
暗号化対象のフレーズをKeyVaultClient#encryptもしくはencryptAsyncで暗号化
KeyVaultClientインスタンスを作成できたら、encryptメソッドを呼び出して暗号化する。このときに、キー識別子、もしくはきーコンテナのURL、キー名称、バージョンの文字列(つまりキー識別子の構成要素)が必要。
byte[] byteText = textToEncrypt.getBytes("UTF-16");
KeyOperationResult encryptResult1 = client.encrypt(KEY_IDENTIFIER, JsonWebKeyEncryptionAlgorithm.RSA_OAEP, byteText);
encryptAsyncの場合はコールバックを指定する必要がある。
復号化対象のフレーズをKeyVaultClient#decryptもしくはdecryptAsyncで復号化
encryptメソッドと呼び出しパラメータは同じ。
KeyOperationResult decryptResult1 = client.decrypt(KEY_IDENTIFIER, JsonWebKeyEncryptionAlgorithm.RSA_OAEP, encryptResult1.result());
アプリケーションの作成 (SDK 4.0以降の場合)
インポート対象のパッケージや、メソッドが大きく変わっているので注意が必要。大きな違いとして、com.microsoft.azure.xxxからcom.azure.xxxにパッケージ名が変わっている。
依存関係
azure-keyvault-keysを追加しておく。
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-keyvault-keys</artifactId>
<version>4.0.0-preview.4</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.0.0-preview.3</version>
</dependency>
Default HTTP Clientの詳細は以下に記載がある。
Default HTTP Client
https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/keyvault/azure-keyvault-keys#default-http-client
クライアントインスタンスは、SecretClient、KeyClient、CryptographyClientなどと分かれていて、今回はCryptographyClient インスタンスを作成する必要がある。
DefaultAzureCredentialを使って認証する場合
DefaultAzureCredentialを使って認証する場合は、以下の環境変数を設定しておく。DefaultAzureCredentialが環境変数から必要な情報を読み取ってよろしくやってくれる。
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
- AZURE_TENANT_ID
ここで、AZURE_TENANT_IDは登録したアプリ(サービスプリンシパル)の概要で確認できる。

簡単のために、pom.xmlにシステムプロパティとして設定することもできる。
<systemProperties>
<systemProperty>
<key>AZURE_TENANT_ID</key>
<value>{AZURE_TENANT_ID}</value>
</systemProperty>
<systemProperty>
<key>AZURE_CLIENT_ID</key>
<value>{AZURE_CLIENT_ID}</value>
</systemProperty>
<systemProperty>
<key>AZURE_CLIENT_SECRET</key>
<value>{AZURE_CLIENT_SECRET}</value>
</systemProperty>
<systemProperties>
認証のコード例は以下を参照。
Authenticating with DefaultAzureCredential
https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/identity/azure-identity#authenticating-with-defaultazurecredential
以前のSDKと同じような方法で認証したい場合
以前のSDKと同様、ClientSecretCredentialを別途作成して認証し、CryptographyClient インスタンスを作成することもできる。
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId(AZURE_CLIENT_ID)
.clientSecret(AZURE_CLIENT_SECRET)
.tenantId(AZURE_TENANT_ID)
.build();
Authenticating a service principal with a client secret
https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/identity/azure-identity#authenticating-a-service-principal-with-a-client-secret
同期クライアントの場合と非同期クライアントの場合で、末尾のbuildメソッドが異なる。同期型の場合、buildClient()である。
CryptographyClient cryptographyClient = new CryptographyClientBuilder()
.credential(clientSecretCredential)
.keyIdentifier(KEY_IDENTIFIER)
.buildClient();
非同期クライアントの場合はbuildAsyncClient()であることに注意。
CryptographyAsyncClient cryptographyAsyncClient = new CryptographyClientBuilder()
.credential(clientSecretCredential)
.keyIdentifier(KEY_IDENTIFIER)
.buildAsyncClient();
暗号化と復号化
認証が通れば、あとはメソッドが異なるだけで流れは同じ。コード例は以下を参照。このSDKはReactorをベースにしているので、Reactiveな書式でコードを記載できる。
Encrypt/Decrypt (Sync)
https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/keyvault/azure-security-keyvault-keys/src/samples/java/com/azure/security/keyvault/keys/cryptography/EncryptDecryptOperations.java
Encrypt/Decrypt (Async)
https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/keyvault/azure-security-keyvault-keys/src/samples/java/com/azure/security/keyvault/keys/cryptography/EncryptDecryptOperationsAsync.java