すべてのプロダクト
Search
ドキュメントセンター

Key Management Service:KMSを使用してMaxCompute UDFを構築し、データを暗号化および復号化する

最終更新日:Apr 07, 2025

MaxCompute SQLでユーザーデータを暗号化および復号化する場合は、Key Management Service (KMS) インスタンスと一緒にユーザー定義関数 (UDF) を使用できます。 このトピックでは、KMSインスタンスを使用してMaxCompute UDFのデータを暗号化および復号化する方法について説明します。

概要

  1. 暗号化プロセスでは、KMSインスタンスクライアントが初期化され、UDF初期化中にデータキーが生成され、データキーはデータ処理中のエンベロープ暗号化に使用されます。 大量のデータを暗号化する場合は、シークレットを使用してデータキーを取得することをお勧めします。 これにより、過剰なデータキーの生成が防止される。

  2. 復号化プロセスでは、UDF初期化中にKMSインスタンスクライアント、スレッドプール、およびキャッシュが初期化され、データ処理中にデータキー暗号文が復号化されます。 そして、平文データ鍵を用いてデータを復号する。

  3. 暗号化されたデータを格納するフィールドは文字列型です。 データ形式は [データ鍵長 | 固定長4][データ鍵暗号文] [フィールド内のデータの暗号文] です。

重要

MaxCompute UDFの同時実行性は高いです。 キーを使用してUDFのデータを直接暗号化するのではなく、エンベロープ暗号化を実装してKMSインスタンスからデータキーを生成し、そのデータキーを使用してデータを暗号化することを推奨します。 エンベロープ暗号化の詳細については、「エンベロープ暗号化の使用」をご参照ください。

前提条件

  1. ソフトウェアキー管理タイプのKMSインスタンスまたはハードウェアキー管理タイプのKMSインスタンスが購入されます。 詳細については、「KMSインスタンスの購入と有効化」をご参照ください。

  2. 顧客マスターキー (CMK) が作成されます。 エンベロープ暗号化は、ガロア /カウンタモード (GCM) モードのみをサポートします。 したがって、CMKを作成するときは、[キータイプ] パラメーターに [対称キー] を選択する必要があります。 キーの仕様と暗号化モードの詳細については、「キーの種類と仕様」をご参照ください。

  3. アクセスポイントが作成され、クライアント鍵が保存され、KMSインスタンスの認証機関 (CA) 証明書が取得されます。 詳細については、「AAP を作成する」および「インスタンス CA 証明書を取得する」をご参照ください。

サンプルコード

説明

高いパフォーマンスが必要な場合は、JavaでSynchronousKeyEncryptUDFを使用することを推奨します。

Javaのサンプルコード

1. 依存関係のインストール

<dependencies>
        <dependency>
            <groupId>com.aliyun.odps</groupId>
            <artifactId>odps-sdk-udf</artifactId>
            <version>0.48.6-public</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>alibabacloud-kms-java-sdk</artifactId>
            <version>1.2.5</version>
            <exclusions>
                <exclusion>
                    <groupId>com.aliyun</groupId>
                    <artifactId>tea</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea</artifactId>
            <version>1.2.3</version>
        </dependency>
</dependencies>

2. UDFUtils.javaクラスの定義

package com.aliyun.kms.sample;

import com.aliyun.tea.utils.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class UDFUtils {

    private UDFUtils() {
        // do noting
    }

    public static boolean isNull(String value) {
        return StringUtils.isEmpty(value) || "\\N".equals(value);
    }

    public static String getSynchronousKeyVersion(String udfIdentify, String keyIdentify) {
        UUID uuid = null;
        byte[] bytes = null;
        if (StringUtils.isEmpty(keyIdentify)) {
            bytes = udfIdentify.getBytes(StandardCharsets.UTF_8);
            uuid = UUID.nameUUIDFromBytes(bytes);
            return uuid.toString();
        }
        bytes = (udfIdentify + "/" + keyIdentify).getBytes(StandardCharsets.UTF_8);
        uuid = UUID.nameUUIDFromBytes(bytes);
        return uuid.toString();
    }

    public static <K, V> Map<K, V> getLRUMap(int capacity) {
        return new LRUMap<K, V>(capacity);
    }

    private static class LRUMap<K, V> extends LinkedHashMap<K, V> {
        private final int capacity;
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private final Lock readLock = readWriteLock.readLock();
        private final Lock writeLock = readWriteLock.writeLock();

        public LRUMap(int capacity) {
            super(capacity, 0.75f, true);
            this.capacity = capacity;
        }

        @Override
        public V get(Object key) {
            readLock.lock();
            try {
                return super.get(key);
            } finally {
                readLock.unlock();
            }
        }

        @Override
        public V put(K key, V value) {
            writeLock.lock();
            try {
                return super.put(key, value);
            } finally {
                writeLock.unlock();
            }
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > capacity;
        }
    }

}

メソッドの説明

移動方法

説明

isNull

文字列が空の文字列かどうかをチェックします。

getSynchronousKeyVersion

UDF IDとUDFのversion入力パラメーターを組み合わせてバージョン番号を取得します。

3. UDFConstant.javaクラスの定義

説明

UDFConstant.javaクラスは、設定ファイルのパラメーター名を定義します。 パラメーター値の詳細については、「変数の設定」をご参照ください。

package com.aliyun.kms.sample;

public class UDFConstant {

    private UDFConstant() {
        // do nothing
    }
    public static final String KMS_UDF_CONF_NAME = "kms_udf_conf.properties";
    public static final String UDF_ENCRYPT_KEY_ID_KEY = "udf.kms.keyId";
    public static final String KMS_CLIENT_KEY_FILE_KEY = "udf.kms.clientkey.file";
    public static final String KMS_CLIENT_KEY_PASSWORD_KEY = "udf.kms.clientkey.password";
    public static final String KMS_INSTANCE_CA_FILE_KEY = "udf.kms.instance.ca.file";
    public static final String KMS_INSTANCE_ENDPOINT_KEY = "udf.kms.instance.endpoint";
    public static final String KMS_SYNCHRONOUS_SECRET_NAME_KEY = "udf.synchronous.secret.name";
    public static final String KMS_RAM_SECRET_NAME_KEY = "udf.ram.secret.name";
    public static final String KMS_ENDPOINT_KEY = "udf.kms.endpoint";
    public static final String ENCRYPT_DATA_KEY_FORMAT = "%04d";
    public static final int GCM_IV_LENGTH = 12;
    public static final int GCM_IV_BASE64_LENGTH = 16;
    public static final int GCM_TAG_LENGTH = 16;
}

パラメーターの説明

パラメーター

説明

KMS_UDF_CONF_NAME

設定ファイルの名前。

UDF_ENCRYPT_KEY_ID_KEY

KMSによって管理されるキーのID。

KMS_CLIENT_KEY_FILE_KEY

クライアント鍵ファイルの内容。

KMS_CLIENT_KEY_PASSWORD_KEY

クライアントキーのパスワード。

KMS_INSTANCE_CA_FILE_KEY

KMSインスタンスのCA証明書。

KMS_INSTANCE_ENDPOINT_KEY

KMSインスタンスのエンドポイント。

KMS_SYNCHRONOUS_SECRET_NAME_KEY

同期されるシークレットの名前。

KMS_RAM_SECRET_NAME_KEY

RAM (Resource Access Management) シークレットの名前。

KMS_ENDPOINT_KEY

共有ゲートウェイのエンドポイント。

4. 暗号化UDFの作成

説明
  • 大量のデータを含むテーブルの高性能暗号化UDFとして、com.aliyun.kms.sample.SynchronousKeyEncryptUDFクラスを使用することを推奨します。

  • テーブルに少量のデータが含まれていて、UDFの同時実行性が低い場合は、com.aliyun.kms.sample.EncryptUDFクラスを暗号化UDFとして使用できます。

SynchronousKeyEncryptUDFの例

SynchronousKeyEncryptUDFは、大量のデータと高い同時実行性を伴うシナリオに適しています。 ForSynchronousKeyEncryptUDFを使用して、KMSのシークレット管理機能を活用し、データキーとバージョン情報をシークレットとしてKMSに保存できます。 これにより、同じバージョンを持つ複数のキーを防ぎ、生成されるキーの数を減らし、パフォーマンスを向上させます。 データキーを生成するフローチャートを次の図に示します。

image
  1. xxxxSynchronousSecretという名前のシークレットの作成

    KMSコンソールで、またはKMS API操作を呼び出して、ジェネリックシークレットを作成します。 シークレット名はxxxxSynchronousSecretで、シークレット値はランダム値です。 ジェネリックシークレットは、データキーを格納するために使用されます。 UDFでe udf.synchronous.secret.nameパラメーターを指定して、シークレットを指定できます。 シークレットの作成方法の詳細については、「汎用シークレットの管理と使用」をご参照ください。

  2. xxxxRamSecretという名前のRAMシークレットの作成

    KMSコンソールで、またはKMS API操作を呼び出して、RAMシークレットを作成します。 RAMシークレットの値は、共有ゲートウェイの呼び出しに使用されるKMSアカウントのAccessKeyペアです。 RAMシークレットは、UDFがAPI操作を呼び出すときにID認証用に取得されます。 UDFでe udf.ram.secret.nameパラメーターを指定して、シークレットを指定できます。 RAMシークレットの作成方法とKMSアカウントのAccessKeyペアの作成方法の詳細については、「RAM シークレットの管理と使用」および「AccessKeyペアの作成」をご参照ください。 シークレット値の例

    {
    "AccessKeyId":"LTAI****************",
    "AccessKeySecret":"yourAccessKeySecret"
    }
  3. SynchronousKeyEncryptUDF.javaクラスの定義

    package com.aliyun.kms.sample;
    
    import com.aliyun.kms.kms20160120.TransferClient;
    import com.aliyun.kms.kms20160120.model.KmsConfig;
    import com.aliyun.kms.kms20160120.model.KmsRuntimeOptions;
    import com.aliyun.kms20160120.Client;
    import com.aliyun.kms20160120.models.*;
    import com.aliyun.odps.udf.ExecutionContext;
    import com.aliyun.odps.udf.UDF;
    import com.aliyun.odps.udf.UDFException;
    import com.aliyun.tea.TeaException;
    import com.aliyun.tea.utils.StringUtils;
    import com.google.gson.Gson;
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.Serializable;
    import java.nio.charset.StandardCharsets;
    import java.security.SecureRandom;
    import java.util.Map;
    import java.util.Properties;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class SynchronousKeyEncryptUDF extends UDF {
    
        private static Gson gson = new Gson();
        private static Client instanceClient;
        private static com.aliyun.kms.kms20160120.Client shareClient;
    
        private static Map<String, DataKeyObject> dataKeyObjectMap;
        private static Base64 base64 = new Base64();
    
        private String runTaskIdentify;
        private String keySecretName;
        private String keyId;
        private Properties properties = new Properties();
    
        @Override
        public void setup(ExecutionContext ctx) throws UDFException, IOException {
            super.setup(ctx);
            try {
                InputStream in = ctx.readResourceFileAsStream(UDFConstant.KMS_UDF_CONF_NAME);
                properties.load(in);
                // The dimension for generating a data key. If a data key is generated by project, you must use the getRunningProject method. If a data key is generated by table, you must use the getTableInfo method. 
                runTaskIdentify = ctx.getRunningProject();
                dataKeyObjectMap = new ConcurrentHashMap<>();
                keySecretName = properties.getProperty(UDFConstant.KMS_SYNCHRONOUS_SECRET_NAME_KEY);
                keyId = properties.getProperty(UDFConstant.UDF_ENCRYPT_KEY_ID_KEY);
    
                if (StringUtils.isEmpty(keySecretName)) {
                    throw new UDFException("the secret name is null");
                }
                buildInstanceClient(ctx);
                checkSecretExist();
                buildShareClient();
            } catch (Exception e) {
                throw new UDFException(e);
            }
        }
    
        public String evaluate(String keyIdentify, String data) {
            if (UDFUtils.isNull(data)) {
                return data;
            }
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            byte[] iv = null;
            byte[] cipherTextBytes = null;
            try {
                String keyVersion = UDFUtils.getSynchronousKeyVersion(runTaskIdentify, keyIdentify);
                if (!dataKeyObjectMap.containsKey(keyVersion)) {
                    dataKeyObjectMap.put(keyVersion, getDataKeyObject(keyVersion));
                }
                DataKeyObject dataKeyObject = dataKeyObjectMap.get(keyVersion);
                iv = new byte[UDFConstant.GCM_IV_LENGTH];
                SecureRandom random = new SecureRandom();
                random.nextBytes(iv);
                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
                SecretKeySpec keySpec = new SecretKeySpec(dataKeyObject.plaintextBytes, "AES");
                GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(UDFConstant.GCM_TAG_LENGTH * 8, iv);
                cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
                cipherTextBytes = cipher.doFinal(dataBytes);
                return dataKeyObject.encryptedDataKeyPart + base64.encodeAsString(iv) + base64.encodeAsString(cipherTextBytes);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        private DataKeyObject getDataKeyObject(String keyVersion) throws Exception {
            try {
                return getDataKeyObjectByGetSecretValue(keyVersion);
            } catch (TeaException e) {
                if ("Forbidden.ResourceNotFound".equals(e.code)) {
                    return buildDataKeyObject(keyVersion);
                }
            }
            return null;
        }
    
        private DataKeyObject getDataKeyObjectByGetSecretValue(String keyVersion) throws Exception {
            String secretData = getSecretValue(keyVersion);
            DataKeyPersistentObject dataKeyPersistentObject = gson.fromJson(secretData, DataKeyPersistentObject.class);
            byte[] plaintextBytes = base64.decode(dataKeyPersistentObject.plaintext);
            return new DataKeyObject(plaintextBytes, dataKeyPersistentObject.encryptedDataKeyPart);
        }
    
        private DataKeyObject buildDataKeyObject(String keyVersion) throws Exception {
            GenerateDataKeyRequest request = new GenerateDataKeyRequest();
            request.setKeyId(keyId);
            KmsRuntimeOptions runtimeOptions = new KmsRuntimeOptions();
            GenerateDataKeyResponse response = instanceClient.generateDataKeyWithOptions(request, runtimeOptions);
            String plaintext = response.getBody().plaintext;
            byte[] plaintextBytes = base64.decode(plaintext);
            String encryptedDataKeyPart = String.format(UDFConstant.ENCRYPT_DATA_KEY_FORMAT, response.getBody().ciphertextBlob.length()) + response.getBody().ciphertextBlob;
    
            DataKeyObject dataKeyObject = new DataKeyObject(plaintextBytes, encryptedDataKeyPart);
            DataKeyPersistentObject dataKeyPersistentObject = new DataKeyPersistentObject(plaintext, encryptedDataKeyPart);
            PutSecretValueRequest putSecretValueRequest = new PutSecretValueRequest();
            putSecretValueRequest.secretData = dataKeyPersistentObject.toJsonString();
            putSecretValueRequest.secretName = keySecretName;
            putSecretValueRequest.versionId = keyVersion;
    
            try {
                shareClient.putSecretValue(putSecretValueRequest);
            } catch (TeaException e) {
                if ("Rejected.ResourceExist".equals(e.code)) {
                    return getDataKeyObjectByGetSecretValue(keyVersion);
                }
            }
            return dataKeyObject;
        }
    
        @Override
        public void close() throws UDFException, IOException {
            super.close();
        }
    
        private void buildInstanceClient(ExecutionContext ctx) throws Exception {
            try {
                KmsConfig config = new KmsConfig();
                String clientKeyFileName = properties.getProperty(UDFConstant.KMS_CLIENT_KEY_FILE_KEY);
                byte[] clientKeyFileContent = ctx.readResourceFile(clientKeyFileName);
                config.setClientKeyContent(new String(clientKeyFileContent, StandardCharsets.UTF_8));
                String caFileName = properties.getProperty(UDFConstant.KMS_INSTANCE_CA_FILE_KEY);
                byte[] caFileContent = ctx.readResourceFile(caFileName);
                config.setCa(new String(caFileContent, StandardCharsets.UTF_8));
                config.setPassword(properties.getProperty(UDFConstant.KMS_CLIENT_KEY_PASSWORD_KEY));
                config.setEndpoint(properties.getProperty(UDFConstant.KMS_INSTANCE_ENDPOINT_KEY));
                instanceClient = new TransferClient(config);
            } catch (Exception e) {
                throw new UDFException(e);
            }
        }
    
        private void checkSecretExist() throws Exception {
            getSecretValue(null);
        }
    
        private String getSecretValue(String versionId) throws Exception {
            GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest();
            getSecretValueRequest.setSecretName(keySecretName);
            if (!StringUtils.isEmpty(versionId)) {
                getSecretValueRequest.setVersionId(versionId);
            }
            KmsRuntimeOptions runtimeOptions = new KmsRuntimeOptions();
            GetSecretValueResponse getSecretValueResponse = instanceClient.getSecretValueWithOptions(getSecretValueRequest, runtimeOptions);
            return getSecretValueResponse.body.secretData;
        }
    
        private void buildShareClient() throws Exception {
            String ramSecretName = properties.getProperty(UDFConstant.KMS_RAM_SECRET_NAME_KEY);
            GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest();
            getSecretValueRequest.setSecretName(ramSecretName);
            KmsRuntimeOptions runtimeOptions = new KmsRuntimeOptions();
            GetSecretValueResponse getSecretValueResponse = instanceClient.getSecretValueWithOptions(getSecretValueRequest, runtimeOptions);
            AccessKeyObject accessKeyObject = gson.fromJson(getSecretValueResponse.body.secretData, AccessKeyObject.class);
            com.aliyun.teaopenapi.models.Config shareConfig = new com.aliyun.teaopenapi.models.Config()
                    .setAccessKeyId(accessKeyObject.AccessKeyId)
                    .setAccessKeySecret(accessKeyObject.AccessKeySecret)
                    .setEndpoint(properties.getProperty(UDFConstant.KMS_ENDPOINT_KEY));
            shareClient = new com.aliyun.kms.kms20160120.Client(shareConfig);
        }
    
    
        private static class AccessKeyObject implements Serializable {
            private String AccessKeyId;
            private String AccessKeySecret;
        }
    
        private final static class DataKeyObject {
            private byte[] plaintextBytes;
            private String encryptedDataKeyPart;
    
            public DataKeyObject() {
            }
    
            public DataKeyObject(byte[] plaintextBytes, String encryptedDataKeyPart) {
                this.plaintextBytes = plaintextBytes;
                this.encryptedDataKeyPart = encryptedDataKeyPart;
            }
        }
    
        private final static class DataKeyPersistentObject implements Serializable {
            private String plaintext;
            private String encryptedDataKeyPart;
    
            public DataKeyPersistentObject() {
            }
    
            public DataKeyPersistentObject(String plaintext, String encryptedDataKeyPart) {
                this.plaintext = plaintext;
                this.encryptedDataKeyPart = encryptedDataKeyPart;
            }
    
            public String toJsonString() {
                return gson.toJson(this);
            }
        }
    
    }
    

    メインメソッドの説明

    移動方法

    説明

    セットアップ

    設定ファイルを読み込み、KMSインスタンスSDKとAlibaba Cloud SDKを初期化し、初期シークレットが存在するかどうかを確認します。

    buildInstanceClient

    KMSインスタンスSDKを初期化します。

    buildShareClient

    RAMシークレットに保存されているAccessKeyペアを取得し、Alibaba Cloud SDKを初期化します。

    getDataKeyObjectByGetSecretValue

    シークレットに格納されているキーを取得します。

    buildDataKeyObject

    新しいキーを生成し、そのキーをシークレットに保存します。

    評価

    データを暗号化し、暗号化されたデータを返します。

    説明

    KMSの次のAPI操作が使用されます。

    • KMSインスタンスAPI操作: GenerateDataKey操作を呼び出してデータキーを生成し、GetSecretValue操作を呼び出して秘密値を取得します。

    • KMS API操作: PutSecretValue操作は、秘密値を格納するために呼び出されます。

    KMSのAPI操作とAPI操作の呼び出し方法の詳細については、「APIリファレンス」および「SDKリファレンス」をご参照ください。

EncryptUDFの例

EncryptUDF.javaクラスの定義

package com.aliyun.kms.sample;

import com.aliyun.kms.kms20160120.TransferClient;
import com.aliyun.kms.kms20160120.model.KmsConfig;
import com.aliyun.kms.kms20160120.model.KmsRuntimeOptions;
import com.aliyun.kms20160120.Client;
import com.aliyun.kms20160120.models.GenerateDataKeyRequest;
import com.aliyun.kms20160120.models.GenerateDataKeyResponse;
import com.aliyun.odps.udf.ExecutionContext;
import com.aliyun.odps.udf.UDF;
import com.aliyun.odps.udf.UDFException;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Properties;

public class EncryptUDF extends UDF {

    private static Client client;
    private static String plaintext;
    private static byte[] plaintextBytes;
    private static String encryptedDataKeyPart;

    private static Base64 base64 = new Base64();

    @Override
    public void setup(ExecutionContext ctx) throws UDFException, IOException {
        super.setup(ctx);
        try {
            InputStream in = ctx.readResourceFileAsStream(UDFConstant.KMS_UDF_CONF_NAME);
            Properties properties = new Properties();
            properties.load(in);
            KmsConfig config = new KmsConfig();
            String clientKeyFileName = properties.getProperty(UDFConstant.KMS_CLIENT_KEY_FILE_KEY);
            byte[] clientKeyFileContent = ctx.readResourceFile(clientKeyFileName);
            config.setClientKeyContent(new String(clientKeyFileContent, StandardCharsets.UTF_8));
            String caFileName = properties.getProperty(UDFConstant.KMS_INSTANCE_CA_FILE_KEY);
            byte[] caFileContent = ctx.readResourceFile(caFileName);
            config.setCa(new String(caFileContent, StandardCharsets.UTF_8));
            config.setPassword(properties.getProperty(UDFConstant.KMS_CLIENT_KEY_PASSWORD_KEY));
            config.setEndpoint(properties.getProperty(UDFConstant.KMS_INSTANCE_ENDPOINT_KEY));
            String keyId = properties.getProperty(UDFConstant.UDF_ENCRYPT_KEY_ID_KEY);
            client = new TransferClient(config);
            GenerateDataKeyRequest request = new GenerateDataKeyRequest();
            request.setKeyId(keyId);
            KmsRuntimeOptions runtimeOptions = new KmsRuntimeOptions();
            GenerateDataKeyResponse response = client.generateDataKeyWithOptions(request, runtimeOptions);
            plaintext = response.getBody().plaintext;
            plaintextBytes = base64.decode(plaintext);
            encryptedDataKeyPart = String.format(UDFConstant.ENCRYPT_DATA_KEY_FORMAT, response.getBody().ciphertextBlob.length()) + response.getBody().ciphertextBlob;
        } catch (Exception e) {
            throw new UDFException(e);
        }
    }

    public String evaluate(String data) {
        if (UDFUtils.isNull(data)) {
            return data;
        }
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        byte[] iv = null;
        byte[] cipherTextBytes = null;
        try {
            iv = new byte[UDFConstant.GCM_IV_LENGTH];
            SecureRandom random = new SecureRandom();
            random.nextBytes(iv);
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(plaintextBytes, "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(UDFConstant.GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
            cipherTextBytes = cipher.doFinal(dataBytes);
            return encryptedDataKeyPart + base64.encodeAsString(iv) + base64.encodeAsString(cipherTextBytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() throws UDFException, IOException {
        super.close();
    }

}

メインメソッドの説明

移動方法

説明

セットアップ

設定ファイルを読み込み、KMSインスタンスSDKを初期化し、データキー暗号文を生成します。

評価

データを暗号化し、暗号化されたデータを返します。

説明

KMSの次のAPI操作が使用されます。

  • KMSインスタンスAPI: GenerateDataKey操作を呼び出して、データキーを生成します。

KMSのAPI操作とAPI操作の呼び出し方法の詳細については、「APIリファレンス」および「SDKリファレンス」をご参照ください。

5.復号化UDFの作成

DecryptUDF.javaクラスの定義

package com.aliyun.kms.sample;

import com.aliyun.dkms.gcs.openapi.models.Config;
import com.aliyun.kms.kms20160120.TransferClient;
import com.aliyun.kms.kms20160120.model.KmsConfig;
import com.aliyun.kms.kms20160120.model.KmsRuntimeOptions;
import com.aliyun.kms20160120.Client;
import com.aliyun.kms20160120.models.DecryptRequest;
import com.aliyun.kms20160120.models.DecryptResponse;
import com.aliyun.odps.udf.ExecutionContext;
import com.aliyun.odps.udf.UDF;
import com.aliyun.odps.udf.UDFException;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Properties;

public class DecryptUDF extends UDF {

    private static Client client;
    private static Map<String, byte[]> dataKeyMap;
    private static final int BASE_STEP=4;

    private static Base64 base64 = new Base64();

    @Override
    public void setup(ExecutionContext ctx) throws UDFException, IOException {
        super.setup(ctx);
        try {
            InputStream in = ctx.readResourceFileAsStream(UDFConstant.KMS_UDF_CONF_NAME);
            Properties properties = new Properties();
            properties.load(in);
            KmsConfig config = new KmsConfig();
            String clientKeyFileName = properties.getProperty(UDFConstant.KMS_CLIENT_KEY_FILE_KEY);
            byte[] clientKeyFileContent = ctx.readResourceFile(clientKeyFileName);
            config.setClientKeyContent(new String(clientKeyFileContent, StandardCharsets.UTF_8));
            String caFileName = properties.getProperty(UDFConstant.KMS_INSTANCE_CA_FILE_KEY);
            byte[] caFileContent = ctx.readResourceFile(caFileName);
            config.setCa(new String(caFileContent,StandardCharsets.UTF_8));
            config.setPassword(properties.getProperty(UDFConstant.KMS_CLIENT_KEY_PASSWORD_KEY));
            config.setEndpoint(properties.getProperty(UDFConstant.KMS_INSTANCE_ENDPOINT_KEY));
            dataKeyMap = UDFUtils.getLRUMap(1000);
            client = new TransferClient(config);
        } catch (Exception e) {
            throw new UDFException(e);
        }
    }

    public String evaluate(String data) {
        if (UDFUtils.isNull(data)) {
            return data;
        }
        if (data.length() <= BASE_STEP) {
            return data;
        }
        int dataLength = Integer.valueOf(data.substring(0, 4));
        if (data.length() <= BASE_STEP + dataLength) {
            return data;
        }
        String dataKeyCiphertext = data.substring(4, 4 + dataLength);
        try {

            byte[] dataKeyBytes = getDataKeyPlaintext(dataKeyCiphertext);
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(dataKeyBytes, "AES");
            byte[] iv = base64.decode(data.substring(4 + dataLength, 4 + dataLength + UDFConstant.GCM_IV_BASE64_LENGTH));
            byte[] cipherText = base64.decode(data.substring(4 + dataLength + UDFConstant.GCM_IV_BASE64_LENGTH));
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(UDFConstant.GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
            byte[] decryptedData = cipher.doFinal(cipherText);
            return new String(decryptedData, "UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private byte[] getDataKeyPlaintext(String dataKeyCiphertext) throws Exception {
        if (!dataKeyMap.containsKey(dataKeyCiphertext)) {
            synchronized (dataKeyCiphertext.intern()) {
                DecryptRequest request = new DecryptRequest();
                request.ciphertextBlob = dataKeyCiphertext;
                KmsRuntimeOptions runtimeOptions = new KmsRuntimeOptions();
                DecryptResponse response = client.decryptWithOptions(request, runtimeOptions);
                if (!dataKeyMap.containsKey(dataKeyCiphertext)) {
                    dataKeyMap.put(dataKeyCiphertext, base64.decode(response.getBody().plaintext));
                }
            }
        }
        return dataKeyMap.get(dataKeyCiphertext);
    }


    @Override
    public void close() throws UDFException, IOException {
        super.close();
    }
}

メインメソッドの説明

移動方法

説明

セットアップ

設定ファイルを読み取り、KMSインスタンスSDKを初期化します。

getDataKeyPlaintext

平文データキーを取得します。

評価

平文データキーを使用してデータを復号し、復号されたデータを返します。

説明

KMSの次のAPI操作が使用されます。

  • KMSインスタンスAPI: Decrypt操作を呼び出して、プレーンテキストデータキーを取得します。

KMSのAPI操作とAPI操作の呼び出し方法の詳細については、「APIリファレンス」および「SDKリファレンス」をご参照ください。

6. SQL UDFの作成

重要
  • SQL UDFを作成する前に、UDF実装クラスのJARパッケージをMaxComputeプロジェクトに追加する必要があります。

  • この例では、encrypt_udf、sync_key_encrypt_udf、およびdecrypt_udf関数が作成されます。 ビジネス要件に基づいてUDFを削除できます。 UDFを削除するには、JARパッケージとCREATE文を削除するだけです。

次のステートメントを使用してUDFを作成します。

# Add the JAR package of the UDF implementation class to the MaxCompute project.
ADD JAR encrypt_udf.jar;
ADD JAR sync_key_encrypt_udf.jar;
ADD JAR decrypt_udf.jar;
# Add the configuration file to the MaxCompute project.
ADD FILE kms_udf_conf.properties;
# Add the client key file of the KMS instance to the MaxCompute project.
ADD FILE clientkey_xxxx.json;
# Add the CA certificate file of the KMS instance to the MaxCompute project.
ADD FILE PrivateKmsCA_xxxx.pem;
# Create an encryption UDF named encrypt_udf.
CREATE FUNCTION encrypt_udf AS 'com.aliyun.kms.sample.EncryptUDF'
USING 'encrypt_udf.jar,kms_udf_conf.properties,clientkey_xxxx.json,PrivateKmsCA_xxxx.pem';
# Create a high-performance encryption UDF named sync_key_encrypt_udf.
CREATE FUNCTION sync_key_encrypt_udf AS 'com.aliyun.kms.sample.SynchronousKeyEncryptUDF'
USING 'sync_key_encrypt_udf.jar,kms_udf_conf.properties,clientkey_xxxx.json,PrivateKmsCA_xxxx.pem';
# Create a decryption UDF named decrypt_udf. 
CREATE FUNCTION decrypt_udf AS 'com.aliyun.kms.sample.DecryptUDF'
USING 'decrypt_udf.jar,kms_udf_conf.properties,clientkey_xxxx.json,PrivateKmsCA_xxxx.pem';

パラメーターの説明

実装クラス

JARパッケージ

UDF

説明

EncryptUDF.java

encrypt_udf.jar

encrypt_udf

暗号化UDF

SynchronousKeyEncryptUDF.java

sync_key_encrypt_udf.jar

sync_key_encrypt_udf

高性能暗号化UDF

DecryptUDF.java

decrypt_udf.jar

decrypt_udf

解読UDF

7.変数の設定

UDF実装クラスに必要な変数は、kms_udf_conf.properties設定ファイルに格納されます。 シークレット関連の設定が、高性能UDFの設定ファイルに追加されます。

高性能UDFの構成ファイル

# Configure variables.
# Specify the key ID.
udf.kms.keyId=key-xxxxxxxxxxxx
# Specify the name of the client key file.
udf.kms.clientkey.file=clientkey_xxxx.json
# Specify the password of the client key.
udf.kms.clientkey.password=xxxxxxxx
# Specify the CA certificate of the KMS instance.
udf.kms.instance.ca.file=PrivateKmsCA_xxxx.pem
# Specify the endpoint of the KMS instance.
udf.kms.instance.endpoint=xxxxx.cryptoservice.kms.aliyuncs.com
# Specify the name of the secret that is synchronized.
udf.synchronous.secret.name=xxxxSynchronousSecretName
# Specify the name of the RAM secret.
udf.ram.secret.name=xxxxRamSecret
# Specify the endpoint of the shared gateway.
udf.kms.endpoint=kms-vpc.cn-xxx.aliyuncs.com
説明

同期されるシークレットの名前、RAMシークレットの名前、共有ゲートウェイのエンドポイントなど、シークレット関連の設定が追加されます。

通常設定ファイル

# Configure variables.
# Specify the key ID.
udf.kms.keyId=key-xxxxxxxxxxxx
# Specify the name of the client key file.
udf.kms.clientkey.file=clientkey_xxxx.json
# Specify the password of the client key.
udf.kms.clientkey.password=xxxxxxxx
# Specify the CA certificate of the KMS instance.
udf.kms.instance.ca.file=PrivateKmsCA_xxxx.pem
# Specify the endpoint of the KMS instance.
udf.kms.instance.endpoint=xxxxx.cryptoservice.kms.aliyuncs.com

8.UDFの使用

-- Use a UDF to encrypt data.
SELECT encrypt_udf(column_name) FROM my_table;
-- Use a UDF to encrypt data. The value of version is the secret version, and the default value of version is null.
SELECT sync_key_encrypt_udf(version,column_name) FROM my_table;
-- Use a UDF to decrypt data.
SELECT decrypt_udf(column_name) FROM my_table;

Pythonのサンプルコード

1. requirements.txt関連の依存関係をインストールする

pyodps==0.11.6.2
alibabacloud-kms-python-sdk==1.1.3

2. kms_udf_utils.pyクラスを定義する

# coding=utf-8
import uuid
from collections import OrderedDict


def is_null(value):
    return len(value) == 0 or value == '\\N'


def generate_uuid(key_version):
    return uuid.uuid5(uuid.NAMESPACE_DNS, key_version)


def get_config_value(config_dict, dict_key):
    if dict_key not in config_dict:
        raise ValueError("can not find key[{}]" % dict_key)
    return config_dict[dict_key]


def properties_to_dict(cache_file):
    config_dict = {}
    for line in cache_file:
        line = line.strip()
        if not line:
            continue
        k, v = line.split("=")
        config_dict[k.strip()] = v.strip()
    return config_dict


def object_to_dict(obj):
    return obj.__dict__


class LRUCache(object):
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = OrderedDict()

    def get(self, key):
        if key not in self.cache:
            return -1
        value = self.cache.pop(key)  # Remove the item to update the order
        self.cache[key] = value  # Re-insert it to the end
        return value

    def put(self, key, value):
        if key in self.cache:
            self.cache.pop(key)  # Remove the existing entry to update the order
        elif len(self.cache) == self.capacity:
            self.cache.popitem(last=False)  # Remove the least recently used item
        self.cache[key] = value

    def contains_key(self, key):
        return key in self.cache

3. kms_udf_constant.pyクラスを定義する

# coding=utf-8
UDF_CONFIG_CACHE_FILE = "kms_udf_conf.properties"
KMS_CLIENT_KEY_FILE_KEY = "udf.kms.clientkey.file"
KMS_CLIENT_KEY_PASSWORD_KEY = "udf.kms.clientkey.password"
KMS_INSTANCE_ENDPOINT_KEY = "udf.kms.instance.endpoint"
KMS_INSTANCE_CA_FILE_KEY = "udf.kms.instance.ca.file"
KMS_SYNCHRONOUS_SECRET_NAME_KEY = "udf.synchronous.secret.name"
KMS_RAM_SECRET_NAME_KEY = "udf.ram.secret.name"
KMS_REGION_KEY = "udf.kms.regionId"
UDF_ENCRYPT_KEY_ID_KEY = "udf.kms.keyId"
ENCRYPT_DATA_KEY_FORMAT = "%04d"
GCM_IV_LENGTH = 12
GCM_TAG_LENGTH = 16
DEFAULT_NUMBER_OF_BYTES = 32

パラメータ説明

パラメーター

説明

UDF_CONFIG_CACHE_FILE

設定ファイルの名前。

KMS_CLIENT_KEY_FILE_KEY

クライアント鍵ファイルの内容。

KMS_CLIENT_KEY_PASSWORD_KEY

クライアントキーのパスワード。

KMS_INSTANCE_ENDPOINT_KEY

KMSインスタンスのエンドポイント。

KMS_INSTANCE_CA_FILE_KEY

KMSインスタンスのCA証明書。

KMS_SYNCHRONOUS_SECRET_NAME_KEY

同期されるシークレットの名前。

KMS_RAM_SECRET_NAME_KEY

RAMシークレットの名前。

KMS_ENDPOINT_KEY

共有ゲートウェイのエンドポイント。

UDF_ENCRYPT_KEY_ID_KEY

KMSによって管理されるキーのID。

4. 暗号化UDFの作成

encrypt_udf.pyクラスの定義

# coding=utf-8
import base64

from alibabacloud_kms20160120.models import GenerateDataKeyRequest
from alibabacloud_kms_kms20160120.models import KmsConfig, KmsRuntimeOptions
from alibabacloud_kms_kms20160120.transfer_client import TransferClient
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from odps.distcache import get_cache_file
import random
import string
import kms_udf_constant
import kms_udf_utils


class EncryptUDF(object):

    def __init__(self):
        cache_file = get_cache_file(kms_udf_constant.UDF_CONFIG_CACHE_FILE)
        self.config_dict = kms_udf_utils.properties_to_dict(cache_file)
        cache_file.close()
        kms_config = KmsConfig(
            protocol='https',
            client_key_file=kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_CLIENT_KEY_FILE_KEY),
            password=kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_CLIENT_KEY_PASSWORD_KEY),
            endpoint=kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_INSTANCE_ENDPOINT_KEY),
        )
        self.ca_file_path = kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_INSTANCE_CA_FILE_KEY)
        self.keyId = kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.UDF_ENCRYPT_KEY_ID_KEY)
        self.instance_client = TransferClient(kms_config=kms_config)
        resp = self.generate_data_key()
        self.encrypted_data_key_part = (kms_udf_constant.ENCRYPT_DATA_KEY_FORMAT %
                                        len(resp.body.ciphertext_blob) + resp.body.ciphertext_blob)
        self.plaintext = resp.body.plaintext
        self.ciphertext_blob = resp.body.ciphertext_blob

    def evaluate(self, data):
        if kms_udf_utils.is_null(data):
            return data
        iv = bytes(''.join(random.sample(string.ascii_letters + string.digits, kms_udf_constant.GCM_IV_LENGTH)),
                   encoding="utf-8")
        data_bytes = data.encode("utf-8")
        encryptor = Cipher(
            algorithms.AES(base64.b64decode(self.plaintext)),
            modes.GCM(iv),
        ).encryptor()
        ciphertext = encryptor.update(data_bytes) + encryptor.finalize()
        return self.encrypted_data_key_part + str(base64.b64encode(iv), "utf-8") + str(
            base64.b64encode(ciphertext + encryptor.tag), "utf-8")

    def generate_data_key(self):
        request = GenerateDataKeyRequest()
        request.key_id = self.keyId
        request.number_of_bytes = kms_udf_constant.DEFAULT_NUMBER_OF_BYTES
        runtime = KmsRuntimeOptions(
            ca=self.ca_file_path
        )
        return self.instance_client.generate_data_key_with_options(request, runtime)

説明

KMSの次のAPI操作が使用されます。

  • KMSインスタンスAPI: GenerateDataKey操作を呼び出して、データキーを生成します。

KMSのAPI操作とAPI操作の呼び出し方法の詳細については、「APIリファレンス」および「SDKリファレンス」をご参照ください。

5. 復号化UDFの作成

decrypt_udf.pyクラスの定義

# coding=utf-8
import base64

from alibabacloud_kms20160120 import models as kms_20160120_models
from alibabacloud_kms_kms20160120.models import KmsConfig, KmsRuntimeOptions
from alibabacloud_kms_kms20160120.transfer_client import TransferClient
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from odps.distcache import get_cache_file

import kms_udf_constant
import kms_udf_utils
from kms_udf_utils import LRUCache


class DecryptUDF(object):

    def __init__(self):
        cache_file = get_cache_file(kms_udf_constant.UDF_CONFIG_CACHE_FILE)
        self.config_dict = kms_udf_utils.properties_to_dict(cache_file)
        cache_file.close()
        kms_config = KmsConfig(
            protocol='https',
            client_key_file=kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_CLIENT_KEY_FILE_KEY),
            password=kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_CLIENT_KEY_PASSWORD_KEY),
            endpoint=kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_INSTANCE_ENDPOINT_KEY),
        )
        self.ca_file_path = kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.KMS_INSTANCE_CA_FILE_KEY)
        self.keyId = kms_udf_utils.get_config_value(self.config_dict, kms_udf_constant.UDF_ENCRYPT_KEY_ID_KEY)
        self.instance_client = TransferClient(kms_config=kms_config)
        self.data_key_cache = LRUCache(1000)

    def evaluate(self, data):
        if kms_udf_utils.is_null(data):
            return data
        if len(data) < 4:
            return data
        data_key_len = int(data[0:4])
        if len(data) < 4 + data_key_len:
            return data
        data_key_ciphertext = data[4:4 + data_key_len]
        if not self.data_key_cache.contains_key(data_key_ciphertext):
            decrypt_request = kms_20160120_models.DecryptRequest(
                ciphertext_blob=data_key_ciphertext
            )
            decrypt_runtime = KmsRuntimeOptions(
                ca=self.ca_file_path
            )
            decrypt_response = self.instance_client.decrypt_with_options(decrypt_request, decrypt_runtime)
            self.data_key_cache.put(data_key_ciphertext, base64.b64decode(decrypt_response.body.plaintext))
        data_key = self.data_key_cache.get(data_key_ciphertext)
        envelope_cipher_bytes = base64.b64decode(data[4 + data_key_len:])
        iv = envelope_cipher_bytes[0:kms_udf_constant.GCM_IV_LENGTH]
        cipher_bytes = envelope_cipher_bytes[
                   kms_udf_constant.GCM_IV_LENGTH:len(envelope_cipher_bytes) - kms_udf_constant.GCM_TAG_LENGTH]
        tag = envelope_cipher_bytes[len(envelope_cipher_bytes) - kms_udf_constant.GCM_TAG_LENGTH:]
        decryptor = Cipher(algorithms.AES(data_key), modes.GCM(iv, tag)).decryptor()
        return str(decryptor.update(cipher_bytes) + decryptor.finalize())
説明

KMSの次のAPI操作が使用されます。

  • KMSインスタンスAPI: Decrypt操作を呼び出して、プレーンテキストデータキーを取得します。

KMSのAPI操作とAPI操作の呼び出し方法の詳細については、「APIリファレンス」および「SDKリファレンス」をご参照ください。

6. SQL UDFの作成

重要

この例では、encrypt_udf関数とdecrypt_udf関数が作成されます。 ビジネス要件に基づいてUDFを削除できます。 UDFを削除するには、JARパッケージとCREATE文を削除するだけです。

# Add the preceding program and requirements.txt-related dependencies to the resource.
ADD PY decrypt_udf.py;
ADD PY encrypt_udf.py;
# Add the configuration file to the MaxCompute project.
ADD FILE kms_udf_conf.properties;
# Add the client key file of the KMS instance to the MaxCompute project.
ADD FILE clientkey_xxxx.json;
# Add the CA certificate file of the KMS instance to the MaxCompute project.
ADD FILE PrivateKmsCA_xxxx.pem;
# Create an encryption UDF.
CREATE FUNCTION encrypt_udf AS 'com.aliyun.kms.sample.EncryptUDF'
USING 'encrypt_udf.py,kms_udf_conf.properties,clientkey_xxxx.json,PrivateKmsCA_xxxx.pem';
# Create a decryption UDF.
CREATE FUNCTION decrypt_udf AS 'com.aliyun.kms.sample.DecryptUDF'
USING 'decrypt_udf.py,kms_udf_conf.properties,clientkey_xxxx.json,PrivateKmsCA_xxxx.pem';

パラメーターの説明

依存名

UDF

説明

encrypt_udf.py

encrypt_udf

暗号化UDF

decrypt_udf.py

decrypt_udf

解読UDF

7. 変数の設定

UDF実装クラスに必要な変数は、kms_udf_conf.properties設定ファイルに格納されます。

# Configure variables.
# Specify the key ID.
udf.kms.keyId=key-xxxxxxxxxxxx
# Specify the name of the client key file.
udf.kms.clientkey.file=clientkey_xxxx.json
# Specify the password of the client key.
udf.kms.clientkey.password=xxxxxxxx
# Specify the CA certificate of the KMS instance.
udf.kms.instance.ca.file=PrivateKmsCA_xxxx.pem
# Specify the endpoint of the KMS instance.
udf.kms.instance.endpoint=xxxxx.cryptoservice.kms.aliyuncs.com

8. UDFの使用

-- Use a UDF to encrypt data.
SELECT encrypt_udf(column_name) FROM my_table;
-- Use a UDF to decrypt data.
SELECT decrypt_udf(column_name) FROM my_table;

関連ドキュメント