本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
離線操作範例
下載非對稱 KMS 金鑰對的公有金鑰之後,您可以與他人共用,並使用它來執行離線操作。
AWS CloudTrail 記錄每個 AWS KMS 操作的日誌,包括請求、回應、日期、時間和授權使用者,不會記錄 外部公有金鑰的使用 AWS KMS。
本主題提供範例離線操作,以及工具 AWS KMS 提供的詳細資訊,讓您更輕鬆地進行離線操作。
離線衍生共用秘密
您可以下載 ECC 金鑰對的公有金鑰,用於離線操作,也就是 外部的操作 AWS KMS。
下列 OpenSSL
-
在 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"`
-
在 中建立 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}
-
使用 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 支援 ML-DSA 簽署的對沖變體,如聯邦資訊處理標準 (FIPS) 204 標準
若要簽署大於 4 KB 的訊息,請在 外部執行訊息預先處理步驟 AWS KMS。此雜湊步驟會建立 64 位元組訊息代表 μ,如 NIST FIPS 204 第 6.2 節所定義。
AWS KMS 對於大於 4 EXTERNAL_MU
KB 的訊息, 具有呼叫 的訊息類型。當您使用此功能而非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
,請將訊息從外部預先堆疊到 64 位元組的代表 μ,如 NIST FIPS 204 第 6.2 節所定義,並將其傳遞給簽署或驗證操作。請注意,這與 NIST FIPS 204 第 5.4 節中定義的「雜湊前 MLDSA」或 HashML-DSA 不同。
-
首先,建構訊息字首。字首包含網域分隔符號、任何內容的長度,以及內容。網域分隔符號和內容長度的預設值為零。
-
在訊息前面加上訊息字首。
-
使用 SHAKE256 雜湊公有金鑰,並在步驟 2 的結果前面加上。
-
最後,雜湊步驟 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 金鑰對進行離線驗證 (僅限中國區域)
若要 AWS KMS 使用 SM2 公有金鑰驗證 外部的簽章,您必須指定辨別 ID。當您將原始訊息 傳遞MessageType:RAW
至 Sign API 時, AWS KMS 會使用 OSCCA 在 GM/T 0009-2012 中1234567812345678
定義的預設辨別 ID 。您無法在 AWS KMS中指定自己的辦別 ID。
不過,如果您在 外部產生訊息摘要 AWS,您可以指定自己的辨別 ID,然後將訊息摘要 傳遞MessageType:DIGEST
至 AWS KMS 以簽署。若要執行此操作,請變更 SM2OfflineOperationHelper
類別中的 DEFAULT_DISTINGUISHING_ID
值。您指定的辨別 ID 可以是最長 8,192 個字元的任何字串。 AWS KMS 簽署訊息摘要後,您需要訊息摘要或訊息,以及用來計算摘要以離線驗證的辨別 ID。
重要
SM2OfflineOperationHelper
參考程式碼設計為與 Bouncy Castle
SM2OfflineOperationHelper
類別
為了協助您使用 SM2 金鑰進行離線操作,適用於 Java 的 SM2OfflineOperationHelper
類別具有可為您執行任務的方法。您可以使用此輔助程式類別作為針對其他密碼提供者的模型。
在其中 AWS KMS,會自動進行原始加密文字轉換和 SM2DSA 訊息摘要計算。並非所有加密提供者都以相同的方式實施 SM2。有些程式庫,例如 OpenSSLSM2OfflineOperationHelper
類別和像是 Bouncy Castle
所以 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"); } }