기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.
예제 오프라인 작업
비대칭 KMS 키 페어의 퍼블릭 키를 다운로드한 후 다른 사람과 공유하고 이를 사용하여 오프라인 작업을 수행할 수 있습니다.
요청, 응답, 날짜, 시간 및 인증된 사용자를 포함하여 모든 AWS CloudTrail 작업을 기록하는AWS KMS 로그는 AWS KMS 외부에서의 퍼블릭 키 사용을 기록하지 않습니다.
이 주제에서는 예제 오프라인 작업을 제공하고 오프라인 작업을 더 쉽게 하기 위해 AWS KMS에서 제공하는 도구에 대해 자세히 설명합니다.
공유 보안 비밀을 오프라인으로 도출
오프라인 작업, 즉 AWS KMS 외부의 작업에 사용하는 ECC 키 페어의 퍼블릭 키를 다운로드할 수 있습니다.
다음 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"` -
AWS KMS에서 ECC 키 계약 키 페어를 생성하고 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는 4 KB 이하 메시지에 대해 연방정보처리표준(FIPS) 204 표준
4 KB보다 큰 메시지에 서명하려면 메시지 사전 처리 단계를 AWS KMS 외부에서 수행해야 합니다. 이 해싱 단계는 NIST FIPS 204 섹션 6.2에 정의된 대로 64바이트 메시지 표현값 μ를 생성합니다.
AWS KMS에는 4 KB보다 큰 메시지에 사용할 수 있는 EXTERNAL_MU라는 메시지 유형이 있습니다. RAW 메시지 유형 대신 이를 사용하는 경우 AWS KMS:
-
해싱 단계를 이미 수행했다고 가정함
-
내부 해싱 프로세스 건너뛰기
-
메시지 크기와 관계없이 사용 가능
메시지를 검증할 때 사용하는 방법은 외부 시스템 또는 라이브러리의 크기 제한 여부와 64바이트 메시지 대푯값 μ 지원 여부에 따라 달라집니다.
-
메시지가 제한 크기보다 작은 경우
RAW메시지 유형을 사용하세요. -
메시지가 제한 크기보다 큰 경우 외부 시스템에서 생성한 대푯값 μ를 사용하세요.
다음 섹션에서는 AWS KMS를 사용해 메시지에 서명하고 OpenSSL을 사용해 메시지를 검증하는 방법을 설명합니다. AWS KMS에서 부과한 4 KB 메시지 크기 제한 이하 및 초과 메시지 모두에 대한 예시를 제공합니다. 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 미만인 메시지
4KB 미만의 메시지의 경우 AWS KMS과 함께 RAW 메시지 유형을 사용하세요. 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와는 다릅니다.
-
먼저 메시지 접두사를 구성하세요. 접두사는 도메인 구분자, 컨텍스트의 길이 및 컨텍스트를 포함합니다. 도메인 구분자와 컨텍스트 길이의 기본값은 0입니다.
-
메시지 앞에 메시지 접두사를 추가하세요.
-
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에서 정의한 기본 구분 ID, 1234567812345678을 사용합니다. 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 클래스를 사용하여 이러한 변환과 계산을 수작업으로 수행해야 합니다.
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 -inciphertext.der
기본적으로 SM2OfflineOperationHelper 클래스는 SM2DSA 작업에 대한 메시지 다이제스트를 생성할 때 기본 구분 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"); } }