개발자 문서
개발자 문서

민감 정보 암호화

작업 파라미터 중 민감한 값을 서버 공개키로 암호화해 encrypted_fields 로 보내는 방법입니다.

주민등록번호처럼 민감한 값은 params 에 평문으로 넣지 않습니다. 서버 공개키로 암호화해 encrypted_fields 에 담아 보내면, 서버가 복호화해 params 에 합친 뒤 기관을 호출합니다.

인증서 비밀번호나 기관 로그인 비밀번호는 여기서 보내는 값이 아닙니다. 그 값들은 자격증명 등록 때 한 번 등록합니다.

  1. STEP 01
    공개키 조회

    GET /v1/crypto/public-key 로 환경별 공개키를 받습니다.

  2. STEP 02
    값 암호화

    RSA-OAEP(SHA-256)로 암호화해 base64 문자열로 만듭니다.

  3. STEP 03
    작업 제출

    encrypted_fields 에 담아 POST /v1/jobs 로 보냅니다.

  4. STEP 04
    서버에서 복호화

    복호화한 값을 params 에 합쳐 기관을 호출합니다.

공개키를 받아 값을 암호화하고, encrypted_fields 에 담아 제출하면 서버가 복호화해 params 에 합칩니다.

공개키 받기

GET /v1/crypto/public-key 는 요청한 환경의 공개키를 JWKS 형태로 돌려줍니다. 환경은 env_scope 질의 문자열이나 X-Env-Scope 헤더로 지정합니다.

키를 교체하는 동안에는 새 키와 이전 키가 함께 오고, 사용 중인 키가 먼저 옵니다. use 가 enc 인 첫 번째 키로 암호화합니다.

json
{
  "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 로 인코딩합니다.

언어별 예시

encrypt-field.mjs
javascript
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') },
};

지켜야 할 규칙

규칙어기면
값은 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 로 보냅니다.

콘솔 자격증명 등록 화면은 브라우저에서 같은 방식으로 자동 암호화합니다. 이 문서는 서버에서 직접 호출할 때 필요합니다.