예제 오프라인 작업 - AWS Key Management Service

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

예제 오프라인 작업

비대칭 KMS 키 페어의 퍼블릭 키를 다운로드한 후 다른 사람과 공유하고 이를 사용하여 오프라인 작업을 수행할 수 있습니다.

요청, 응답, 날짜, 시간 및 인증된 사용자를 포함하여 모든 AWS KMS 작업을 기록하는AWS CloudTrail 로그는 AWS KMS 외부에서의 퍼블릭 키 사용을 기록하지 않습니다.

이 주제에서는 예제 오프라인 작업을 제공하고 오프라인 작업을 더 쉽게 하기 위해 AWS KMS에서 제공하는 도구에 대해 자세히 설명합니다.

공유 보안 비밀을 오프라인으로 도출

오프라인 작업, 즉 AWS KMS 외부의 작업에 사용하는 ECC 키 페어의 퍼블릭 키를 다운로드할 수 있습니다.

다음 OpenSSL 연습에서는 ECC KMS 키 페어의 퍼블릭 키와 OpenSSL로 생성된 프라이빗 키를 사용하여 AWS KMS 외부에서 공유 보안 비밀을 도출하는 한 가지 방법을 보여줍니다.

  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. 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}
  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}

SM2 키 페어를 사용한 오프라인 인증(중국 리전 전용)

AWS KMS 외부에서 SM2 퍼블릭 키로 서명을 인증하려면 고유한 ID를 지정해야 합니다. 원본 메시지 MessageType:RAWSign 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 버전 1.68과 호환되도록 설계되었습니다. 다른 버전에 대한 도움말은 bouncycastle.org를 참조하세요.

SM2OfflineOperationHelper 클래스

SM2 키를 사용하는 오프라인 작업을 돕기 위해 Java용 SM2OfflineOperationHelper 클래스에는 이 태스크를 수행하는 메서드가 있습니다. 이 도우미 클래스를 다른 암호화 공급자의 모델로 사용할 수 있습니다.

AWS KMS 내에서 원시 사이퍼텍스트 변환과 SM2DSA 메시지 다이제스트 계산은 자동으로 수행됩니다. 모든 암호화 공급자가 동일한 방식으로 SM2를 구현하는 것은 아닙니다. OpenSSL 버전 1.1.1 이상의 일부 라이브러리는 이러한 작업을 자동으로 수행합니다. AWS KMS는 OpenSSL 버전 3.0과의 테스트에서 이 동작을 확인했습니다. Bouncy Castle과 같은 라이브러리와 함께 다음 SM2OfflineOperationHelper 클래스를 사용하여 이러한 변환과 계산을 수작업으로 수행해야 합니다.

SM2OfflineOperationHelper 클래스는 다음 오프라인 작업에 대한 메서드를 제공합니다.

  • 메시지 다이제스트 계산

    오프라인 인증에 사용하거나 AWS KMS로 전달해 서명에 사용할 수 있는 메시지 다이제스트를 오프라인으로 생성하려면 calculateSM2Digest 메서드를 사용합니다. calculateSM2Digest 메서드는 SM3 해싱 알고리즘으로 메시지 다이제스트를 생성합니다. GetPublicKey API는 바이너리 형식으로 퍼블릭 키를 반환합니다. 바이너리 키는 Java PublicKey로 파싱해야 합니다. 파싱된 퍼블릭 키에 메시지를 제공합니다. 이 방법은 메시지와 기본 구분 ID, 1234567812345678를 자동으로 결합하지만 DEFAULT_DISTINGUISHING_ID 값을 변경하면 자체적인 구분 ID를 설정할 수 있습니다.

  • Verify

    오프라인에서 서명을 인증하려면 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

기본적으로 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"); } }