Search
🙏

AWS KMS Encryption Context로 이더리움 시드 키 보호하기

블록체인 지갑 서비스나 핀테크 앱을 개발할 때 가장 중요한 것은 Seed Key(Private Key)의 관리입니다. 단순히 DB에 암호화해 저장하는 것을 넘어, "특정 맥락(Context)이 일치하지 않으면 AWS 관리자조차 복호화할 수 없게" 만드는 방법을 실습을 통해 알아볼 예정입니다.

Part 1. 실전 CLI 핸즈온

준비 사항

AWS CLI가 설치된 macOS 환경 (Linux/Windows는 base64 옵션이 다를 수 있음)
AWS KMS Key ID 준비

Step 1. KMS 키 정책(Key Policy) 설정

먼저 KMS 키가 올바른 꼬리표(Context)를 가진 요청만 허용하도록 정책을 설정해야 합니다. AWS 콘솔에서 해당 KMS 생성 후 KMS의 Key Policy를 편집합니다.
핵심은 Condition 블록입니다. kms:EncryptionContext:ProjectEthWallet일 때만 암호화/복호화를 허용합니다.
{ "Version": "2012-10-17", "Id": "key-policy-with-context", "Statement": [ { "Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::YOUR_ACCOUNT_ID:root" }, "Action": "kms:*", "Resource": "*" }, { "Sid": "Allow use of the key only with valid context", "Effect": "Allow", "Principal": { "AWS": "AWS": "arn:aws:iam::YOUR_ACCOUNT_ID:user/YOUR_KEY_USER" }, "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey" ], "Resource": "*", "Condition": { "StringEquals": { "kms:EncryptionContext:Project": "EthWallet" } } } ] }
HCL
복사
주의: Principal의 ARN은 본인의 IAM 사용자 정보로 변경하세요.

Step 2. 작업 환경 구성 및 시드 키 생성

터미널을 열고 실습 폴더로 이동한 뒤, 테스트용 시드 키 파일을 생성합니다. 여기서는 이더리움 Private key를 사용했습니다
0xabf82ff96b463e9d82b83cb9bb450fe87e6166d4db6d7021d0c71d7e960d5abe
HCL
복사
echo "0xabf82ff96b463e9d82b83cb9bb450fe87e6166d4db6d7021d0c71d7e960d5abe" > private_key.txt
HCL
복사

Step 3. 암호화 (Encrypt)

이제 KMS를 이용해 암호화를 진행합니다. 이때 --encryption-context 옵션을 반드시 포함해야 정책에 의해 승인됩니다.
aws kms encrypt \ --region us-east-1 \ --key-id YOUR-KEY-ID \ --plaintext fileb://private_key.txt \ --encryption-context Project=EthWallet \ --output text \ --query CiphertextBlob | base64 -D > private_key.encrypted
HCL
복사
주의: key-id를 각자 여러분의 kms key-id로 변경, region은 “us-east-1”인지 확인 체크 포인트:
ls -l private_key.encrypted 명령어로 파일 용량이 0이 아닌지 확인하세요.

Step 4. 복호화 테스트 (성공 vs 실패)

이 튜토리얼의 하이라이트입니다. 암호화된 파일을 다시 풀 때, Context 유무에 따른 차이를 확인해 봅시다.

Case 1: 성공 (올바른 Context 입력)

정책에서 요구하는 Project=EthWallet을 명시하면 정상적으로 원문이 출력됩니다.
aws kms decrypt \ --region us-east-1 \ --ciphertext-blob fileb://private_key.encrypted \ --encryption-context Project=EthWallet \ --output text \ --query Plaintext | base64 -D
HCL
복사
결과: 0xabf82ff96b463e9d82b83cb9bb450fe87e6166d4db6d7021d0c71d7e960d5abe

Case 2: 실패 (Context 누락 또는 불일치)

해커가 파일을 탈취했거나, 내부자가 권한을 오용하려 할 때 Context를 모르면 어떻게 될까요?
aws kms decrypt \ --region us-east-1 \ --ciphertext-blob fileb://private_key.encrypted \ --output text \ --query Plaintext
HCL
복사
결과: An error occurred (AccessDeniedException)...
해설: 키 사용 권한이 있는 Admin이라도, 문맥(Context)이 일치하지 않으므로 KMS가 복호화를 거부합니다.

Part 2. 심화: 클라이언트 사이드 봉투 암호화 (Envelope Encryption)

실제 프로덕션 환경에서는 보안 표준인 클라이언트 사이드 봉투 암호화를 적용해야 합니다.

왜 이 방식이 필요한가요?

AWS로 평문 데이터를 보내지 않고, 클라이언트(브라우저/앱) 내부에서 암호화를 완료한 뒤 결과물만 전송하기 때문입니다. AWS조차도 여러분의 시드 키 원문을 볼 수 없습니다.

전체 아키텍처 흐름

1. 암호화 및 저장 (Client → AWS)

1.
DEK 요청: 클라이언트가 KMS에 GenerateDataKey를 요청합니다.
2.
DEK 수신: KMS는 '평문 키(Plaintext DEK)'와 '암호화된 키(Encrypted DEK)' 두 개를 줍니다.
3.
로컬 암호화: 클라이언트는 메모리 상에서 '평문 키'로 시드 구문을 암호화합니다.
4.
(핵심 요소) 메모리 소거: 암호화 직후, 메모리에서 '평문 키'와 '시드 구문'을 즉시 삭제합니다.
5.
전송: '암호화된 시드'와 '암호화된 키'만 백엔드 DB에 저장합니다.

실습 준비 (Node.js)

npm init -y npm install @aws-sdk/client-kms
HCL
복사

Step 1. 암호화 스크립트 (encrypt.js)

이 스크립트는 KMS에서 키 발급 → 로컬 암호화 → 키 폐기 → 파일 저장 과정을 수행합니다.
encrypt.js 파일을 생성하고 아래 코드를 붙여넣으세요.
const { KMSClient, GenerateDataKeyCommand } = require("@aws-sdk/client-kms"); const fs = require('fs'); const crypto = require('crypto'); // 설정 const KEY_ID = 'alias/kms-key'; const REGION = 'us-east-1'; const CONTEXT = { Project: 'EthWallet' }; const client = new KMSClient({ region: REGION }); async function envelopeEncrypt(plainTextSecret) { console.log(`🔒 [1단계] KMS (${REGION})에 데이터 키(DEK) 생성을 요청합니다...`); // 1. 데이터 키 생성 요청 const command = new GenerateDataKeyCommand({ KeyId: KEY_ID, KeySpec: 'AES_256', EncryptionContext: CONTEXT }); const response = await client.send(command); const plaintextDek = Buffer.from(response.Plaintext); // 쓰고 버릴 키 const encryptedDek = Buffer.from(response.CiphertextBlob); // 저장할 키 console.log("⚡ [2단계] 로컬 메모리에서 AES-256-GCM 암호화를 수행합니다..."); // 2. 로컬 암호화 const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv('aes-256-gcm', plaintextDek, iv); let encryptedData = cipher.update(plainTextSecret, 'utf8'); encryptedData = Buffer.concat([encryptedData, cipher.final()]); const authTag = cipher.getAuthTag(); // 3. 키 폐기 (가장 중요!) plaintextDek.fill(0); console.log("🗑️ [3단계] 보안을 위해 평문 키를 메모리에서 삭제했습니다."); return { EncryptedData: encryptedData.toString('base64'), EncryptedDEK: encryptedDek.toString('base64'), IV: iv.toString('base64'), AuthTag: authTag.toString('base64') }; } (async () => { try { const mySeed = "0xabf82ff96b463e9d82b83cb9bb450fe87e6166d4db6d7021d0c71d7e960d5abe"; console.log(`원본 데이터: ${mySeed.substring(0, 10)}...`); const result = await envelopeEncrypt(mySeed); fs.writeFileSync('mock_database.json', JSON.stringify(result, null, 2)); console.log("✅ [4단계] 암호화된 데이터를 'mock_database.json'에 저장했습니다."); } catch (err) { console.error(err); } })();
HCL
복사
node encrypt.js
HCL
복사
원본 데이터: 0xabf82ff9... 🔒 [1단계] KMS (us-east-1)에 데이터 키(DEK) 생성을 요청합니다... 👉 사용 중인 키 ID: alias/kms-key ⚡ [2단계] 로컬 메모리에서 AES-256-GCM 암호화를 수행합니다... 🗑️ [3단계] 보안을 위해 평문 키를 메모리에서 삭제했습니다. ✅ [4단계] 암호화된 데이터를 'mock_database.json'에 저장했습니다.
HCL
복사

2. 복호화 및 사용 (AWS → Client)

1.
데이터 수신: 클라이언트가 DB에서 암호화된 데이터들을 받아옵니다.
2.
키 복구: 클라이언트는 '암호화된 키'를 KMS에 보내 Decrypt를 요청합니다. (이때 올바른 Encryption Context가 필수입니다.)
3.
원문 복구: KMS가 돌려준 '평문 키'로 시드 구문을 복호화하여 서명에 사용하고, 다시 메모리에서 즉시 삭제합니다.

Step 2. 복호화 스크립트 (decrypt.js)

이 스크립트는 DB 로드 → KMS로 키 복구 → 로컬 복호화 과정을 수행합니다.
decrypt.js 파일을 생성하고 아래 코드를 붙여넣으세요.
const { KMSClient, DecryptCommand } = require("@aws-sdk/client-kms"); const fs = require('fs'); const crypto = require('crypto'); // --- 설정 --- const REGION = 'us-east-1'; const CONTEXT = { Project: 'EthWallet' }; // 암호화 시 사용한 것과 동일해야 함 const client = new KMSClient({ region: REGION }); async function envelopeDecrypt() { // 1. DB 파일 읽기 if (!fs.existsSync('mock_database.json')) { console.log("❌ DB 파일이 없습니다. encrypt.js를 먼저 실행하세요."); return; } const dbRecord = JSON.parse(fs.readFileSync('mock_database.json', 'utf8')); // Base64 문자열을 다시 Buffer로 변환 const encryptedDek = Buffer.from(dbRecord.EncryptedDEK, 'base64'); const encryptedData = Buffer.from(dbRecord.EncryptedData, 'base64'); const iv = Buffer.from(dbRecord.IV, 'base64'); const authTag = Buffer.from(dbRecord.AuthTag, 'base64'); console.log("KEY 🔑 [1단계] KMS에 잠긴 키(DEK)의 해제를 요청합니다..."); try { // 2. KMS에 Decrypt 요청 const command = new DecryptCommand({ CiphertextBlob: encryptedDek, EncryptionContext: CONTEXT }); const response = await client.send(command); const plaintextDek = Buffer.from(response.Plaintext); console.log("🔓 [2단계] 로컬에서 데이터를 복구합니다..."); // 3. 로컬 복호화 (AES-256-GCM) const decipher = crypto.createDecipheriv('aes-256-gcm', plaintextDek, iv); decipher.setAuthTag(authTag); // GCM 모드는 Auth Tag 검증이 필수 let decrypted = decipher.update(encryptedData); decrypted = Buffer.concat([decrypted, decipher.final()]); // 4. 키 삭제 plaintextDek.fill(0); return decrypted.toString('utf8'); } catch (err) { console.error("❌ 복호화 실패! (Context 불일치 또는 권한 문제)"); throw err; } } // --- 실행부 --- (async () => { try { const recoveredText = await envelopeDecrypt(); console.log(`✅ [완료] 복구된 원본 데이터: ${recoveredText}`); console.log("🎉 봉투 암호화/복호화 성공!"); } catch (err) { // 에러 처리 } })();
HCL
복사
node decrypt.js
HCL
복사
컨택스트가 다르다면 아래와 같이 에러 발생
KEY 🔑 [1단계] KMS에 잠긴 키(DEK)의 해제를 요청합니다... ❌ 복호화 실패! (Context 불일치 또는 권한 문제)
HCL
복사
컨택스트가 같다면 아래와 같이 성공
KEY 🔑 [1단계] KMS에 잠긴 키(DEK)의 해제를 요청합니다... 🔓 [2단계] 로컬에서 데이터를 복구합니다... ✅ [완료] 복구된 원본 데이터: 0xabf82ff96b463e9d82b83cb9bb450fe87e6166d4db6d7021d0c71d7e960d5abe 🎉 봉투 암호화/복호화 성공!
HCL
복사

결론

AWS KMS의 Encryption Context봉투 암호화를 결합하면, 데이터 전송 구간(TLS), 저장 공간(DB), 그리고 키 관리 시스템(KMS)까지 삼중으로 보호되는 강력한 보안 아키텍처를 구축할 수 있습니다.