민감 정보 암호화
작업 파라미터 중 민감한 값을 서버 공개키로 암호화해 encrypted_fields 로 보내는 방법입니다.
주민등록번호처럼 민감한 값은 params 에 평문으로 넣지 않습니다. 서버 공개키로 암호화해 encrypted_fields 에 담아 보내면, 서버가 복호화해 params 에 합친 뒤 기관을 호출합니다.
인증서 비밀번호나 기관 로그인 비밀번호는 여기서 보내는 값이 아닙니다. 그 값들은 자격증명 등록 때 한 번 등록합니다.
- STEP 01공개키 조회
GET /v1/crypto/public-key 로 환경별 공개키를 받습니다.
- STEP 02값 암호화
RSA-OAEP(SHA-256)로 암호화해 base64 문자열로 만듭니다.
- STEP 03작업 제출
encrypted_fields 에 담아 POST /v1/jobs 로 보냅니다.
- STEP 04서버에서 복호화
복호화한 값을 params 에 합쳐 기관을 호출합니다.
공개키 받기
GET /v1/crypto/public-key 는 요청한 환경의 공개키를 JWKS 형태로 돌려줍니다. 환경은 env_scope 질의 문자열이나 X-Env-Scope 헤더로 지정합니다.
키를 교체하는 동안에는 새 키와 이전 키가 함께 오고, 사용 중인 키가 먼저 옵니다. use 가 enc 인 첫 번째 키로 암호화합니다.
{
"keys": [
{
"kid": "key_01HW9X7P9Q8YQ2KQABC123XYZA",
"kty": "RSA",
"alg": "RSA-OAEP-256",
"use": "enc",
"n": "u1SU1LfVLPHCozMxH2Mo...",
"e": "AQAB"
}
]
}암호화 방법
RSA-OAEP 로 암호화합니다. 해시와 MGF1 모두 SHA-256, label 은 쓰지 않습니다.
키가 4096비트라 한 번에 감쌀 수 있는 평문은 446바이트입니다. 더 길면 446바이트씩 나눠 조각마다 암호화하고, 나온 조각(각 512바이트)을 순서대로 이어 붙인 다음 전체를 base64 로 인코딩합니다.
언어별 예시
import { createPublicKey, publicEncrypt, constants } from 'node:crypto';
// 1. 환경별 공개키를 받습니다(교체 중에는 이전 키가 뒤에 함께 옵니다).
const response = await fetch('https://api.xdata.kr/v1/crypto/public-key?env_scope=sandbox', {
headers: { Authorization: `Basic ${token}` },
});
const { keys } = await response.json();
const jwk = keys.find((key) => key.use === 'enc') ?? keys[0];
const publicKey = createPublicKey({ key: { kty: jwk.kty, n: jwk.n, e: jwk.e }, format: 'jwk' });
// 2. 평문을 446바이트씩 나눠 조각마다 암호화하고, 이어 붙여 base64 로 만듭니다.
const CHUNK = 446;
function encryptField(plaintext) {
const bytes = Buffer.from(plaintext, 'utf8');
const parts = [];
for (let offset = 0; offset < bytes.length; offset += CHUNK) {
parts.push(
publicEncrypt(
{ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
bytes.subarray(offset, offset + CHUNK),
),
);
}
return Buffer.concat(parts).toString('base64');
}
// 3. 같은 이름을 params 에 두지 않고 encrypted_fields 에만 넣습니다.
const body = {
provider: 'fourinsure',
action: 'fourinsure.b1.certificate.issue_fact.confirm',
params: { join_kind_cd: '1', issu_no: '1234567890', idnty_nm: '홍길동' },
encrypted_fields: { frnt_rrno: encryptField('900101') },
};# pip install cryptography requests
import base64, requests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
# 1. 환경별 공개키를 받습니다(교체 중에는 이전 키가 뒤에 함께 옵니다).
keys = requests.get(
'https://api.xdata.kr/v1/crypto/public-key',
params={'env_scope': 'sandbox'},
headers={'Authorization': f'Basic {token}'},
).json()['keys']
jwk = next((key for key in keys if key['use'] == 'enc'), keys[0])
def _b64url_int(value: str) -> int:
padded = value + '=' * (-len(value) % 4)
return int.from_bytes(base64.urlsafe_b64decode(padded), 'big')
public_key = rsa.RSAPublicNumbers(_b64url_int(jwk['e']), _b64url_int(jwk['n'])).public_key()
# 2. 평문을 446바이트씩 나눠 조각마다 암호화하고, 이어 붙여 base64 로 만듭니다.
CHUNK = 446
def encrypt_field(plaintext: str) -> str:
data = plaintext.encode()
parts = [
public_key.encrypt(
data[offset:offset + CHUNK],
padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None),
)
for offset in range(0, len(data), CHUNK)
]
return base64.b64encode(b''.join(parts)).decode()
# 3. 같은 이름을 params 에 두지 않고 encrypted_fields 에만 넣습니다.
body = {
'provider': 'fourinsure',
'action': 'fourinsure.b1.certificate.issue_fact.confirm',
'params': {'join_kind_cd': '1', 'issu_no': '1234567890', 'idnty_nm': '홍길동'},
'encrypted_fields': {'frnt_rrno': encrypt_field('900101')},
}지켜야 할 규칙
| 규칙 | 어기면 |
|---|---|
| 값은 base64 문자로만 된 16자 이상의 문자열이어야 합니다. | 문자열이 아니면(객체, 배열, 숫자) 422 VALIDATION_INVALID_FIELD 로 거절되고 error.fields 목록에 틀린 필드가 실립니다. 문자열인데 base64 문자가 아닌 것이 섞였거나 16자보다 짧으면 400 VALIDATION_INVALID_FIELD 입니다. |
| params 의 필드 이름에 password · secret · api_key · access_token · private_key · ssn · jumin 같은 낱말을 쓰지 않습니다. 민감한 값은 encrypted_fields 로 보냅니다. | 400 VALIDATION_INVALID_FIELD — 이 규칙은 params 에만 적용됩니다. encrypted_fields 의 이름은 액션이 선언한 그대로(예: cert_password) 씁니다. |
| 같은 이름을 params 와 encrypted_fields 에 함께 두지 않습니다. | 작업이 실행 단계에서 실패합니다. |
| 민감한 값을 params 에 평문으로 넣지 않습니다. | 서버는 키 이름만 보고 값은 보지 않습니다. 이름이 중립이면 평문이 그대로 저장되므로 민감한 값은 직접 encrypted_fields 로 보냅니다. |
콘솔 자격증명 등록 화면은 브라우저에서 같은 방식으로 자동 암호화합니다. 이 문서는 서버에서 직접 호출할 때 필요합니다.