混淆/屏蔽/加扰个人信息
Posted
技术标签:
【中文标题】混淆/屏蔽/加扰个人信息【英文标题】:Obfuscate / Mask / Scramble personal information 【发布时间】:2010-09-15 04:42:06 【问题描述】:我正在寻找一种本土方法来打乱生产数据以用于开发和测试。我已经构建了几个脚本来生成随机社会安全号码、改变出生日期、打乱电子邮件等。但我在试图打乱客户姓名时遇到了困难。我想保留真实姓名,以便我们仍然可以使用或搜索,因此随机字母生成已结束。到目前为止,我尝试的是构建表中所有姓氏的临时表,然后使用临时表中的随机选择来更新客户表。像这样:
DECLARE @Names TABLE (Id int IDENTITY(1,1),[Name] varchar(100))
/* Scramble the last names (randomly pick another last name) */
INSERT @Names SELECT LastName FROM Customer ORDER BY NEWID();
WITH [Customer ORDERED BY ROWID] AS
(SELECT ROW_NUMBER() OVER (ORDER BY NEWID()) AS ROWID, LastName FROM Customer)
UPDATE [Customer ORDERED BY ROWID] SET LastName=(SELECT [Name] FROM @Names WHERE ROWID=Id)
这在测试中运行良好,但在处理大量数据时完全陷入困境(40K 行>20 分钟)
所有这些都是要问的,您将如何在保持真实姓名和生产数据权重的同时打乱客户姓名?
更新:永远不会失败,您尝试将所有信息都放在帖子中,而您却忘记了一些重要的事情。这些数据也将用于我们公开的销售和演示环境。一些答案是我试图做的,“切换”名称,但我的问题是字面意思,如何在 T-SQL 中编码?
【问题讨论】:
【参考方案1】:我使用generatedata。它是一个开源的 php 脚本,可以生成各种虚拟数据。
【讨论】:
很好的提示 - 谢谢。 [这是我多年来一直想写但一直没有时间的事情之一]...【参考方案2】:一个非常简单的解决方案是将文本 ROT13。
一个更好的问题可能是您为什么觉得需要打乱数据?如果您有加密密钥,您还可以考虑通过 DES 或 AES 或类似方式运行文本。然而,这可能会出现性能问题。
【讨论】:
正如我所说,我需要与生产具有相似/相同权重的真实姓名,以便搜索执行相似。 附加的 ROT13 实际上不会对数据进行打乱,因为它是一种容易可逆的算法... 是的,它很容易可逆——但它确实符合“掩码”或“混淆”的标准——你至少需要识别出它是 ROT13 的,并取消它 :)【参考方案3】:当做这样的事情时,我通常会编写一个小程序,首先在两个数组中加载大量姓名和姓氏,然后使用数组中的随机姓名/姓氏更新数据库。即使对于非常大的数据集(200.000+ 条记录),它也能运行得非常快
【讨论】:
【参考方案4】:我使用一种方法,将名称中的字符更改为与英文名称使用频率相同“范围”内的其他字符。显然,姓名中字符的分布与普通会话英语不同。例如,“x”和“z”出现 0.245% 的时间,因此它们被交换。另一个极端,“w”的使用率为 5.5%,“s”为 6.86%,“t”为 15.978%。我将“s”更改为“w”,将“t”更改为“s”,将“w”更改为“t”。 我将元音“aeio”放在一个单独的组中,这样一个元音只会被另一个元音替换。类似地,“q”、“u”和“y”根本不会被替换。我的分组和决定完全是主观的。
我最终得到了 7 个不同的 2-5 个字符的“组”,主要基于频率。每个组中的字符与同一组中的其他字符交换。
最终的结果是名字看起来有点像可能是名字,但来自“不在此处”。
Original name Morphed name
Loren Nimag
Juanita Kuogewso
Tennyson Saggywig
David Mijsm
Julie Kunewa
这是我使用的 SQL,其中包括一个“TitleCase”函数。根据我在网上找到的不同字母频率,有 2 个不同版本的“变形”名称。
-- from https://***.com/a/28712621
-- Convert and return param as Title Case
CREATE FUNCTION [dbo].[fnConvert_TitleCase] (@InputString VARCHAR(4000) )
RETURNS VARCHAR(4000)AS
BEGIN
DECLARE @Index INT
DECLARE @Char CHAR(1)
DECLARE @OutputString VARCHAR(255)
SET @OutputString = LOWER(@InputString)
SET @Index = 2
SET @OutputString = STUFF(@OutputString, 1, 1,UPPER(SUBSTRING(@InputString,1,1)))
WHILE @Index <= LEN(@InputString)
BEGIN
SET @Char = SUBSTRING(@InputString, @Index, 1)
IF @Char IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&','''','(','','[','@')
IF @Index + 1 <= LEN(@InputString)
BEGIN
IF @Char != '''' OR UPPER(SUBSTRING(@InputString, @Index + 1, 1)) != 'S'
SET @OutputString = STUFF(@OutputString, @Index + 1, 1,UPPER(SUBSTRING(@InputString, @Index + 1, 1)))
END
SET @Index = @Index + 1
END
RETURN ISNULL(@OutputString,'')
END
Go
-- 00.045 x 0.045%
-- 00.045 z 0.045%
--
-- Replace(Replace(Replace(TS_NAME,'x','#'),'z','x'),'#','z')
--
-- 00.456 k 0.456%
-- 00.511 j 0.511%
-- 00.824 v 0.824%
-- kjv
-- Replace(Replace(Replace(Replace(TS_NAME,'k','#'),'j','k'),'v','j'),'#','v')
--
-- 01.642 g 1.642%
-- 02.284 n 2.284%
-- 02.415 l 2.415%
-- gnl
-- Replace(Replace(Replace(Replace(TS_NAME,'g','#'),'n','g'),'l','n'),'#','l')
--
-- 02.826 r 2.826%
-- 03.174 d 3.174%
-- 03.826 m 3.826%
-- rdm
-- Replace(Replace(Replace(Replace(TS_NAME,'r','#'),'d','r'),'m','d'),'#','m')
--
-- 04.027 f 4.027%
-- 04.200 h 4.200%
-- 04.319 p 4.319%
-- 04.434 b 4.434%
-- 05.238 c 5.238%
-- fhpbc
-- Replace(Replace(Replace(Replace(Replace(Replace(TS_NAME,'f','#'),'h','f'),'p','h'),'b','p'),'c','b'),'#','c')
--
-- 05.497 w 5.497%
-- 06.686 s 6.686%
-- 15.978 t 15.978%
-- wst
-- Replace(Replace(Replace(Replace(TS_NAME,'w','#'),'s','w'),'t','s'),'#','t')
--
--
-- 02.799 e 2.799%
-- 07.294 i 7.294%
-- 07.631 o 7.631%
-- 11.682 a 11.682%
-- eioa
-- Replace(Replace(Replace(Replace(Replace(TS_NAME,'e','#'),'i','ew'),'o','i'),'a','o'),'#','a')
--
-- -- dont replace
-- 00.222 q 0.222%
-- 00.763 y 0.763%
-- 01.183 u 1.183%
-- Obfuscate a name
Select
ts_id,
Cast(ts_name as varchar(42)) as [Original Name]
Cast(dbo.fnConvert_TitleCase(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(TS_NAME,'x','#'),'z','x'),'#','z'),'k','#'),'j','k'),'v','j'),'#','v'),'g','#'),'n','g'),'l','n'),'#','l'),'r','#'),'d','r'),'m','d'),'#','m'),'f','#'),'h','f'),'p','h'),'b','p'),'c','b'),'#','c'),'w','#'),'s','w'),'t','s'),'#','t'),'e','#'),'i','ew'),'o','i'),'a','o'),'#','a')) as VarChar(42)) As [morphed name] ,
Cast(dbo.fnConvert_TitleCase(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(TS_NAME,'e','t'),'~','e'),'t','~'),'a','o'),'~','a'),'o','~'),'i','n'),'~','i'),'n','~'),'s','h'),'~','s'),'h','r'),'r','~'),'d','l'),'~','d'),'l','~'),'m','w'),'~','m'),'w','f'),'f','~'),'g','y'),'~','g'),'y','p'),'p','~'),'b','v'),'~','b'),'v','k'),'k','~'),'x','~'),'j','x'),'~','j')) as VarChar(42)) As [morphed name2]
From
ts_users
;
【讨论】:
【参考方案5】:为什么不直接使用某种Random Name Generator?
【讨论】:
【参考方案6】:改为使用临时表,查询速度非常快。我只是在 4 秒内运行了 60K 行。我会继续使用这个。
DECLARE TABLE #Names
(Id int IDENTITY(1,1),[Name] varchar(100))
/* 打乱姓氏(随机选择另一个姓氏)*/
INSERT #Names
SELECT LastName
FROM Customer
ORDER BY NEWID();
WITH [Customer ORDERED BY ROWID] AS
(SELECT ROW_NUMBER() OVER (ORDER BY NEWID()) AS ROWID, LastName FROM Customer)
UPDATE [Customer ORDERED BY ROWID]
SET LastName=(SELECT [Name] FROM #Names WHERE ROWID=Id)
DROP TABLE #Names
【讨论】:
你仍然可能会得到一个糟糕的卷并且有两个......等等。 NewID() 生成 UUID。我的立场是正确的。【参考方案7】:以下方法对我们有用,假设我们有 2 个表 Customer 和 Products:
CREATE FUNCTION [dbo].[GenerateDummyValues]
(
@dataType varchar(100),
@currentValue varchar(4000)=NULL
)
RETURNS varchar(4000)
AS
BEGIN
IF @dataType = 'int'
BEGIN
Return '0'
END
ELSE IF @dataType = 'varchar' OR @dataType = 'nvarchar' OR @dataType = 'char' OR @dataType = 'nchar'
BEGIN
Return 'AAAA'
END
ELSE IF @dataType = 'datetime'
BEGIN
Return Convert(varchar(2000),GetDate())
END
-- you can add more checks, add complicated logic etc
Return 'XXX'
END
上述函数将有助于根据传入的数据类型生成不同的数据。
现在,对于每个表中没有单词“id”的每一列,使用以下查询生成进一步的查询来操作数据:
select 'select ''update '' + TABLE_NAME + '' set '' + COLUMN_NAME + '' = '' + '''''''' + dbo.GenerateDummyValues( Data_type,'''') + '''''' where id = '' + Convert(varchar(10),Id) from INFORMATION_SCHEMA.COLUMNS, ' + table_name + ' where RIGHT(LOWER(COLUMN_NAME),2) <> ''id'' and TABLE_NAME = '''+ table_name + '''' + ';' from INFORMATION_SCHEMA.TABLES;
当您执行上述查询时,它将为每个表和该表的每一列生成更新查询,例如:
select 'update ' + TABLE_NAME + ' set ' + COLUMN_NAME + ' = ' + '''' + dbo.GenerateDummyValues( Data_type,'') + ''' where id = ' + Convert(varchar(10),Id) from INFORMATION_SCHEMA.COLUMNS, Customers where RIGHT(LOWER(COLUMN_NAME),2) <> 'id' and TABLE_NAME = 'Customers';
select 'update ' + TABLE_NAME + ' set ' + COLUMN_NAME + ' = ' + '''' + dbo.GenerateDummyValues( Data_type,'') + ''' where id = ' + Convert(varchar(10),Id) from INFORMATION_SCHEMA.COLUMNS, Products where RIGHT(LOWER(COLUMN_NAME),2) <> 'id' and TABLE_NAME = 'Products';
现在,当您执行上述查询时,您将获得最终更新查询,这将更新您的表的数据。
您可以在任何 SQL Server 数据库上执行此操作,无论您有多少表,它都会为您生成可以进一步执行的查询。
希望这会有所帮助。
【讨论】:
【参考方案8】:另一个生成成形假数据集的站点,带有 T-SQL 输出选项: https://mockaroo.com/
【讨论】:
【参考方案9】:这是一种使用 ROT47 的方法,它是可逆的,另一种是随机的。您可以添加一个 PK 以链接回“未加扰”的版本
declare @table table (ID int, PLAIN_TEXT nvarchar(4000))
insert into @table
values
(1,N'Some Dudes name'),
(2,N'Another Person Name'),
(3,N'Yet Another Name')
--split your string into a column, and compute the decimal value (N)
if object_id('tempdb..#staging') is not null drop table #staging
select
substring(a.b, v.number+1, 1) as Val
,ascii(substring(a.b, v.number+1, 1)) as N
--,dense_rank() over (order by b) as RN
,a.ID
into #staging
from (select PLAIN_TEXT b, ID FROM @table) a
inner join
master..spt_values v on v.number < len(a.b)
where v.type = 'P'
--select * from #staging
--create a fast tally table of numbers to be used to build the ROT-47 table.
;WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
--Here we put it all together with stuff and FOR XML
select
PLAIN_TEXT
,ENCRYPTED_TEXT =
stuff((
select
--s.Val
--,s.N
e.ENCRYPTED_TEXT
from #staging s
left join(
select
N as DECIMAL_VALUE
,char(N) as ASCII_VALUE
,case
when 47 + N <= 126 then char(47 + N)
when 47 + N > 126 then char(N-47)
end as ENCRYPTED_TEXT
from cteTally
where N between 33 and 126) e on e.DECIMAL_VALUE = s.N
where s.ID = t.ID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 0, '')
from @table t
--or if you want really random
select
PLAIN_TEXT
,ENCRYPTED_TEXT =
stuff((
select
--s.Val
--,s.N
e.ENCRYPTED_TEXT
from #staging s
left join(
select
N as DECIMAL_VALUE
,char(N) as ASCII_VALUE
,char((select ROUND(((122 - N -1) * RAND() + N), 0))) as ENCRYPTED_TEXT
from cteTally
where (N between 65 and 122) and N not in (91,92,93,94,95,96)) e on e.DECIMAL_VALUE = s.N
where s.ID = t.ID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 0, '')
from @table t
【讨论】:
【参考方案10】:我自己也遇到了同样的问题,并想出了一个可能对其他人有用的替代解决方案。
这个想法是在名称上使用 MD5,然后将其最后 3 个十六进制数字映射到名称表中。您可以对名字和姓氏分别执行此操作。
3 个十六进制数字表示 0 到 4095 之间的小数,因此我们需要一个包含 4096 个名字和 4096 个姓氏的列表。
所以conv(substr(md5(first_name), 3),16,10)
(在mysql
语法中)将是一个从0 到4095 的索引,可以与包含4096 个名字的表连接。同样的概念也可以应用于姓氏。
使用 MD5(而不是随机数)保证原始数据中的名称将始终映射到测试数据中的相同名称。
您可以在此处获取名称列表:
https://gist.github.com/elifiner/cc90fdd387449158829515782936a9a4
【讨论】:
【参考方案11】:坦率地说,我不确定为什么需要这样做。您的开发/测试环境应该是私有的,位于防火墙后面,并且不能从网络访问。
您的开发人员应该受到信任,如果他们不辜负您的信任,您可以向他们追索法律。
我认为真正的问题应该是“我应该打乱数据吗?”,答案是(在我看来)“不”。
如果您出于某种原因将其发送到异地,或者您必须让您的环境可以通过网络访问,或者如果您偏执,我会实施随机切换。与其构建临时表,不如在每个位置和表中的随机行之间运行切换,一次交换一条数据。
最终结果将是一个包含所有相同数据但随机重组的表。我相信它也应该比你的临时表更快。
在 SQL 中实现Fisher-Yates Shuffle 应该足够简单......或者至少在读取数据库并写入目标的控制台应用程序中。
编辑 (2):T-SQL 中的即兴回答:
声明@name varchar(50) set @name = (SELECT lastName from person where personID = (random id number) 更新人 设置姓氏 = @name WHERE personID = (当前行的人名)
将其包装在一个循环中,并按照 Fisher-Yates 的指导方针修改随机值约束,然后就可以设置了。
【讨论】:
永远不会失败,您尝试将所有信息都放在帖子中,而您却忘记了一些重要的事情。这些数据需求也将用于我们公开可用的销售和演示环境。你的想法是我试图“切换”名称,但我的问题是字面意思,如何编码? 你可以试试 en.wikipedia.org/wiki/… Shuffle 它应该很简单,可以在 SQL 中实现......或者在一个简单的控制台应用程序中读取数据库并写入到目标数据库。 en.wikipedia.org/wiki/Fisher-Yates_shuffle 这是正确的链接,我想我必须在这里了解更多关于环境的信息;)更新我的答案。 会投赞成票,但“不确定为什么需要这样做”部分只是噪音。需要这样做的原因有很多。 @BrianMacKay:在我发布这篇文章的时候,我给出的确切原因不仅仅是“噪音”。然后,Chip 在他最初的评论中回答了我,这个网站应该如何运作。【参考方案12】:我现在正在我的公司从事这项工作——结果证明这是一件非常棘手的事情。您希望拥有真实的姓名,但不得透露任何真实的个人信息。
我的方法是首先创建姓氏到其他姓氏的随机“映射”,然后使用该映射来更改所有姓氏。如果您有重复的姓名记录,这很好。假设您有 2 个“John Smith”记录,它们都代表同一个真实的人。如果您将一条记录更改为“John Adams”,另一条更改为“John Best”,那么您的一个“人”现在有 2 个不同的名称!通过映射,所有出现的“Smith”都会更改为“Jones”,因此重复项(甚至家庭成员)仍以相同的姓氏结束,从而使数据更加“真实”。
我还必须打乱地址、电话号码、银行帐号等……我不确定如何处理这些。在加扰的同时保持数据“真实”无疑是一个深刻的话题。这一定是很多公司做过很多次了——谁以前做过?你学到了什么?
【讨论】:
以上是关于混淆/屏蔽/加扰个人信息的主要内容,如果未能解决你的问题,请参考以下文章