近年、社会的に個人情報の取り扱いが厳しくなっております。
社外向けのみならず社内向けの情報転送にも、当たり前のように個人情報の漏えいがないように気を付けています。
システムの高可用性を維持するために導入したマイクロサービスアーキテクチャは、その特徴によって同じ情報(形も意味も含めて)を複数のシステムでそれぞれ持つようになりました。アーキテクチャの特徴による個人情報を含めた様々な重複情報の存在は、データベース1箇所で集約したモノリシックアーキテクチャより遥かに頻雑になりました。
また、AIやデータサイエンスの台頭によるデータ分析基盤の構築で会社が扱っているデータの1箇所への集約も大きな課題になっています。
弊社のXplentyでは、多数のシステムに散在している様々な形の情報を取り集め、集計や結合などのデータ変換で高付加価値の情報への転換に適したSaaS型サービスです。
その点を有効活用によって会社として特定の個人情報に一貫した保護策を講じられます。
今回のブログでは、シャッフル、部分隠し、暗号化による個人情報の保護策をXplentyでどう実装するかを見てみましょう。
個人情報マスキングの種類
個人情報の保護は、下記の3種類の方法があります。各々の方法には長所と短所があり、用途に沿って使う必要があります。
- シャッフル
- データの文字や数字を置き換える、または順番を変えること
- 本番データをテストデータとして提供する場合に有効
- 意味を持つ部分の置き換えは、意味を維持しつつ変換するための工夫が必要
- 部分隠し
- データの一部分を無意味な文字に置き換えること
- フォーマットは維持と特定の部分(元の値を置き換えが必要)のみを見せる場合に有効
- シャッフルと同じく意味を持つ部分の置き換えは工夫が必要
- 暗号化
- 単方向ハッシュ関数を使い、別のデータに変換すること
- 種類が少ないデータの原型が把握出来なくする場合に有効
- 復号化が出来ない関数を使うのうが大事である
個人情報のマスキングの実行箇所
個人情報の保護について実装箇所は、主にselectコンポーネントにて行われます。
selectコンポーネントの中を覗いてみると下記の通りです。
また、各々のフィールドの設定は、図の赤の矢印が示している部分をクリックすると現れる下記のExpression Editor上で行われます。
もちろん、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をお試し頂けます。