オフラインオペレーションの例 - AWS Key Management Service

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

オフラインオペレーションの例

非対称 KMS キーペアのパブリックキーをダウンロードしたら、それを他のユーザーと共有し、オフラインオペレーションの実行に使用することができます。

AWS CloudTrail リクエスト、レスポンス、日付、時刻、承認されたユーザーなど、すべての AWS KMS オペレーションを記録する ログは、 の外部でパブリックキーの使用を記録しません AWS KMS。

このトピックでは、オフラインオペレーションの例と、オフラインオペレーションを簡単にするためにツール AWS KMS が提供する詳細について説明します。

オフラインで共有シークレットを取得する

ECC キーペアのパブリックキーをダウンロードし、オフラインオペレーション、つまり AWS KMSの外部でのオペレーションに使用することができます。

次の OpenSSL チュートリアルでは、ECC KMS キーペアのパブリックキーと OpenSSL で作成されたプライベートキー AWS KMS を使用して、 外で共有シークレットを取得する 1 つの方法を示します。

  1. OpenSSL で ECC キーペアを作成し、 で使用する準備をします AWS KMS。

    // Create an ECC key pair in OpenSSL and save the private key in openssl_ecc_key_priv.pem export OPENSSL_CURVE_NAME="P-256" export KMS_CURVE_NAME="ECC_NIST_P256" export OPENSSL_KEY1_PRIV_PEM="openssl_ecc_key1_priv.pem" openssl ecparam -name ${OPENSSL_CURVE_NAME} -genkey -out ${OPENSSL_KEY1_PRIV_PEM} // Derive the public key from the private key export OPENSSL_KEY1_PUB_PEM="openssl_ecc_key1_pub.pem" openssl ec -in ${OPENSSL_KEY1_PRIV_PEM} -pubout -outform pem \ -out ${OPENSSL_KEY1_PUB_PEM} // View the PEM file containing the public key and extract the public key as a // Base64 encoded string into OPENSSL_KEY1_PUB_BASE64 for use with AWS KMS export OPENSSL_KEY1_PUB_BASE64=`cat ${OPENSSL_KEY1_PUB_PEM} | \ tee /dev/stderr | grep -v "PUBLIC KEY" | tr -d "\n"`
  2. で ECC キーアグリーメントキーペアを作成し AWS KMS 、OpenSSL で使用する準備をします。

    // Create a KMS key on the same curve as the key pair from step 1 // with a key usage of KEY_AGREEMENT // Save its ARN in KMS_KEY1_ARN. export KMS_KEY1_ARN=`aws kms create-key --key-spec ${KMS_CURVE_NAME} \ --key-usage KEY_AGREEMENT | tee /dev/stderr | jq -r .KeyMetadata.Arn` // Download the public key and save the Base64-encoded version in KMS_KEY1_PUB_BASE64 export KMS_KEY1_PUB_BASE64=`aws kms get-public-key --key-id ${KMS_KEY1_ARN} | \ tee /dev/stderr | jq -r .PublicKey` // Create a PEM file for the public KMS key for use with OpenSSL export KMS_KEY1_PUB_PEM="aws_kms_ecdh_key1_pub.pem" echo "-----BEGIN PUBLIC KEY-----" > ${KMS_KEY1_PUB_PEM} echo ${KMS_KEY1_PUB_BASE64} | fold -w 64 >> ${KMS_KEY1_PUB_PEM} echo "-----END PUBLIC KEY-----" >> ${KMS_KEY1_PUB_PEM}
  3. OpenSSL のプライベートキーとパブリック KMS キーを使用して、OpenSSL で共有シークレットを取得します。

    export OPENSSL_SHARED_SECRET1_BIN="openssl_shared_secret1.bin" openssl pkeyutl -derive -inkey ${OPENSSL_KEY1_PRIV_PEM} \ -peerkey ${KMS_KEY1_PUB_PEM} -out ${OPENSSL_SHARED_SECRET1_BIN}

ML-DSA キーペアによるオフライン検証

AWS KMS は、最大 4 KB バイトのメッセージについて連邦情報処理規格 (FIPS) 204 標準セクション 3.4 で説明されているように、ML-DSA 署名のヘッジされたバリアントをサポートします。

4 KB を超えるメッセージに署名するには、メッセージの前処理ステップを の外部で実行します AWS KMS。このハッシュステップでは、NIST FIPS 204 セクション 6.2 で定義されているように、64 バイトのメッセージ代表的な μ を作成します。

AWS KMS には、4 KB を超えるメッセージEXTERNAL_MUに対して というメッセージタイプがあります。RAW メッセージタイプの代わりにこれを使用すると、次のようになります AWS KMS。

  • ハッシュステップをすでに実行していることを前提としています

  • 内部ハッシュプロセスをスキップする

  • 任意のサイズのメッセージに対応

メッセージを検証する場合、使用する方法は、外部システムまたはライブラリのサイズ制限と、64 バイトのメッセージ代表 μ をサポートしているかどうかによって異なります。

  • メッセージのサイズ制限より小さい場合は、RAWメッセージタイプを使用します。

  • メッセージのサイズ制限より大きい場合は、外部システムで代表的な μ を使用します。

以下のセクションでは、 を使用してメッセージに署名 AWS KMS し、OpenSSL を使用してメッセージを検証する方法を示します。によって課される 4 KB のメッセージサイズ制限の下と上の両方のメッセージの例を示します AWS KMS。OpenSSL は、検証のためにメッセージサイズに制限を課しません。

どちらの例でも、まずパブリックキーを取得します AWS KMS。次の AWS CLI コマンドを使用します。

aws kms get-public-key \ --key-id _<1234abcd-12ab-34cd-56ef-1234567890ab>_ \ --output text \ --query PublicKey | base64 --decode > public_key.der

メッセージサイズが 4KB 未満

4 KB 未満のメッセージの場合は、 でRAWメッセージタイプを使用します AWS KMS。を使用できますがEXTERNAL_MU、サイズ制限内のメッセージには必要ありません。

次の AWS CLI コマンドを使用してメッセージに署名します。

aws kms sign \ --key-id _<1234abcd-12ab-34cd-56ef-1234567890ab>_ \ --message 'your message' \ --message-type RAW \ --signing-algorithm ML_DSA_SHAKE_256 \ --output text \ --query Signature | base64 --decode > ExampleSignature.bin

OpenSSL を使用してこのメッセージを確認するには、次のコマンドを使用します。

echo -n 'your message' | ./openssl dgst -verify public_key.der -signature ExampleSignature.bin

4KB を超えるメッセージサイズ

4KB を超えるメッセージに署名するには、EXTERNAL_MUメッセージタイプを使用します。を使用する場合はEXTERNAL_MU、NIST FIPS 204 セクション 6.2 で定義されている 64 バイトの代表的な μ にメッセージを外部で事前にハッシュし、署名または検証オペレーションに渡します。これは、NIST FIPS 204 セクション 5.4 で定義されている「Pre-hash MLDSA」または HashML-DSA とは異なることに注意してください。

  1. まず、メッセージプレフィックスを作成します。プレフィックスには、ドメイン区切り文字、コンテキストの長さ、およびコンテキストが含まれます。ドメイン区切り文字とコンテキストの長さのデフォルトは 0 です。

  2. メッセージプレフィックスをメッセージに追加します。

  3. SHAKE256 を使用してパブリックキーをハッシュし、ステップ 2 の結果に付加します。

  4. 最後に、ステップ 3 の結果をハッシュして 64 バイトの を生成しますEXTERNAL_MU

次の例では、OpenSSL 3.5 を使用して を構築しますEXTERNAL_MU

{ openssl asn1parse -inform DER -in public_key.der -strparse 17 -noout -out - 2>/dev/null | openssl dgst -provider default -shake256 -xoflen 64 -binary; printf '\x00\x00'; echo -n "your message" } | openssl dgst -provider default -shake256 -xoflen 64 -binary > mu.bin

mu.bin ファイルを作成したら、次のコマンドを使用して AWS KMS API を呼び出し、メッセージに署名します。

aws kms sign \ --key-id _<1234abcd-12ab-34cd-56ef-1234567890ab>_ \ --message fileb://mu.bin \ --message-type EXTERNAL_MU \ --signing-algorithm ML_DSA_SHAKE_256 \ --output text \ --query Signature | base64 --decode > ExampleSignature.bin

結果として得られる署名は、元のメッセージRAWの署名と同じです。同じ OpenSSL 3.5 コマンドを使用してメッセージを確認できます。

echo -n 'your message' | ./openssl dgst -verify public_key.der -signature ExampleSignature.bin

SM2 キーペアによるオフライン検証 (中国リージョンのみ)

SM2 パブリックキー AWS KMS を使用して の外部の署名を検証するには、識別 ID を指定する必要があります。未加工のメッセージ を Sign API MessageType:RAWに渡すと、 AWS KMS は 1234567812345678GM/T 0009-2012 の OSCCA で定義されたデフォルトの識別 ID を使用します。独自の識別 ID を AWS KMSで指定することはできません。

ただし、 の外部でメッセージダイジェストを生成する場合は AWS、独自の識別 ID を指定し、メッセージダイジェスト を MessageType:DIGESTに渡 AWS KMS して署名できます。これを行うには、SM2OfflineOperationHelper クラスの DEFAULT_DISTINGUISHING_ID 値を変更します。指定する識別 ID は、最大 8,192 文字の任意の文字列です。がメッセージダイジェスト AWS KMS に署名したら、メッセージダイジェストまたはメッセージ、およびダイジェストを計算してオフラインで検証するために使用される識別 ID が必要です。

重要

SM2OfflineOperationHelper リファレンスコードは Bouncy Castle バージョン 1.68 と互換性があるように設計されています。他のバージョンに関するヘルプについては、bouncycastle.org にアクセスしてください。

SM2OfflineOperationHelper クラス

SM2 キーのオフライン操作の支援向けに、Java の SM2OfflineOperationHelper クラスにはいくつかのタスク実行メソッドが用意されています。このヘルパークラスは、他の暗号化プロバイダのモデルとして使用できます。

内では AWS KMS、raw 暗号文変換と SM2DSA メッセージダイジェスト計算が自動的に行われます。どの暗号化プロバイダーも同じ方法で SM2 を実装しているとは限りません。OpenSSL バージョン 1.1.1 以降などの一部のライブラリは、これらのアクションを自動的に実行します。 は、OpenSSL バージョン 3.0 でのテストでこの動作 AWS KMS を確認しました。変換と計算を手動で実行するには、Bouncy Castle などのライブラリを持つ以下の SM2OfflineOperationHelper クラスを使用します。

SM2OfflineOperationHelper クラスは、次のオフラインオペレーションのためのメソッドを提供します。

  • メッセージダイジェストの計算

    オフライン検証に使用できるメッセージダイジェストをオフラインで生成したり、 に渡 AWS KMS して署名したりするには、 calculateSM2Digestメソッドを使用します。calculateSM2Digest メソッドは SM3 ハッシュアルゴリズムでメッセージダイジェストを生成します。GetPublicKey API は、パブリックキーをバイナリ形式で返します。バイナリキーを解析して Java PublicKey にする必要があります。解析されたパブリックキーをメッセージとともに提供します。このメソッドは、メッセージをデフォルトの識別 ID、1234567812345678 と自動的に組み合わせますが、DEFAULT_DISTINGUISHING_ID 値を変更して、独自の識別 ID を設定することもできます。

  • 検証

    署名をオフラインで検証するには、offlineSM2DSAVerify メソッドを使用します。offlineSM2DSAVerify メソッドは、指定された識別 ID から計算されたメッセージダイジェストと、指定された元のメッセージを使用してデジタル署名を検証します。GetPublicKey API は、パブリックキーをバイナリ形式で返します。バイナリキーを解析して Java PublicKey にする必要があります。解析されたパブリックキーに、元のメッセージと検証する署名を指定します。詳細については、「SM2 キーペアによるオフライン検証」を参照してください。

  • 暗号化

    プレーンテキストをオフラインで暗号化するには、offlineSM2PKEEncrypt メソッドを使用します。この方法では、暗号文が復号 AWS KMS できる形式になります。offlineSM2PKEEncrypt メソッドは、プレーンテキストを暗号化し、生成された生の暗号文を SM2PKE によって ASN.1 形式に変換します。GetPublicKey API は、パブリックキーをバイナリ形式で返します。バイナリキーを解析して Java PublicKey にする必要があります。解析したパブリックキーに、暗号化するプレーンテキストを指定します。

    変換を実行する必要があるかどうかわからない場合は、次の OpenSSL オペレーション使用して暗号文の形式をテストします。オペレーションが失敗した場合は、暗号文を ASN.1 形式に変換する必要があります。

    openssl asn1parse -inform DER -in ciphertext.der

デフォルトでは、SM2DSA オペレーションのメッセージダイジェストを生成するとき、SM2OfflineOperationHelper クラスはデフォルトの識別 ID、1234567812345678 を使用します。

package com.amazon.kms.utils; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.jce.interfaces.ECPublicKey; import java.util.Arrays; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.params.ParametersWithID; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; public class SM2OfflineOperationHelper { // You can change the DEFAULT_DISTINGUISHING_ID value to set your own distinguishing ID, // the DEFAULT_DISTINGUISHING_ID can be any string up to 8,192 characters long. private static final byte[] DEFAULT_DISTINGUISHING_ID = "1234567812345678".getBytes(StandardCharsets.UTF_8); private static final X9ECParameters SM2_X9EC_PARAMETERS = GMNamedCurves.getByName("sm2p256v1"); // ***calculateSM2Digest*** // Calculate message digest public static byte[] calculateSM2Digest(final PublicKey publicKey, final byte[] message) throws NoSuchProviderException, NoSuchAlgorithmException { final ECPublicKey ecPublicKey = (ECPublicKey) publicKey; // Generate SM3 hash of default distinguishing ID, 1234567812345678 final int entlenA = DEFAULT_DISTINGUISHING_ID.length * 8; final byte [] entla = new byte[] { (byte) (entlenA & 0xFF00), (byte) (entlenA & 0x00FF) }; final byte [] a = SM2_X9EC_PARAMETERS.getCurve().getA().getEncoded(); final byte [] b = SM2_X9EC_PARAMETERS.getCurve().getB().getEncoded(); final byte [] xg = SM2_X9EC_PARAMETERS.getG().getXCoord().getEncoded(); final byte [] yg = SM2_X9EC_PARAMETERS.getG().getYCoord().getEncoded(); final byte[] xa = ecPublicKey.getQ().getXCoord().getEncoded(); final byte[] ya = ecPublicKey.getQ().getYCoord().getEncoded(); final byte[] za = MessageDigest.getInstance("SM3", "BC") .digest(ByteBuffer.allocate(entla.length + DEFAULT_DISTINGUISHING_ID.length + a.length + b.length + xg.length + yg.length + xa.length + ya.length).put(entla).put(DEFAULT_DISTINGUISHING_ID).put(a).put(b).put(xg).put(yg).put(xa).put(ya) .array()); // Combine hashed distinguishing ID with original message to generate final digest return MessageDigest.getInstance("SM3", "BC") .digest(ByteBuffer.allocate(za.length + message.length).put(za).put(message) .array()); } // ***offlineSM2DSAVerify*** // Verify digital signature with SM2 public key public static boolean offlineSM2DSAVerify(final PublicKey publicKey, final byte [] message, final byte [] signature) throws InvalidKeyException { final SM2Signer signer = new SM2Signer(); CipherParameters cipherParameters = ECUtil.generatePublicKeyParameter(publicKey); cipherParameters = new ParametersWithID(cipherParameters, DEFAULT_DISTINGUISHING_ID); signer.init(false, cipherParameters); signer.update(message, 0, message.length); return signer.verifySignature(signature); } // ***offlineSM2PKEEncrypt*** // Encrypt data with SM2 public key public static byte[] offlineSM2PKEEncrypt(final PublicKey publicKey, final byte [] plaintext) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException { final Cipher sm2Cipher = Cipher.getInstance("SM2", "BC"); sm2Cipher.init(Cipher.ENCRYPT_MODE, publicKey); // By default, Bouncy Castle returns raw ciphertext in the c1c2c3 format final byte [] cipherText = sm2Cipher.doFinal(plaintext); // Convert the raw ciphertext to the ASN.1 format before passing it to AWS KMS final ASN1EncodableVector asn1EncodableVector = new ASN1EncodableVector(); final int coordinateLength = (SM2_X9EC_PARAMETERS.getCurve().getFieldSize() + 7) / 8 * 2 + 1; final int sm3HashLength = 32; final int xCoordinateInCipherText = 33; final int yCoordinateInCipherText = 65; byte[] coords = new byte[coordinateLength]; byte[] sm3Hash = new byte[sm3HashLength]; byte[] remainingCipherText = new byte[cipherText.length - coordinateLength - sm3HashLength]; // Split components out of the ciphertext System.arraycopy(cipherText, 0, coords, 0, coordinateLength); System.arraycopy(cipherText, cipherText.length - sm3HashLength, sm3Hash, 0, sm3HashLength); System.arraycopy(cipherText, coordinateLength, remainingCipherText, 0,cipherText.length - coordinateLength - sm3HashLength); // Build standard SM2PKE ASN.1 ciphertext vector asn1EncodableVector.add(new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(coords, 1, xCoordinateInCipherText)))); asn1EncodableVector.add(new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(coords, xCoordinateInCipherText, yCoordinateInCipherText)))); asn1EncodableVector.add(new DEROctetString(sm3Hash)); asn1EncodableVector.add(new DEROctetString(remainingCipherText)); return new DERSequence(asn1EncodableVector).getEncoded("DER"); } }