> ## Documentation Index
> Fetch the complete documentation index at: https://www.integrate.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# ETL: Xplenty에서 개인 정보를 마스킹하는 방법

> Integrate.io ETL의 표현식 함수와 Select 컴포넌트로 이름, 이메일, 주민등록번호 등 데이터 파이프라인의 개인정보 컬럼을 마스킹하여 민감 정보 유출을 방지하고 데이터 거버넌스를 강화하는 방법을 안내합니다. 데이터 파이프라인 구축 시 참고할 수 있는 가이드입니다.

최근들어 사회적으로 개인정보의 취급이 엄격해지고 있습니다. 사외용 뿐만 아니라 사내용의 정보 전송에도 당연한 것처럼 개인정보의 유출이 없는지 주의를 하고 있습니다.

시스템의 고가용성을 유지하기 위해 도입한 마이크로서비스 아키텍처는, 같은 정보(형태와 의미를 모두 포함해)가 여러개의 시스템에서 관리되고 있습니다. 아키텍처의 특징에 의한 개인정보를 포함한 다양한 중복 정보의 존재는 데이터베이스 1곳에 모아 관리하는 모놀리식 아키텍처보다 훨씬 번잡해졌습니다. 또, AI나 데이터 사이언스의 대두에 의한 데이터 분석 기반의 구축으로 회사가 취급하고 있는 데이터를 1곳에 모으는 일도 매우 큰 과제가 되었습니다.

저희 Xplenty에서는, 다수의 시스템에 흩어져 있는 다양한 형태의 정보를 수집해, 집계나 결합등의 데이터 변환으로 고부가가치의 정보로의 변환에 적합한 SaaS형 서비스입니다. 이 점을 적극적으로 활용함으로써 회사가 관리하는 특정 개인정보에 통일된 보호정책을 유지 할 수 있습니다.

이번 블로그에서는 셔플, 부분 숨김, 암호화에 의한 개인정보의 보호책을 Xplenty로 어떻게 구현하는지 살펴보겠습니다.

## **개인정보 마스킹 유형**

개인정보의 보호를 위한 방법은 아래와 같은 3종류의 방법이 있습니다.
각 방법에는 장점과 단점이 있으며 용도에 따라 사용할 필요가 있습니다.

* 셔플

* 데이터의 문자나 숫자를 바꾸거나 순서를 변경

* 프로덕션 데이터를 테스트 데이터로 제공하는 경우 유효

* 의미를 유지하며 대체하기 위해 변환방법이 따로 필요

* 부분 숨김

* 데이터의 일부를 의미 없는 문자로 대체

* 형식은 유지 및 특정 부분(원래 값을 대체해야 함)만 표시하는 경우에 유효

* 셔플과 마찬가지로 의미를 가진 부분에 대한 대체방법이 필요

* 암호화

* 단방향 해시 함수등을 사용하여 다른 형태의 데이터로 변환

* 종류가 적은 데이터의 원형을 파악할 수 없게 하는 경우에 유효

* 암호화를 풀기 힘든 함수를 사용하는 것이 중요

## **개인정보 마스킹 실행 장소**

개인정보의 보호를 위한 데이터 변환은 select 컴포넌트에서 이루어집니다.

\[

<Frame>
  <img src="https://mintcdn.com/integrateio/erg_659RimicWdnj/images/creating-packages/etc-part10-ko/image-1.webp?fit=max&auto=format&n=erg_659RimicWdnj&q=85&s=89d60e6b0f78e77a9c0c27f5b8e2b523" alt="etc-part10-ko image 1" width="255" height="530" data-path="images/creating-packages/etc-part10-ko/image-1.webp" />
</Frame>

]\([https://cdn.filestackcontent.com/auto\_image//compress/cache=expiry:max/URpHNr0Q80UUkDdi4rOA](https://cdn.filestackcontent.com/auto_image//compress/cache=expiry:max/URpHNr0Q80UUkDdi4rOA))

select 컴포넌트 안의 설정은 아래와 같습니다.
[](https://camo.qiitausercontent.com/ef2c593a7a149505e350c681bf25a6c8ee550fef/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f36353436312f32373662653562622d333935342d616136622d333664332d6130616264353135313064322e706e67)

\[

<Frame>
  <img src="https://mintcdn.com/integrateio/erg_659RimicWdnj/images/creating-packages/etc-part10-ko/image-2.webp?fit=max&auto=format&n=erg_659RimicWdnj&q=85&s=d2f7b71d3a2d7cb2541cf0dfc1c61726" alt="etc-part10-ko image 2" width="1054" height="1155" data-path="images/creating-packages/etc-part10-ko/image-2.webp" />
</Frame>

]\([https://cdn.filestackcontent.com/auto\_image//compress/cache=expiry:max/AyAJO3BSRWKyYG6wpNER)\[](https://cdn.filestackcontent.com/auto_image//compress/cache=expiry:max/AyAJO3BSRWKyYG6wpNER\)\[)

<Frame>
  <img src="https://mintcdn.com/integrateio/erg_659RimicWdnj/images/creating-packages/etc-part10-ko/image-3.webp?fit=max&auto=format&n=erg_659RimicWdnj&q=85&s=0a043362cf803676c7dc1e543bda6854" alt="etc-part10-ko image 3" width="1056" height="1090" data-path="images/creating-packages/etc-part10-ko/image-3.webp" />
</Frame>

]\([https://cdn.filestackcontent.com/auto\_image//compress/cache=expiry:max/1txMf1ZSOm6ooLHuAQqV](https://cdn.filestackcontent.com/auto_image//compress/cache=expiry:max/1txMf1ZSOm6ooLHuAQqV))

또한 각 필드의 설정은 그림의 빨간색 화살표가 표시된 부분을 클릭하면 나타나는 아래의 Expression Editor에서 이루어집니다. 물론 Expression에 해당 함수식을 복사해도 문제 없습니다.

\[

<Frame>
  <img src="https://mintcdn.com/integrateio/erg_659RimicWdnj/images/creating-packages/etc-part10-ko/image-4.webp?fit=max&auto=format&n=erg_659RimicWdnj&q=85&s=8bba9f18ce790e3e1e7c7c16c08d2803" alt="etc-part10-ko image 4" width="810" height="753" data-path="images/creating-packages/etc-part10-ko/image-4.webp" />
</Frame>

]\([https://cdn.filestackcontent.com/auto\_image//compress/cache=expiry:max/Uv8QhoJToteiczKI9gGA](https://cdn.filestackcontent.com/auto_image//compress/cache=expiry:max/Uv8QhoJToteiczKI9gGA))

## **개인정보 마스킹 예**

예제에 사용된 모든 데이터는 SmartHR의 샌드박스 환경에서 가져온 것입니다.

SmartHR의 샌드박스 환경과 API에 대해서는[SmartHR API Specifications](https://developer.smarthr.jp/api/index.html)를 참조해 주시면 감사하겠습니다.

### **직원 번호 (숫자 전용)**

**원래 데이터**
**셔플**
**부분 숨김**

00008
00903
\##008

00023
03103
0##23

00026
03104
000##

00025
03304
000##

00017
02502
\##017

00006
01400
000##

00001
00104
\##001

00024
03201
\##024

00019
02701
\##019

00018
02504
0##18

● 셔플
SPRINTF('%03d%02d',
(int)SUBSTRING(emp\_code, 3, Length(emp\_code)) + (ROUND(RANDOM()\*10) % 10),
(int)SUBSTRING(emp\_code, 0, 3) + (ROUND(RANDOM()\*10) % 10)
)

● 부분 숨김
CASE ROUND(RANDOM() \* 10) % 4
WHEN 1 THEN
CONCAT(SUBSTRING(emp\_code, 0, Length(emp\_code)-2), '##')
WHEN 2 THEN
CONCAT(SUBSTRING(emp\_code, 0, 1), '##', SUBSTRING(emp\_code, Length(emp\_code)-2, Length(emp\_code)))
WHEN 3 THEN
CONCAT(SUBSTRING(emp\_code, 0, 2), '##', SUBSTRING(emp\_code, Length(emp\_code)-1, Length(emp\_code)))
ELSE
CONCAT('##', SUBSTRING(emp\_code, 2, Length(emp\_code)))
END

### **이름(한자)**

원래 데이터

셔플

부분 숨김

Last Name
First Name
Last Name
First Name
Last Name
First Name

石井
玲佳
玲井
石佳
\#井
玲#

古賀
祐奈
古奈
祐賀
\#賀
\#奈

菊池
舞麗
菊麗
舞池
菊#

舞#

湯浅
海琴
海浅
海浅
湯#
\#琴

高松
暢
松
高暢
高#

#

河野
咲枝
咲野
河枝
河#
咲#

上野
真綾
真野
上綾
\#野
\#綾

小山
泰治
泰山
小治
小#
泰#

大場
孝浩
大浩
大浩
\#場
孝#

松原
敏雄
敏原
松雄
松#
\#雄

● 셔플(last\_name, first\_name)
CASE ROUND(RANDOM() \* 10) % 2
WHEN 0 THEN
CONCAT(SUBSTRING(last\_name, 0, 1), SUBSTRING(first\_name, 1, Length(first\_name)))
ELSE
CONCAT(SUBSTRING(Reverse(first\_name), 1, Length(first\_name)), SUBSTRING(Reverse(last\_name), 0, 1))
END
CASE ROUND(RANDOM() \* 10) % 2
WHEN 1 THEN
CONCAT(SUBSTRING(first\_name, 0, 1), SUBSTRING(last\_name, 1, Length(last\_name)))
ELSE
CONCAT( SUBSTRING(Reverse(last\_name), 1, Length(last\_name)), SUBSTRING(Reverse(first\_name), 0, 1))
END

● 부분 숨김(last\_name, first\_name)
CASE ROUND(RANDOM() \* 10) % 2
WHEN 0 THEN
SPRINTF('%s%s', SUBSTRING(last\_name, 0, 1), REPLACE(SUBSTRING(last\_name, 1, Length(last\_name)), '.', '#'))
ELSE
SPRINTF('%s%s', REPLACE(SUBSTRING(last\_name, 0, Length(last\_name)-1), '.', '#'), SUBSTRING(last\_name, Length(last\_name)-1, Length(last\_name)))
END
CASE ROUND(RANDOM() \* 10) % 2
WHEN 1
THEN SPRINTF('%s%s', SUBSTRING(first\_name, 0, 1), REPLACE(SUBSTRING(first\_name, 1, Length(first\_name)), '.', '#'))
ELSE
SPRINTF('%s%s', REPLACE(SUBSTRING(first\_name, 0, Length(first\_name)-1), '.', '#'), SUBSTRING(first\_name, Length(first\_name)-1, Length(first\_name)))
END

### **생일(년월일)**

원래 데이터
부분 숨김

1948-12-22
19\*\*-**-**

● 部分隠し
CONCAT(
SUBSTRING(birth\_at, 0, 2),
REPLACE(SUBSTRING(birth\_at, 2, Length(birth\_at)), '\[0-9]', '\*')
)

### **성별**

원래 데이터
부분 숨김

male
07cf4

female
273b9

● 暗号化
SUBSTRING(MD5(gender), 0, 5)

# MD5대신에 SHA256등의 다른 단방향 암호화 함수를 사용해도 됨

### **전화번호**

원래 데이터
셔플
부분 숨김

090-5302-5398
090-2053-8953
090-**-**

070-4596-8665
070-6945-5686
070-**-**

070-7119-7796
070-9171-6977
070-**-**

090-3071-1160
090-1730-0611
090-**-**

070-1688-0980
070-8816-0809
070-**-**

090-8426-7235
090-6284-5372
090-**-**

070-7213-2746
070-3172-6427
070-**-**

090-8151-8277
090-1581-7782
090-**-**

090-3304-2880
090-4033-0828
090-**-**

080-5377-6182
080-7753-2861
080-**-**

● シャッフル
SPRINTF('%s-%s%s-%s%s',
STRSPLIT(tel\_number, '-').$0,
	SUBSTRING(Reverse(STRSPLIT(tel_number, '-').$1), 0, 2),
SUBSTRING(STRSPLIT(tel\_number, '-').$1, 0, 2),
	SUBSTRING(Reverse(STRSPLIT(tel_number, '-').$2), 0, 2),
SUBSTRING(STRSPLIT(tel\_number, '-').\$2, 0, 2)
)

● 部分隠し
CONCAT(STRSPLIT(tel\_number, '-').$0, '-',
	REPLACE(STRSPLIT(tel_number, '-').$1, '\[0-9]', '*'),'-',
REPLACE(STRSPLIT(tel\_number, '-').\$2, '\[0-9]', '*')
)

### **우편번호**

원래 데이터
부분 숨김

567-0896
\#6#-#896

480-1122
\#8#-11##

484-0863
4#4-0###

414-0026
4#4-002#

409-2946
\##9-#9#6

347-0104
\#47-0#04

938-0178
9##-0#7#

950-1113
9##-1113

964-0935
9#4-09##

957-0066
9##-##66

● 部分隠し
(ROUND(RANDOM()\*10) % 2 == 0 ?
REPLACE(address#'zip\_code', '\[13568]' ,'#') :
REPLACE(address#'zip\_code', '\[02457]' ,'#')
)

### **주소(번지)**

원래 데이터
셔플\_1
셔플\_2
부분 숨김

4-23-11
4-11-23
10-27-14
4-2#-##

7-22-7
7-22-7
16-29-9
\#-22-#

7-5-11
7-11-5
7-14-13
7-5-11

1-13-19
1-31-91
6-20-26
\#-##-##

3-9-29
3-9-92
5-15-37
3-9-#9

7-27-30
7-72-3
9-34-39
7-#7-3#

8-22-21
8-22-12
8-23-25
8-22-2#

6-4-17
6-17-4
13-11-19
6-4-##

1-26-16
1-16-26
10-27-23
\#-26-#6

7-23-10
7-10-23
14-27-19
7-#3-1#

● シャッフル\_1
(ROUND(RANDOM()\*10) % 2 == 0 ?
SPRINTF('%s-%d-%d',
STRSPLIT(address#'street', '-').$0,
		(int)STRSPLIT(address#'street', '-').$2,
(int)STRSPLIT(address#'street', '-').$1
	) :
	SPRINTF('%s-%d-%d',
		STRSPLIT(address#'street', '-').$0,
(int)Reverse(STRSPLIT(address#'street', '-').$1),
		(int)Reverse(STRSPLIT(address#'street', '-').$2)
)
)

● シャッフル\_2
SPRINTF('%d-%d-%d',
(int)STRSPLIT(address#'street', '-').$0 + (ROUND(RANDOM()*10) %10),
	(int)STRSPLIT(address#'street', '-').$1 + (ROUND(RANDOM()\*10) %10),
(int)STRSPLIT(address#'street', '-').\$2 + (ROUND(RANDOM()\*10) %10)
)

● 部分隠し
(ROUND(RANDOM()\*10) % 2 == 0 ?
REPLACE(address#'street', '\[13579]' ,'#') :
REPLACE(address#'street', '\[02468]' ,'#')
)

### **이메일 주소**

원래 데이터
부분 숨김\_1
부분 숨김\_2

[mailto:gaston.nikolaus@example.com](mailto:gaston.nikolaus@example.com)
ga\*\*\*\*\*\*\*\*\*\*\*\*\*@######.com
ga\*\*\*\*\*\*\*@######.com

[mailto:salvatore@example.com](mailto:salvatore@example.com)
sa\*\*\*\*\*\*\*@######..com
sa\*\*\*\*\*\*\*@######..com

[mailto:bruno\_abbott@example.net](mailto:bruno_abbott@example.net)
br\*\*\*\*\*\*\*\*\*\*@######.net
br\*\*\*\*\*\*\*@######.net

[mailto:arnold.shanahan@example.net](mailto:arnold.shanahan@example.net)
ar\*\*\*\*\*\*\*\*\*\*\*\*\*@######.net
ar\*\*\*\*\*\*\*@######.net

[mailto:george@example.net](mailto:george@example.net)
ge\*\*\*\*@######.net
ge\*\*\*\*\*\*\*@######.net

[mailto:celestina@example.org](mailto:celestina@example.org)
ce\*\*\*\*\*\*\*@######.org
ce\*\*\*\*\*\*\*@######.org

[mailto:avery.homenick@example.org](mailto:avery.homenick@example.org)
av\*\*\*\*\*\*\*\*\*\*\*\*@######.org
av\*\*\*\*\*\*\*@######.org

[mailto:tyson@example.net](mailto:tyson@example.net)
ty\*\*\*@######.net
ty\*\*\*\*\*\*\*@######.net

[mailto:sang.hirthe@example.org](mailto:sang.hirthe@example.org)
sa\*\*\*\*\*\*\*\*\*@######.org
sa\*\*\*\*\*\*\*@######.org

[mailto:hunter@example.com](mailto:hunter@example.com)
hu\*\*\*\*@######.com
hu\*\*\*\*\*\*\*@######.com

● 部分隠し\_1
SPRINTF('%s%s@%s%s',
SUBSTRING(STRSPLIT(email, '@').$0, 0, 2),
	REPLACE(SUBSTRING(STRSPLIT(email, '@').$0, 2, Length(STRSPLIT(email, '@').$0)), '.', '*'),
	REPLACE(SUBSTRING(STRSPLIT(email, '@').$1, 0, LAST\_INDEX\_OF(STRSPLIT(email, '@').$1, '.') - 1), '.', '#' ),
	SUBSTRING(STRSPLIT(email, '@').$1, LAST\_INDEX\_OF(STRSPLIT(email, '@').$1, '.'), Length(STRSPLIT(email, '@').$1))
)

● 部分隠し\_2
SPRINTF('%s%s@%s%s',
SUBSTRING(STRSPLIT(email, '@').$0, 0, 2),
	'*******',
	'######',
	SUBSTRING(STRSPLIT(email, '@').$1, LAST\_INDEX\_OF(STRSPLIT(email, '@').$1, '.'), Length(STRSPLIT(email, '@').$1))
)

### **각종 사회보험번호**

원래 데이터
셔플
부분 숨김\_1
부분 숨김\_2
부분 숨김\_3

5063-196700-3
5107-007833-5
50##-#9#700-#
5\*\*3-1####0-3
\*0@@-\@967@@-@

4598-668799-1
4603-997988-2
\#59#-###799-1
4\*\*8-6####9-1
\*5@@-\@687@@-@

4676-458881-5
4685-189071-8
\##7#-#5###1-5
4\*\*6-4####1-5
\*6@@-\@588@@-@

5105-769271-1
5180-173803-8
5#05-7#927#-#
5\*\*5-7####1-1
\*1@@-\@692@@-@

5323-496653-1
5358-356889-3
53#3-#9##53-1
5\*\*3-4####3-1
\*3@@-\@966@@-@

8875-203981-7
8965-189912-7
88##-20##8#-#
8\*\*5-2####1-7
\*8@@-\@039@@-@

8845-730489-5
8883-984208-6
\###5-73###9-5
8\*\*5-7####9-5
\*8@@-\@304@@-@

2765-123674-2
2856-476816-9
27#5-#2##7#-2
2\*\*5-1####4-2
\*7@@-\@236@@-@

8968-785929-6
9023-930160-4
8#68-#8####-6
8\*\*8-7####9-6
\*9@@-\@859@@-@

4879-952886-9
4923-688784-6
\##79-952###-9
4\*\*9-9####6-9
\*8@@-\@528@@-@

\`\`● シャッフル
SPRINTF('%04d-%06d-%d',
((int)STRSPLIT(emp\_ins\_insured\_person\_number, '-').$0 + ROUND(RANDOM()*100)) % 10000,
	((int)Reverse(STRSPLIT(emp_ins_insured_person_number, '-').$1) + ROUND(RANDOM()\*1000)) % 1000000,
((int)STRSPLIT(emp\_ins\_insured\_person\_number, '-').\$2 + ROUND(RANDOM()\*10)) % 10
)

● 部分隠し\_1
CASE ROUND(RANDOM() \* 10) % 4
WHEN 1 THEN
REPLACE(emp\_ins\_insured\_person\_number, '\[13579]', '#')
WHEN 2 THEN
REPLACE(emp\_ins\_insured\_person\_number, '\[02579]', '#')
WHEN 3 THEN
REPLACE(emp\_ins\_insured\_person\_number, '\[13468]', '#')
ELSE
REPLACE(emp\_ins\_insured\_person\_number, '\[02468]', '#')
END

● 部分隠し\_2
SPRINTF('%s%s%s-%s%s%s-%s',
REGEX\_EXTRACT\_ALL(emp\_ins\_insured\_person\_number, '(\\\d)(\\\d+)(\\\d)-(\\\d)(\\\d+)(\\\d)-(\\\d)').$0,
	REPLACE(REGEX_EXTRACT_ALL(emp_ins_insured_person_number, '(\\\\d)(\\\\d+)(\\\\d)-(\\\\d)(\\\\d+)(\\\\d)-(\\\\d)').$1, '.', '\*'),
REGEX\_EXTRACT\_ALL(emp\_ins\_insured\_person\_number, '(\\\d)(\\\d+)(\\\d)-(\\\d)(\\\d+)(\\\d)-(\\\d)').$2,
	REGEX_EXTRACT_ALL(emp_ins_insured_person_number, '(\\\\d)(\\\\d+)(\\\\d)-(\\\\d)(\\\\d+)(\\\\d)-(\\\\d)').$3,
REPLACE(REGEX\_EXTRACT\_ALL(emp\_ins\_insured\_person\_number, '(\\\d)(\\\d+)(\\\d)-(\\\d)(\\\d+)(\\\d)-(\\\d)').$4, '.', '#'),
	REGEX_EXTRACT_ALL(emp_ins_insured_person_number, '(\\\\d)(\\\\d+)(\\\\d)-(\\\\d)(\\\\d+)(\\\\d)-(\\\\d)').$5,
REGEX\_EXTRACT\_ALL(emp\_ins\_insured\_person\_number, '(\\\d)(\\\d+)(\\\d)-(\\\d)(\\\d+)(\\\d)-(\\\d)').\$6
)

● 部分隠し\_3
REPLACE(REPLACE(emp\_ins\_insured\_person\_number, '\\\d\\\d-\\\d', '@@-@'), '^\\\d', '\*')

## **요약**

Xplenty의 select 컴포넌트에는 이러한 마스킹 외에도 다양한 기능을 구현할 수 있습니다. 한번 Xplenty의 공식 매뉴얼이나 X-console에서 기능을 확인해 보면 어떻습니까?
[무료 평가판 신청](https://try.integrate.io/kr-demo/)을 통해 언제라도 Xplenty를 사용 해 보실 수 있습니다. 

\<!-- notionvc: e2b72a84-a199-46f8-ae35-bfb7ce39fd76 -->

##

\<!-- Google Tag Manager hidden fields -->

\<!-- End Google Tag Manager hidden fields -->

\<!-- Text input-->

Get Started

Free 7-day trial. Easy setup. Cancel any time
