3 PRIVANALYZER:强制执行隐私政策的静态分析
本节介绍PRIVANALYZER,这是一个用于强制执行由PRIVGUARD追踪的隐私政策的静态分析器**。我们首先回顾LEGALEASE政策语言,我们使用它来正式编码政策,然后描述如何静态地强制执行它们**。正式模型推迟到附录A。
3.1 背景与设计挑战
LEGALEASE是一个不断增长的工作体系中的一个例子,这个体系探索了编码隐私政策的正式语言。相关工作的完整讨论出现在第5节。我们采用LEGALEASE来表达PRIVGUARD政策,因为它具有表达能力强、正式语义和可扩展性。
Sen等人开发了一个名为GROK的系统,该系统结合静态和动态分析来强制执行LEGALEASE政策。GROK构建了一个数据依赖图,该图编码了所有敏感数据的流动,然后应用一组推理规则来检查图中的每个节点是否满足政策。GROK结合了系统日志的分析和有限的静态分析来构建图表。
GROK方法提出了两个挑战。首先,该方法是一种启发式:它检查程序的语法属性和程序的个别执行(通过系统日志),因此可能会因为隐式流而错过政策违规行为。其次,GROK方法要求使整个数据流图显式化;在具有许多数据流的大型系统中,构造这样的图可能是不可行的。
PRIVANALYZER被设计为一种替代方案,旨在解决这两个挑战。它使用基于抽象解释的静态分析,而不是GROK的启发式分析,并通过构建可组合的剩余政策来避免使数据流图显式化。
3.2 策略语法与语义
PRIVANALYZER执行在LEGALEASE 中指定的隐私策略,一个用于使用各种类型的属性表达策略的框架。属性在概念格中被组织起来,提供了属性值的偏序关系。我们根据图3中的语法来表达策略(这与LEGALEASE的语法略有不同)。一个策略由顶级的ALLOW关键字开始,其后是用AND(表示合取)和OR(表示析取)分隔的条款。例如,以下简单的策略指定医生或研究者可以检查分析结果,只要分析中没有使用未成年人的记录:
ALLOW (ROLE Doctor OR ROLE Researcher)
AND FILTER age >= 18
Sen等人使用一组推理规则和每个属性的概念格给出的偏序关系定义了LEGALEASE策略的形式语义。我们采取相同的方法,但是使用基于抽象域的新属性框架而不是概念格。我们的方法使PRIVPOLICY能够以更具表达力的要求编码策略,如基于行的访问控制和使用隐私增强技术等属性。属性是LEGALEASE中的基本构建块。Sen等人描述了一组有用的属性。我们用两个新属性扩展了这个集合:FILTER编码基于行的访问控制要求,PRIVACY要求使用隐私增强技术。
角色:ROLE属性控制谁可以检查数据的内容。角色被组织成部分有序的层次结构。一个特定个体可能拥有多个角色,一个特定的角色规范可能代表多个个体。例如,医生角色可能代表具有许多不同专业的医生。以下策略说明只有具有肿瘤学家角色的个体可以检查其涵盖的数据:
ALLOW ROLE Oncologist
模式:SCHEMA属性控制可以检查数据的哪些列。例如,以下策略允许肿瘤学家检查年龄和病情列,但不允许检查其他列:
ALLOW ROLE Oncologist
AND SCHEMA age, condition
隐私:PRIVACY属性通过要求使用隐私增强技术来控制数据的使用方式。作为可用机制谱系的代表性样本,我们的实现支持以下(易于添加):(1)去标识化(或伪名化);(2)聚合;(3)k-匿名性;(4)`-多样性[41];(5)t-接近性[40];(6)差分隐私。例如,以下策略允许肿瘤学家在特定隐私预算的差分隐私保护下检查年龄和病情列:
ALLOW ROLE Oncologist
AND SCHEMA age, condition
AND PRIVACY DP (1.0, 1e-5)
过滤器:FILTER属性允许策略指定必须从分析中排除某些数据项。例如,以下策略说明肿瘤学家可以在差分隐私保护下检查年龄超过18岁个体的年龄和病情:
ALLOW ROLE Oncologist
AND SCHEMA age, condition
AND PRIVACY DP (1.0, 1e-5)
AND FILTER age > 18
编辑:REDACT属性允许策略要求部分或完全编辑列中的信息。例如,以下策略要求分析时编辑ZIP代码的最后3位数字(例如,用星号替换)。(2:)符号取自Python的切片语法,表示从字符串的第三个字符到结尾的子字符串。
ALLOW ROLE Oncologist
AND SCHEMA age, condition
AND PRIVACY DP (1.0, 1e-5)
AND FILTER age > 18
AND REDACT zip (2 : )
目的:PURPOSE属性允许策略限制数据可被分析的目的。例如,以下策略允许在满足所有上述要求的情况下,出于公共利益目的使用年龄和医疗状况:
ALLOW ROLE Oncologist
AND SCHEMA age, condition
AND PRIVACY DP (1.0, 1e-5)
AND FILTER age > 18
AND REDACT zip (2 : )
AND PURPOSE PublicInterest
3.3 PRIVANALYZER 概览
PRIVANALYZER 通过抽象解释来执行其静态分析,这是一种用于程序正确性分析的通用框架。抽象解释通过在抽象值而非具体(常规)值上运行程序来工作。抽象值被组织在抽象域中:部分有序的抽象值集合,能够代表编程语言中所有可能的具体值。一个抽象值通常代表其所代表的具体值共有的特定属性。在PRIVANALYZER中,抽象值基于前文描述的抽象域。
我们对静态分析的方法基于抽象解释框架的一个新颖实例化,在此实例化中,我们将策略编码为抽象值。该方法在图4中进行了总结。抽象解释的使用使我们能够系统地构建静态分析系统,确保它与属性值的预期语义相对应。
分析Python程序。抽象解释的典型方法是构建一个计算抽象值的抽象解释器。对于像Python这样的复杂、通用目的语言,这种方法需要大量的工程工作。我们没有从头开始构建一个抽象解释器,而是重用了标准。我们将附有隐私策略的抽象值嵌入为Python对象,并定义在这些对象上的抽象值操作作为方法。
例如,Pandas库定义了对具体数据帧的操作;PRIVANALYZER定义了用于抽象数据帧的AbsDataFrame类。AbsDataFrame类具有与Pandas DataFrame类相同的接口,但其方法被重新定义为对附有策略的抽象值进行计算。我们称重新定义的方法为函数摘要,因为它总结了原始方法的与策略相关的行为。例如,Pandas的索引函数__getitem__用于过滤,因此PRIVANALYZER对这个函数的函数摘要会从策略中移除已满足的FILTER属性。
def __getitem__(self, key):
...
if isinstance(key, AbsIndicatorSeries):
# ‘runFilter‘ removes satisfied FILTER attributes
newPolicy = self.policy.runFilter(...)
return Dataframe(..., newPolicy)
...
多步骤分析和剩余策略。如图4所示,PRIVANALYZER的输出是一个剩余策略。剩余策略是程序具体输出的新策略——它包含分析程序尚未满足的要求。对于多步骤分析,每个分析步骤可以作为一个单独的分析程序输入到PRIVANALYZER中,前一步骤的剩余策略成为下一步骤的输入策略。PRIVANALYZER是组合式的:如果将多个分析合并为一个单一的分析程序,那么PRIVANALYZER为多步骤分析返回的最终剩余策略将至少与单步版本一样严格。在PRIVGUARD中使用剩余策略使得不需要明确构建全局数据流图就可以进行组合分析,解决了之前提到的GROK面临的挑战。
处理库问题。对于许多静态分析方法,包括抽象解释器,扩展到大型程序是一个主要挑战。库通常呈现最大的挑战,因为它们往往既庞大又复杂;如果目标程序依赖于一个大型库,那么甚至可能无法分析一个相对较小的程序。在我们的设置中(用于数据处理的Python程序),这一点尤其正确,这些程序通常利用大型库,如pandas(327,007行代码)、scikit-learn(184,144行代码)、PyTorch(981,753行代码)和Tensorflow(2,490,119行代码)。更糟糕的是,许多库出于性能原因是用多种语言(例如Python和C/C++)编写的,因此需要对这些每一种语言进行分析。
我们的解决方案是开发这些库的抽象功能规范,如之前显示的AbsDataFrame示例,以函数摘要的形式。我们在分析过程中使用函数摘要,而不是库本身的具体实现。这种方法允许PRIVGUARD即使对于利用用多种语言编写的极大型库的分析程序也能执行策略。
处理库的方法需要一个具有库知识的领域专家来实现其规范。根据我们的经验,数据科学社区大体上已经同意了一组重要的常用库(例如NumPy、pandas、scikit-learn等),因此为少数几个库提供规范就足以处理大多数程序。为了从经验上验证这一猜想,我们从Kaggle平台随机选取了200个程序,并计算了它们使用的库(图5)。结果确认了大多数数据分析程序使用类似的库。我们已经为最常用的库实现了规范(第4节)。幸运的是,库函数的抽象行为往往比其具体行为简单。我们已经实现了主要针对Numpy、Pandas和scikit-learn的380个函数摘要,并且正在积极添加更多库的函数摘要。
我们需要正确的规范来严格执行隐私策略。正确实现规范重要性的一个示例是重命名函数。狡猾的内部攻击者可能想通过重命名敏感列来绕过静态分析。一个在架构和隐私条款中都重命名列的正确规范应该能够缓解这种攻击。为了减少由于此类错误带来的风险,函数摘要应该是开源的,以便社区可以帮助检查其正确性。
与动态方法的比较。我们选择对PRIVANALYZER进行静态分析的动机,主要基于两个方面超过动态方法的优势:(1) 能够处理隐式数据流;(2) 目标是添加最小的运行时开销。检测隐式流的能力是静态分析系统,包括PRIVANALYZER的一大优势。不像动态方法,PRIVANALYZER不能被设计来混淆执行流程的复杂控制流所击败。例如,数据主体可能指定策略ALLOW REDACT name (1 : ),要求对大部分姓名列进行编辑。分析师可能编写以下程序:
if data.name == 'Alice':
return 1
else:
return 2
即使这个程序没有直接返回data.name的值,它显然违反了策略。这种违反是由于姓名列到返回值的隐式流造成的。返回值为1允许分析师确定数据主体的名字是Alice。这种类型的隐式流对于动态分析来说是一个重大挑战,因为动态分析只执行每个条件的一个分支,并且不能对未采取的分支做出结论。动态分析必须对在条件中使用敏感值施加重大限制,或者允许由于隐式流导致的不准确性。
另一方面,像PRIVANALYZER这样的静态分析器可以进行最坏情况分析,检查两个分支。PRIVANALYZER的分析使用抽象解释器执行两个分支,并返回两个分支的最坏情况结果。对于迭代次数没有上限的循环,分析结果代表了最坏情况的结果,无论运行时执行了多少次迭代。这种能力以可能缺乏精确性为代价——分析可能会拒绝实际上是安全运行的程序。然而,我们的评估表明,PRIVANALYZER分析对于执行数据分析的程序而言足够精确。像PRIVANALYZER这样的静态分析工具不要求策略规范意识到隐式流,因为它在其结果中结合了两种类型的流。
3.4 通过示例了解PRIVANALYZER
1. 示例:一个用于数据预处理和执行KMeans聚类分析的程序
df = pd.read_csv('german_credit_data.csv')
# 定义数值型变量的列表。
numerical = ['Credit', 'Age', 'Duration']
# 定义类别型变量的列表。
categorical = ['Sex', 'Job', 'Housing', 'Saving accounts', 'Checking account', 'Purpose']
# 定义未使用变量的列表。
unused = ['Unnamed: 0']
# 从数据框中删除未使用的列。
df = df.drop(columns=unused)
# 过滤数据框,只保留年龄大于25岁的记录。
df = df[df['Age'] > 25]
# 对于每个类别型变量,填充缺失值为该列的众数。
for cat in categorical:
df[cat] = df[cat].fillna(df[cat].mode().values[0])
# 对数值型变量进行对数转换以正态化其分布。
df_cluster_log = np.log(df[['Age', 'Credit', 'Duration']])
# 初始化StandardScaler实例。
scaler = StandardScaler()
# 对经过对数转换的数据进行标准化处理。
cluster_scaled = scaler.fit_transform(df_cluster_log)
# 初始化一个空列表,用于存储不同k值下的总平方距离。
Sum_of_squared_distances = []
# 定义一个范围,从1到15,用于KMeans聚类的不同k值。
K = range(1, 15)
# 对于每个k值,进行KMeans聚类,并计算总平方距离。
for k in K:
km = KMeans(n_clusters=k)
km = km.fit(cluster_scaled)
Sum_of_squared_distances.append(km.inertia_)
“抽象数据框架”(Abstract Data Frameworks, ADFs)
“抽象数据框架”(Abstract Data Frameworks, ADFs),是假想的或概念性的框架,用于在数据处理和分析中表示和跟踪数据隐私策略。每个ADF代表一组与数据相关的隐私策略和结构(schema),可以用于确保数据处理活动符合特定的隐私要求。下面是对每个ADF表达内容的解释:
-
ADF(SCHEMA = {age, credit, duration}, FILTER(age)=[18,inf], PRIVACY DeId):
- 这个ADF定义了一个数据架构,包括三个字段:年龄(age)、信用(credit)和期限(duration)。同时,它指定了一个过滤策略,要求只包含年龄在18岁及以上的记录(FILTER(age)=[18,inf])。此外,它还应用了一个隐私策略(PRIVACY DeId),可能意味着对数据进行去标识化处理以保护个人隐私。
-
ADF(SCHEMA = {age, credit, duration}, PRIVACY DeId):
- 这个ADF同样定义了包含三个字段的数据架构,但没有明确的年龄过滤策略(与前两个不同)。它仍然应用了去标识化的隐私策略(PRIVACY DeId),用于保护隐私。
-
ADF(PRIVACY DeId):
- 这个ADF不指定具体的数据架构,仅仅强调隐私策略,即数据需要经过去标识化处理(DeId)。这可能意味着它适用于任何数据集,只要数据被适当地去标识化。
-
ADF():
- 这表示一个空的ADF,没有指定数据架构或隐私策略。这可能表示数据处理的某个阶段不涉及特定的隐私限制,或者所有先前的隐私策略已经得到满足和处理。
3. ADF工作的原理以及作用
-
初始状态(ADF(SCHEMA = {age, credit, duration}, FILTER(age)=[18,inf], PRIVACY DeId)):
- 在数据处理的初始阶段,我们有一个数据集的架构,包含年龄(age)、信用(credit)和期限(duration)。对应的隐私策略要求(1)过滤掉18岁以下的个体(FILTER(age)=[18,inf]),以及(2)对数据进行去标识化处理(PRIVACY DeId)。这个ADF代表数据处理开始时的隐私要求和数据结构。
-
数据预处理(去除未使用的列和过滤年龄):
- 当代码删除未使用的列和过滤掉25岁以下的个体时(对应图中的操作2和3),这些操作对隐私策略没有直接影响,但是帮助进一步满足年龄过滤的要求。尽管图中没有明确改变ADF,但实际上这可以视为在进一步满足和细化初始ADF中的FILTER条件。
-
填充缺失值(对于每个类别型变量):
- 图中的操作4涉及填充缺失值,这是常见的数据预处理步骤,目的是让数据更完整,准备进行更深入的分析。这个步骤不直接影响隐私策略,因此在ADF的上下文中不会引起变化。
-
特征变换和标准化(对数变换和标准化):
- 图中的操作5和6展示了对特定数值型变量进行的对数变换和标准化。这些操作可能改变数据的原始含义和范围,与隐私策略相关,因为它们可能影响数据的识别风险。在这一步骤,我们可能需要重新评估数据的隐私策略,确保去标识化的要求仍被满足。然而,具体的ADF变化在图片中没有直接展示,但原则上,这应该考虑到如何维持或调整PRIVACY DeId策略。
-
KMeans聚类:
- 图中的操作7和8涉及使用KMeans算法对处理后的数据进行聚类。这一步是数据分析过程的一部分,可能不直接影响隐私策略,除非聚类结果需要公开。在聚类分析后,如果分析结果需要共享,我们需要确保这些结果遵循隐私DeId策略,即不泄露任何个人信息。
在整个过程中,每个ADF的变化和更新反映了在数据处理和分析的每个阶段对隐私策略的遵守情况。通过使用ADF,数据科学家可以系统地追踪和确保他们的数据处理流程遵循所有相关的隐私要求,从而保护个人数据免受滥用。
3.5 挑战性语言特性
我们现在讨论PRIVANALYZER对几种挑战性语言特性采取的方法。
条件判断。依赖抽象值的条件判断要求抽象解释器运行两个分支并计算两个结果的上界。由于Python不允许重定义if语句,我们在PRIVGUARD中添加了一个预处理步骤,这一步骤通过运行两个分支来转换条件判断。
循环。循环传统上是抽象解释器处理起来最具挑战性的结构。幸运的是,用于数据分析的Python程序中的循环通常属于受限类别,就像图6中的示例那样。这个示例中的两个循环都是在常数值上进行的——因此我们的抽象解释器可以简单地按照常数所要求的次数运行每个循环体。
在抽象值上的循环更具挑战性,简单的方法可能永远不会终止。为了解决这个问题,我们为PRIVANALYZER中使用的每个抽象域定义了一个扩展操作符。扩展操作符强制循环到达一个固定点;在我们的示例中,扩展对应于假设循环体将在整个数据框上执行。
别名。抽象解释面临的另一个挑战来自别名问题,其中两个变量指向同一个值。有时,分析无法确定变量引用哪个抽象值。在这种情况下,也无法确定对变量的副作用的结果。
我们利用现有Python解释器的方法有助于解决这个挑战:在PRIVANALYZER中,所有变量引用都是具体评估的。在大多数情况下,引用指向具体对象,所以分析与具体执行完全相符。然而,在少数情况下,这种方法导致分析精度降低。例如,如果一个变量在条件的两个分支中都被重新赋值,PRIVANALYZER必须假设最坏情况的抽象值(即具有最严格策略的值)在两种情况下都被分配给变量。这种方法在我们的设置中效果很好,其中条件判断和别名都相对罕见。
3.6 属性执行
我们现在描述我们合规分析的一些属性特定细节。SCHEMA(模式),FILTER(过滤器)和REDACT(删除)属性可以被正式定义,并且可以通过PRIVANALYZER进行合规性检查。在我们的实现中,相关的函数摘要会在库的具体实现满足相应要求的情况下,从隐私政策中移除该属性。因此,我们的摘要实现了这些函数的抽象解释。请注意,PRIVANALYZER假设没有摘要的函数不满足任何政策要求。因此,PRIVANALYZER是不完整的:尽管满足相关政策,但由于缺乏足够的函数摘要,一些程序可能会被拒绝。
隐私。隐私属性也由PRIVANALYZER检查。分析程序可以通过调用删除识别信息的函数(例如,聚合记录或训练机器学习模型)来满足去标识化要求。程序可以通过调用提供这些属性的特定函数来满足k-匿名,多样性,t-相似性或差分隐私要求。我们的函数摘要包括当前文献中的代表性实现:IBM差分隐私库,K-匿名库和Google的Tensorflow隐私库。
在执行差分隐私属性时存在两个微妙之处。首先,满足差分隐私的程序还需要跟踪隐私预算。默认情况下,PRIVGUARD为每个提交数据源跟踪单个全局累积隐私成本(ε和δ的值),并在隐私成本超过预算金额后拒绝新的分析程序。PRIVANALYZER报告单个分析程序的隐私成本,使得PRIVGUARD能够更新全局隐私成本。在具有许多分析员执行不同分析的环境中,单个全局隐私预算可能会迅速耗尽。解决此问题的一种方法是生成差分隐私合成数据,这些数据可以在以后的分析中使用而无需进一步的隐私成本。高维矩阵机制(HDMM)是一个用于此目的的算法示例,美国人口调查局用于发布差分隐私数据。在PRIVGUARD中,可以在像HDMM这样的算法输出上执行任意多的附加分析,而不会消耗隐私预算。另一个解决方案是在记录级别进行细粒度的预算控制(如ProPer)或在静态定义的“区域”级别进行(如UniTraX)。前者更精确,但需要在隐私预算耗尽时静默丢弃记录,从而导致偏倚的结果。这两种方法都允许在不断增长的数据库中对新数据进行持续分析(例如,每天仅对当天获得的新数据运行特定的查询工作负载)。其次,为了计算隐私预算,PRIVGUARD 初始化一个变量来跟踪差分隐私函数之前的预处理步骤的敏感性。预处理函数摘要应该操纵该变量以指定它们对敏感性的影响。如果在差分隐私函数之前的任何函数中缺少此类规范,PRIVGUARD将抛出警告并将识别差分隐私要求为未满足。
角色:角色属性由身份验证技术强制执行,例如密码、两因素身份验证,甚至生物识别身份验证。此外,角色属性还记录在下一段描述的可审计系统日志中,分析员和数据管理员将对虚假身份负责。
目的:目的属性本质上是非正式的。因此,我们采取一种基于问责制的方法来检查目的的合规性。分析员在提交分析程序时可以指定其目的,并可能无意或恶意指定无效目的。这些目的将由PRIVANALYZER用于满足目的要求。PRIVGUARD生成一个审计日志,记录分析员、分析程序和声明的目的。因此,系统中发生的所有分析都可以在事后进行验证,并且分析员可以因使用无效目的而在法律上承担责任。