LoginSignup
1
0

More than 1 year has passed since last update.

Xplentyで個人情報のマスキング方法

Last updated at Posted at 2022-12-05

近年、社会的に個人情報の取り扱いが厳しくなっております。
社外向けのみならず社内向けの情報転送にも、当たり前のように個人情報の漏えいがないように気を付けています。

システムの高可用性を維持するために導入したマイクロサービスアーキテクチャは、その特徴によって同じ情報(形も意味も含めて)を複数のシステムでそれぞれ持つようになりました。アーキテクチャの特徴による個人情報を含めた様々な重複情報の存在は、データベース1箇所で集約したモノリシックアーキテクチャより遥かに頻雑になりました。

また、AIやデータサイエンスの台頭によるデータ分析基盤の構築で会社が扱っているデータの1箇所への集約も大きな課題になっています。

弊社のXplentyでは、多数のシステムに散在している様々な形の情報を取り集め、集計や結合などのデータ変換で高付加価値の情報への転換に適したSaaS型サービスです。
その点を有効活用によって会社として特定の個人情報に一貫した保護策を講じられます。

今回のブログでは、シャッフル、部分隠し、暗号化による個人情報の保護策をXplentyでどう実装するかを見てみましょう。

個人情報マスキングの種類

個人情報の保護は、下記の3種類の方法があります。各々の方法には長所と短所があり、用途に沿って使う必要があります。

  1. シャッフル
    • データの文字や数字を置き換える、または順番を変えること
    • 本番データをテストデータとして提供する場合に有効
    • 意味を持つ部分の置き換えは、意味を維持しつつ変換するための工夫が必要
  2. 部分隠し
    • データの一部分を無意味な文字に置き換えること
    • フォーマットは維持と特定の部分(元の値を置き換えが必要)のみを見せる場合に有効
    • シャッフルと同じく意味を持つ部分の置き換えは工夫が必要
  3. 暗号化
    • 単方向ハッシュ関数を使い、別のデータに変換すること
    • 種類が少ないデータの原型が把握出来なくする場合に有効
    • 復号化が出来ない関数を使うのうが大事である

個人情報のマスキングの実行箇所

個人情報の保護について実装箇所は、主にselectコンポーネントにて行われます。
image.png

selectコンポーネントの中を覗いてみると下記の通りです。
image.png
image.png

また、各々のフィールドの設定は、図の赤の矢印が示している部分をクリックすると現れる下記のExpression Editor上で行われます。
image.png
もちろん、Expressionに該当関数式をコピペしても問題ありません。

個人情報のマスキングの例

例に使われているデータは、すべてSmartHRのサンドボックス環境から取得したものです。
SmartHRのサンドボックス環境やAPIについては、SmartHR API Specificationsをご参照頂ければ幸いです。

社員番号(数字のみ)

元のデータ シャッフル 部分隠し
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
石井 玲佳 玲井 石佳 #井
古賀 祐奈 古奈 祐賀 #賀
菊池 舞麗 菊麗 舞池 菊#
湯浅 海琴 海浅 海浅 湯#
高松 高暢 高#
河野 咲枝 咲野 河枝 河#
上野 真綾 真野 上綾 #野
小山 泰治 泰山 小治 小#
大場 孝浩 大浩 大浩 #場
松原 敏雄 敏原 松雄 松#
● シャッフル(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
gaston.nikolaus@example.com ga*************@######.com ga*******@######.com
salvatore@example.com sa*******@######.com sa*******@######.com
bruno_abbott@example.net br**********@######.net br*******@######.net
arnold.shanahan@example.net ar*************@######.net ar*******@######.net
george@example.net ge****@######.net ge*******@######.net
celestina@example.org ce*******@######.org ce*******@######.org
avery.homenick@example.org av************@######.org av*******@######.org
tyson@example.net ty***@######.net ty*******@######.net
sang.hirthe@example.org sa*********@######.org sa*******@######.org
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にて機能を確認してみてはいかがでしょうか?

無料トライアル申請にていつでもXplentyをお試し頂けます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0