[Nexacro N] 데이터 암호화

Nexacro N 애플리케이션에서 민감 데이터를 보호하는 암호화 전략을 설명합니다. TLS 전송 암호화, AES 필드 레벨 암호화, addServiceHeader를 활용한 키 전달, 마스킹 표시 구현 방법을 다룹니다.

· 6 min read · PALDYN Team

지난 글에서 CSRF 방어를 살펴보았다. 이번에는 Nexacro N 애플리케이션에서 민감 데이터를 보호하는 암호화 전략을 다룬다. 주민번호, 카드번호, 계좌번호 같은 개인정보는 화면 표시, 전송, 저장 각 단계에서 별도의 보호가 필요하다.

암호화 레이어 구조

Nexacro N 보안 설계에서 암호화는 네 레이어로 나뉜다. 클라이언트가 직접 처리하는 부분과 서버가 처리하는 부분을 명확히 구분해야 한다.

데이터 암호화 레이어

  • Layer 1 (TLS/HTTPS): 가장 기본. 서비스 URL을 https://로 고정하고 HTTP 리다이렉트를 서버에서 강제한다
  • Layer 2 (필드 레벨 암호화): 특정 민감 필드를 클라이언트에서 암호화한 뒤 서버로 전송
  • Layer 3 (DB 컬럼 암호화): 서버 영역. 애플리케이션 또는 DB 레벨 암호화
  • Layer 4 (UI 마스킹): 화면에서 일부만 표시 — 암호화가 아닌 표시 제한

TLS 설정 확인

Nexacro N 앱에서 서비스 URL이 HTTP로 지정되면 전송 구간 암호화가 무력화된다. TypeDefinition.xadl의 서비스 URL을 반드시 HTTPS로 설정한다.

<!-- TypeDefinition.xadl -->
<TypeDefinition>
  <Environments>
    <Environment id="SERVICE_URL" value="https://api.example.com/service/"/>
    <!-- http:// 사용 금지 -->
  </Environments>
</TypeDefinition>

서버(Spring)에서는 HTTP 접근을 HTTPS로 강제 리다이렉트한다.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.requiresChannel(channel ->
        channel.anyRequest().requiresSecure()
    );
    return http.build();
}

필드 레벨 AES 암호화

TLS가 전송 전체를 암호화하더라도, 로그 기록·프록시 서버·중간 시스템에서 평문이 노출될 수 있다. 민감 필드는 클라이언트에서 직접 암호화해 전송한다.

CryptoJS 라이브러리 포함

Nexacro N 프로젝트의 공통 스크립트 경로에 CryptoJS를 추가한다.

<!-- TypeDefinition.xadl -->
<Script id="cryptojs" src="CommonLib/crypto-js.min.js"/>

AES 암호화 함수

// CommonLib/Security.xjs
function gfn_aesEncrypt(sPlain, sKey, sIv) {
  var key = CryptoJS.enc.Utf8.parse(sKey);
  var iv  = CryptoJS.enc.Utf8.parse(sIv);
  var enc = CryptoJS.AES.encrypt(sPlain, key, {
    iv        : iv,
    mode      : CryptoJS.mode.CBC,
    padding   : CryptoJS.pad.Pkcs7
  });
  return enc.toString(); // Base64 인코딩 결과
}

function gfn_aesDecrypt(sCipher, sKey, sIv) {
  var key = CryptoJS.enc.Utf8.parse(sKey);
  var iv  = CryptoJS.enc.Utf8.parse(sIv);
  var dec = CryptoJS.AES.decrypt(sCipher, key, {
    iv      : iv,
    mode    : CryptoJS.mode.CBC,
    padding : CryptoJS.pad.Pkcs7
  });
  return dec.toString(CryptoJS.enc.Utf8);
}

AES 암호화 처리 흐름

암호화 키 관리

암호화 키를 클라이언트 소스에 하드코딩하면 안 된다. 다음 두 가지 접근을 권장한다.

방식 A: 세션별 키 교환 (권장)

로그인 후 서버에서 세션 고유 키를 발급받아 메모리 변수에 저장한다.

// BaseForm.xfdl — 로그인 성공 후
function fn_loginCallback(sId, nErrCode, sErrMsg) {
  if (nErrCode == 0) {
    gv_encKey = this.ds_session.getColumn(0, "ENC_KEY");
    gv_encIv  = this.ds_session.getColumn(0, "ENC_IV");
  }
}

방식 B: 서버 공개키 기반 RSA 래핑

AES 키 자체를 서버 RSA 공개키로 암호화해 전송하고, 서버가 개인키로 AES 키를 복호화한다. 전송 키가 매번 달라지는 장점이 있다.

데이터 전송 시 암호화 적용

function fn_save() {
  // 민감 필드 암호화 후 Dataset에 설정
  var sPlain  = this.edt_ssn.value; // 주민번호 평문
  var sCipher = gfn_aesEncrypt(sPlain, gv_encKey, gv_encIv);
  this.ds_input.setColumn(0, "SSN_ENC", sCipher);
  this.ds_input.setColumn(0, "SSN",     ""); // 평문 비움

  this.transaction(
    "savePersonal",
    "svc://PersonalService/save",
    "ds_input=ds_input",
    "ds_output=ds_output",
    "",
    "fn_saveCallback"
  );
}

UI 마스킹 처리

암호화와 별개로, 화면에서 민감 정보를 일부만 보여주는 마스킹은 UX와 보안 모두를 위한 처리다.

MaskEdit 컴포넌트 활용

<!-- 주민번호 마스킹 입력 -->
<MaskEdit id="edt_ssn"
          mask="999999-9999999"
          maskChar="*"
          displayMask="999999-*******"/>

동적 마스킹 함수

서버에서 받은 값을 화면에 표시할 때 마스킹을 적용한다.

function gfn_maskSsn(sSsn) {
  if (!sSsn || sSsn.length < 7) return sSsn;
  return sSsn.substring(0, 6) + "-*******";
}

function fn_searchCallback(sId, nErrCode, sErrMsg) {
  if (nErrCode == 0) {
    var rawSsn = this.ds_result.getColumn(0, "SSN_MASKED");
    this.edt_ssn.value = rawSsn; // 서버가 이미 마스킹해서 반환
  }
}

서버에서 마스킹 값을 반환하는 방식이 더 안전하다. 클라이언트가 전체 값을 받아 마스킹하면 개발자 도구로 원본이 노출될 수 있다.

개인정보 취급 체크리스트

항목조치
전송 구간HTTPS 강제, HSTS 설정
민감 필드AES-256 암호화 전송
암호화 키서버 발급, 소스 하드코딩 금지
화면 표시서버 마스킹 후 반환
로그암호화 필드 로그 마스킹
DB 저장컬럼 암호화 또는 토크나이제이션
콘솔 출력배포 빌드에서 trace 비활성화

Nexacro N 자체는 암호화 기능을 내장하지 않으므로, CryptoJS 같은 표준 라이브러리를 공통 스크립트에 포함시켜 프로젝트 전체에 일관된 암호화 계층을 구성해야 한다.


지난 글: CSRF 방어

다음 글: 다국어(i18n) 개요


읽어주셔서 감사합니다. 😊