{ "info": { "_postman_id": "b7e2e1d0-3c1a-4b0a-9d2e-crypto-manage-0001", "name": "CryptoModuleManage API (/manage/crypto)", "description": "CryptoModuleManager 기반 암호화모듈 진단 및 암복호화 테스트 API 샘플 모음.\n\n사용 전 확인사항:\n1. Collection Variables 의 baseUrl 을 실제 서버 주소로 변경\n2. cryptoName / dynamicCryptoName 을 DB(crypto_module_config)에 실제 등록된 모듈명으로 변경\n3. DYNAMIC 모듈은 keyDerivParams 의 contextKey 값과 dynamicContextKey 변수가 일치해야 함 (예: X-Api-Enc-Key)\n\n주의: 이 API는 원본 암호키(encKeyHex/decKeyHex/ivHex)를 절대 응답에 포함하지 않는다. 진단 응답은 메타데이터와 키 도출 전략(KeyDerivationStrategy) 로드 상태만 노출한다.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ { "key": "baseUrl", "value": "http://localhost:8080", "type": "string" }, { "key": "cryptoName", "value": "SAMPLE_STATIC", "type": "string", "description": "STATIC 키 방식으로 등록된 실제 cryptoName으로 교체" }, { "key": "dynamicCryptoName", "value": "SAMPLE_DYNAMIC", "type": "string", "description": "DYNAMIC 키 방식으로 등록된 실제 cryptoName으로 교체" }, { "key": "dynamicContextKey", "value": "X-Api-Enc-Key", "type": "string", "description": "DYNAMIC 모듈의 key_deriv_params.contextKey 값과 일치해야 함" }, { "key": "dynamicContextValue", "value": "clientKeyValue01", "type": "string" }, { "key": "plainText", "value": "테스트 평문입니다", "type": "string" }, { "key": "plainTextBase64", "value": "", "type": "string" }, { "key": "staticCipherTextBase64", "value": "", "type": "string" }, { "key": "dynamicCipherTextBase64", "value": "", "type": "string" } ], "item": [ { "name": "1. 진단 (Diagnostics)", "item": [ { "name": "전체 암호화모듈 목록 조회", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/manage/crypto/list", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "list"] }, "description": "등록된 전체 암호화모듈의 진단정보(메타데이터)를 반환한다. 원본 키는 포함되지 않는다." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "pm.test('data는 배열', () => pm.expect(json.data).to.be.an('array'));", "pm.test('원본 키 필드가 응답에 없어야 함', () => {", " const body = pm.response.text();", " pm.expect(body).to.not.include('encKeyHex');", " pm.expect(body).to.not.include('decKeyHex');", "});" ] } } ] }, { "name": "단일 암호화모듈 조회", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{cryptoName}}"] }, "description": "cryptoName 하나의 진단정보를 조회한다. STATIC 모듈은 keyDerivStrategy가 null, strategyLoaded가 false로 내려온다." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "pm.test('cryptoName 일치', () => pm.expect(json.data.cryptoName).to.eql(pm.collectionVariables.get('cryptoName')));" ] } } ] }, { "name": "DYNAMIC 모듈 조회 (전략 로드 확인)", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{dynamicCryptoName}}"] }, "description": "DYNAMIC 모듈은 resolveStrategy를 통해 키 도출 전략(KeyDerivationStrategy) 클래스가 정상 로드되는지 strategyLoaded/strategyClassName으로 확인할 수 있다." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "pm.test('keySourceType = DYNAMIC', () => pm.expect(json.data.keySourceType).to.eql('DYNAMIC'));", "pm.test('strategyLoaded = true', () => pm.expect(json.data.strategyLoaded).to.eql(true));", "pm.test('strategyClassName 존재', () => pm.expect(json.data.strategyClassName).to.be.a('string'));" ] } } ] }, { "name": "동적 키 캐시 목록 조회", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/manage/crypto/cache/dynamic-keys", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "cache", "dynamic-keys"] }, "description": "DYNAMIC 키 방식 모듈이 캐시한 키의 캐시-키 목록(예: 'cryptoName:contextValue')을 반환한다. 원본 키 값이 아니다. 아래 'DYNAMIC 키' 폴더의 암호화 요청을 먼저 실행한 뒤 호출하면 목록이 채워진다." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "pm.test('data는 배열', () => pm.expect(json.data).to.be.an('array'));" ] } } ] } ] }, { "name": "2. STATIC 키 암복호화 테스트", "item": [ { "name": "암호화 (encrypt)", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"plainTextBase64\": \"{{plainTextBase64}}\"\n}" }, "url": { "raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}/test/encrypt", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{cryptoName}}", "test", "encrypt"] }, "description": "STATIC 키 방식 모듈로 평문(Base64)을 암호화한다. runtimeContext 없이 호출한다." }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "const plainText = pm.collectionVariables.get('plainText');", "pm.collectionVariables.set('plainTextBase64', btoa(unescape(encodeURIComponent(plainText))));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "pm.test('cipherTextBase64 존재', () => pm.expect(json.data.cipherTextBase64).to.be.a('string'));", "pm.test('평문 base64와 달라야 함', () => pm.expect(json.data.cipherTextBase64).to.not.eql(pm.collectionVariables.get('plainTextBase64')));", "pm.collectionVariables.set('staticCipherTextBase64', json.data.cipherTextBase64);" ] } } ] }, { "name": "복호화 (decrypt) — 위 암호화 결과 사용", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"cipherTextBase64\": \"{{staticCipherTextBase64}}\"\n}" }, "url": { "raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}/test/decrypt", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{cryptoName}}", "test", "decrypt"] }, "description": "바로 위 '암호화 (encrypt)' 요청에서 저장한 staticCipherTextBase64를 복호화하여 원문과 일치하는지 확인한다. 반드시 암호화 요청을 먼저 실행할 것." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "const decoded = decodeURIComponent(escape(atob(json.data.plainTextBase64)));", "pm.test('원문과 일치 (라운드트립)', () => pm.expect(decoded).to.eql(pm.collectionVariables.get('plainText')));" ] } } ] } ] }, { "name": "3. DYNAMIC 키 암복호화 테스트", "item": [ { "name": "암호화 (encrypt, runtimeContext 포함)", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"plainTextBase64\": \"{{plainTextBase64}}\",\n \"runtimeContext\": {\n \"{{dynamicContextKey}}\": \"{{dynamicContextValue}}\"\n }\n}" }, "url": { "raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}/test/encrypt", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{dynamicCryptoName}}", "test", "encrypt"] }, "description": "DYNAMIC 키 방식 모듈로 runtimeContext(예: 헤더값)를 전달하여 키를 도출한 뒤 암호화한다. dynamicContextKey는 DB에 등록된 key_deriv_params.contextKey와 일치해야 한다." }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "const plainText = pm.collectionVariables.get('plainText');", "pm.collectionVariables.set('plainTextBase64', btoa(unescape(encodeURIComponent(plainText))));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "pm.test('cipherTextBase64 존재', () => pm.expect(json.data.cipherTextBase64).to.be.a('string'));", "pm.collectionVariables.set('dynamicCipherTextBase64', json.data.cipherTextBase64);" ] } } ] }, { "name": "복호화 (decrypt, 동일 runtimeContext 필요)", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"cipherTextBase64\": \"{{dynamicCipherTextBase64}}\",\n \"runtimeContext\": {\n \"{{dynamicContextKey}}\": \"{{dynamicContextValue}}\"\n }\n}" }, "url": { "raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}/test/decrypt", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{dynamicCryptoName}}", "test", "decrypt"] }, "description": "암호화 시 사용한 것과 동일한 runtimeContext를 전달해야 동일한 키가 도출되어 복호화에 성공한다. dynamicContextValue를 바꿔서 보내면 키가 달라져 복호화가 실패(예외)하는 것도 확인해볼 수 있다." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = true', () => pm.expect(json.success).to.eql(true));", "const decoded = decodeURIComponent(escape(atob(json.data.plainTextBase64)));", "pm.test('원문과 일치 (라운드트립)', () => pm.expect(decoded).to.eql(pm.collectionVariables.get('plainText')));" ] } } ] } ] }, { "name": "4. 에러 케이스", "item": [ { "name": "미등록 cryptoName 조회 → success=false", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/manage/crypto/NOT_REGISTERED_MODULE", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "NOT_REGISTERED_MODULE"] }, "description": "존재하지 않는 cryptoName 조회 시 HTTP 200 + success=false + message에 사유가 담겨 반환되는지 확인한다 (예외가 그대로 500으로 노출되지 않음)." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = false', () => pm.expect(json.success).to.eql(false));", "pm.test('message에 모듈명 포함', () => pm.expect(json.message).to.include('NOT_REGISTERED_MODULE'));" ] } } ] }, { "name": "잘못된 Base64로 복호화 → success=false", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"cipherTextBase64\": \"not-valid-base64!!\"\n}" }, "url": { "raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}/test/decrypt", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{cryptoName}}", "test", "decrypt"] }, "description": "Base64 형식이 아닌 문자열을 전달하면 success=false로 처리되고 서버가 500으로 죽지 않는지 확인한다." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "pm.test('success = false', () => pm.expect(json.success).to.eql(false));", "pm.test('message 존재', () => pm.expect(json.message).to.be.a('string'));" ] } } ] }, { "name": "다른 runtimeContext로 DYNAMIC 복호화 시도 → 실패 확인", "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"cipherTextBase64\": \"{{dynamicCipherTextBase64}}\",\n \"runtimeContext\": {\n \"{{dynamicContextKey}}\": \"differentContextValue\"\n }\n}" }, "url": { "raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}/test/decrypt", "host": ["{{baseUrl}}"], "path": ["manage", "crypto", "{{dynamicCryptoName}}", "test", "decrypt"] }, "description": "'3. DYNAMIC 키' 폴더의 암호화를 먼저 실행해 dynamicCipherTextBase64를 채운 뒤, 암호화 때와 다른 runtimeContext 값으로 복호화를 시도한다. 키가 달라 padding/복호화 오류로 success=false가 되는 것을 확인한다 (패딩 모드에 따라 우연히 성공할 수도 있으니 참고용)." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const json = pm.response.json();", "pm.test('status 200', () => pm.response.to.have.status(200));", "console.log('다른 컨텍스트 복호화 결과:', JSON.stringify(json));" ] } } ] } ] } ] }