程序混淆的可行性?
1. 引言
Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs 提出了关于混淆计算机程序任务的一些不可能性结果,激起了一些讨论,也产生了一些困惑(如,见 On the (Im)possibility of Obfuscating Programs讨论)。作为该论文的作者之一,在本文Boaz Barak尝试解释(我个人的看法)这些结论的意义,以及它们不意味着什么。
2. 什么是程序混淆器?
程序混淆器是一种编译器,它将一个程序作为输入,并产生另一个程序作为输出。称输出程序为混淆程序。(注意,与标准编译器一样,输入程序和混淆程序不必使用相同的编程语言。如,输入程序可以用 C 编写,而输出程序可以用机器语言编写。然而,事实证明,这种差异并没有太大影响。)
希望混淆器满足以下三个条件:
- 1)功能性:
混淆程序应具有与输入程序相同的功能(即输入/输出行为)。为了简化起见,在此专注于计算单一非交互式函数的程序。对于这样的程序,功能要求是:- 输入程序和混淆程序应该计算相同的程序。
- 2)效率:
混淆程序不应比输入程序低效太多。允许混淆程序有一些开销,但不应该出现运行时间比输入程序慢得多的情况,比如指数级的慢。 - 3)混淆性:
这是最重要的条件,也是最难定义的条件。此时,可以说混淆程序应该确实有一些“混淆”特性。这意味着,即使原程序的代码非常容易理解,如拥有有意义的注释、函数和变量名称、模块化设计,混淆程序的代码仍然应该难以理解。可以推测,任何查看混淆程序代码的黑客,所获得的也仅仅是头疼而已。
本文不过多阐述为什么人们对混淆器感兴趣,以及为什么有人会认为它们可能存在。在此只想说,混淆器对于许多应用场景非常有用,特别是软件保护和数字版权管理(digital rights management,DRM),而且相信任何曾经试图理解他人代码的人,都能理解为什么混淆器可能存在。更多讨论,参见 Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs。
3. 完全破坏一个混淆器
在没有明确定义混淆特性是什么的情况下,确实有一个最低要求:
- 黑客不能从混淆程序中完全恢复输入程序的源代码。
如果黑客成功地从混淆程序中恢复了程序 P 的源代码,那么说该混淆器在程序 P 上完全失败。现在有两个直接的观察结果:
- 1)很容易避免在某些程序上完全失败:
考虑“注释剥离”混淆器。也就是说,一个编译器,它唯一做的事情就是从输入程序中删除所有注释。这个混淆器显然满足功能性和效率要求。此外,对于某些程序,如果用“注释剥离”混淆器来混淆它们,黑客将无法从混淆程序中恢复源代码。如,如果原程序的注释中包含一个秘密数字,并且这个秘密数字在代码的其他地方没有被引用,那么使用“注释剥离”混淆器就能达到这个效果。 - 2)每个混淆器在某些程序上都会完全失败:
考虑一个打印其自身源代码的程序。如果一个混淆器满足功能性要求,那么任何黑客通过执行混淆程序,就可以获得原始源代码的打印输出。这可以推广为以下结论:- 如果仅通过执行某程序(在不同输入上),就可以恢复它的原始源代码,则说该程序是可学习的。
- 显然,每个混淆器都会在可学习的程序上失败,然而这并不是真正“有趣”的失败。
- 此外,实际中想要混淆的函数(如哈希函数或block cipher 分组加密算法)通常离可学习的程序非常远。
- 如果仅通过执行某程序(在不同输入上),就可以恢复它的原始源代码,则说该程序是可学习的。
4. Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs的结论是什么?
Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs的主要结论如下:
- 存在某程序(实际上是一系列程序,但在此忽略这一点),它具有以下特点:
- 一方面它是(强)不可学习的,
- 另一方面每个混淆器在给定该程序作为输入时都会完全失败。
对于该结论:
- 1)该结论为何强大?
这个结论之所以强大,是因为混淆器在这些程序上完全失败。并不是说混淆器能隐藏一些部分信息,而是黑客可以从任何 supposedly 混淆的版本中完全恢复原始源代码。这个结论强大之处还在于,这些程序是强不可学习的,实际上它们非常接近实际中人们希望混淆的那类函数。 - 2)该结论为何薄弱?
这个结论之所以薄弱,是因为它仅仅表明每个混淆器在某些(不可学习的)程序上会完全失败。当然,在某种意义上,这是所能期望的最佳结果,因为即使是简单的注释剥离混淆器也不会在所有程序上完全失败。- 尽管如此,一个商业混淆器制造商可以合理地说,他的客户并不关心混淆 Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs 中构造的这一系列程序。他们关心的是混淆他们的程序,而在此尚未证明这会不安全。
4.1 该结论意味着什么?
要理解该结论的含义,需要了解当今存在的两类密码学构造。理解这一点非常有益,甚至可能比理解混淆结果更为重要。
本文将所有密码学构造(无论是加密、数字签名、哈希函数还是混淆器),依据它们所具备的安全性水平,分为两类:
- 明确定义的安全性(well-defined security) 构造
- 模糊安全性(fuzzy security) 构造
4.1.1 明确定义的安全性
一个很好的例子就是数字签名。今天,数字签名的标准安全定义是存在性不可伪造性(existential unforgeability)。大致来说,这意味着:
- 即使黑客可以访问许多她选择的消息的签名,她仍然无法伪造任何单个新消息的签名。
这个定义似乎比所需的更强大。事实上,在大多数使用数字签名的应用中,黑客很难获得她选择的消息的签名。此外,伪造一个任意消息的签名,实际上并不能帮助她破坏系统,除非这个消息是有意义的。如:
- 在某些应用中,伪造一个不是英语的消息的签名对她是无用的,
- 而在其他应用中,伪造一个不符合标准 XML 语法的消息的签名也是无用的。
尽管“存在性不可伪造性”的定义相当强大,但它被认为是“正确”的安全定义,因为它使我们能够将数字签名应用于各种应用,并且可以确保在应用中使用的消息类型(无论是英语、法语还是 XML)不会引发安全问题。实际上,构建安全应用程序本身就足够复杂了,不需要担心数字签名可能存在的微妙弱点。此外,实际上有几个已知的构造被广泛认为能满足这一强安全定义。虽然目前没有任何一个构造被无条件证明能满足这个定义,但对于其中一些构造,可以证明,如果存在伪造者,那么就存在一个已知的困难计算问题(比如高效分解大整数问题)的算法。
4.1.1.1 明确定义的安全性 与 已证明安全性 的关系
尽管这些概念相关,明确定义的安全性不应与已证明或可证明的安全性混淆。
- 明确定义这个形容词指的是密码学概念,如数字签名、公钥加密或抗碰撞哈希函数。
- 而可证明这个形容词指的是一个特定的实现,如 Cramer-Shoup 或 RSA 加密系统,它意味着存在一个数学证明,表明如果有人能够破坏该实现,那么就存在一个已知的困难计算问题的高效算法。
当然,如果密码学概念本身没有明确定义,那么就不可能有这样的数学证明(因为安全属性不是一个明确定义的数学陈述)。因此,拥有明确定义的密码学概念是已证明安全性的 必要 条件。
4.1.2 模糊安全性
模糊安全性实际上是密码学家们在过去几十年里所使用的概念。通过模糊安全性,指的是以下过程:
- 某个人提出某种密码学算法(可以考虑一个加密方案,但也可以考虑其他例子,如哈希函数或混淆器)。
- 然后,他对这个算法的安全性做出一些模糊的声明,人们开始将它应用于国家或个人安全等至关重要的领域。
- 接着,另一个人(黑客)设法破坏了这个算法,通常对其用户造成灾难性的结果,之后发明者或用户要么“调整”算法,希望新的调整能够保持安全,要么发明一个新的算法。
模糊安全性的标志:
- 不是它经常被破解并带来灾难性后果。这只是一个副作用。
- 其标志在于从来没有严格的安全性定义,因此也没有清晰表述的关于该算法安全属性的猜想。
模糊安全性的另一个常见特点是保密算法。事实上,这并不令人惊讶——如果你不知道算法的具体安全性是什么,也不了解是什么使它安全,那么保持算法的机密性似乎是延长黑客发现漏洞时间的好方法。
在此并不是要贬低历史上构建“模糊安全”算法的密码学家们。许多人是天才,拥有惊人的直觉,为现代密码学铺平了道路。然而,这并不意味着现在仍然需要使用他们的算法。如今,对于大多数密码学任务,不需要使用模糊安全性,因为有非常好的安全性定义,以及合理猜测能满足这些定义的构造。
- 唯一的例外确实是混淆器,到目前为止,没人提出一个好的混淆器安全性定义。
- 而且(这并非巧合),混淆器研究的进展似乎就是上述提到的类型。
- 某人构建了一个混淆器,行业开始使用它,然后它被破解,接着他们构建了一个新的混淆器(并/或尝试通过法律使破解混淆器成为非法行为)。
4.1.2.1 模糊安全性有什么问题?
在此用一个问题来回答 “模糊安全性有什么问题?” 这个问题:
- 模糊的软件工程有什么问题?
在此指的是对常规(非加密)软件模块的模糊规格。如,会在代码中使用以下规格的函数吗?
/******************************************************** Hopeful adder - ** 通常返回其两个输入的和。 ********************************************************/int hopeAdd(int x, int y);
实际上在代码中使用hopeAdd函数比使用“模糊安全”的加密模块更有意义。原因在于:
- 你可以测试普通用户的输入,但你不能测试黑客攻击。
即:
- 如果你在代码中使用hopeAdd,并通过数小时的Beta测试,且从未遇到它在某个输入上失败的情况,那么实际上这意味着在正常使用应用程序时,这个函数几乎永远不会失败。
- 然而,如果你在应用程序中使用了一个“模糊安全”的加密模块,并且它在Beta测试阶段没有被破解,这并不意味着任何关于该应用程序在现实世界中的安全性。
- 因为黑客很可能会以Beta测试阶段未预料到的方式攻击该应用程序,并故意提供导致该模块失败的“奇怪”输入。
- 这就是为什么在Boaz Barak看来,明确定义的规格在安全性中实际上比在其他软件工程领域中更为重要。
- 当然,正如所有程序员所知,使用严格规范的组件并不能保证整个系统一定是安全的。
- 然而,使用模糊规范的组件几乎可以保证不安全。
4.2 难道所有系统本来就不安全吗?
确实,可能所有大型系统都有安全漏洞,就像所有大型系统也有其他漏洞一样。然而,如果使用的是明确定义的安全组件,那么理论上可以构建安全系统,就像理论上可以构建无bug的程序一样。
唯一的问题是,构建这样的“完美”系统是非常非常困难的,尤其是在大型系统中。
尽管如此,随着时间的推移,通过反复的测试和审查,系统可以逐步趋向无bug状态(前提是这段时间确实用于修复bug,而不是增加新特性——也许最好的例子就是 Knuth 的 TeX 程序)。
如果使用的是模糊安全的组件,这种趋向是无法实现的。
5. 混淆器能享有明确定义的安全性吗?
确实,这是激发论文 Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs 的问题。最初的希望是,可以找到一个软件混淆器的正式定义,并且构造一个可以被证明满足这个定义的方案(基于一些广泛认可的计算猜想),从而将混淆器从模糊安全的领域引入到明确定义的(并且经过证明的)安全领域。不幸的是,在整个研究过程中,每当提出一个定义时,最终都会找到一个反例,证明这个定义无法满足。 On the (Im)possibility of Obfuscating Programs论文作者认为的最弱的自然混淆器定义是:
定义 1:如果一个编译器满足功能性和效率条件,那么它是一个混淆器,前提是对于每个输入/输出行为难以学习的程序 P,黑客无法从 P 的混淆版本中重建出 P 的源代码。
上面提到的反例表明,定义 1 是不可能满足的。这意味着,如果想要一个明确定义且有意义的混淆概念,就需要提出一个新的混淆器定义,使得:
- (1)新的定义足够弱,以至于一个满足它的混淆器的存在不会立即被我们的反例所反驳;
- (2)但新的定义又足够强,能够提供某种有意义的安全性感知。
现在,关于如何使混淆器具备明确定义的安全性的问题,变成了提出一个满足以下属性(1)和(2)定义的问题。似乎有两种自然的方式来获得这样的定义:
-
1) 放宽隐藏属性:
一种方法可能是尝试通过放宽混淆器的定义,来说明混淆器不需要隐藏输入程序的所有细节,只需要隐藏其中的一部分。然而,定义 1 已经将这一要求放宽到极限,定义 1 说的是混淆器需要做的唯一事情就是防止黑客完全重建原始源代码。 -
2) 放宽程序类别:
一个更有前景的方向是说混淆器只需要对特定的子类程序起作用。这个子类应该具有以下属性:- (1)它不应该包含由 Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs 构造的反例程序,
- (2)但它应该包含人们在实践中希望混淆的有用程序。
在论文中讨论了这一方向。很难找到这样的自然子类,因为似乎很难区分所构造的反例与人们感兴趣的有用程序之间的区别。
特别是,该论文的一些反例可以通过对流行的哈希函数和私钥加密方案进行相对轻微的修改来获得。如,在论文中展示了一个伪随机程序家族,在该程序上混淆器会完全失败。
这意味着,当放宽程序类别时,必须获得一个包含“自然的”伪随机函数的放宽类别,这些函数是人们希望混淆的,但不包含“我们的”伪随机函数,尽管“我们”的伪随机函数的输入/输出行为与“自然”伪随机函数的输入/输出行为是不可区分的(因为它们都是伪随机的)。
还要注意,必须很容易判断一个程序是否属于这个类别,因为否则用户无法知道是否可以安全地混淆她的程序。从某种直观的角度来看,似乎如果你找到了这样一个类别,那么你就证明了某种形式的混淆是不存在的……
因此,似乎论文 Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs 确实对任何希望将混淆器的状态从当前的“模糊安全”水平提升到明确定义的安全的人,提出了非常严峻的挑战。最后,关于混淆器有几个有趣的概念,对于其中许多,在该论文中的问题比结论更多。可参考 Boaz Barak等人2001年论文 On the (Im)possibility of Obfuscating Programs 了解更多细节。
6. 混淆器是否没用?
Boaz Barak 2001年时,认为当前的混淆器(或一般的模糊安全组件)可能有一些有限的用途。
这些用途通常出现在能够进行安全性测试的情况下,并且安全漏洞不会导致灾难性的后果。
如,在版权保护的背景下,确实可以检测到“有趣的”安全漏洞:
- 如果一个黑客单独行动,突破了安全性并复制歌曲供自己使用,这种情况是无法被检测到的,但也仅对唱片公司造成微不足道的损害。
- 相比之下,要想造成一些损害,必须存在一个活跃的“黑市”来交易受版权保护的材料,而这样的市场会被工作室察觉,工作室会知道安全性已经被破坏。
然而,为了能够利用这些信息,系统必须是“计划性地失败”的,也就是说,应该能够轻松重新部署一个替代实现、升级版本等……过去的情况并非如此,如在DVD CSS算法中。
因此,只有在理解其固有限制的情况下,模糊安全才应该被使用。当然,在可能的情况下,使用明确定义的(并且最好是经过证明的)安全性要好得多。
参考资料
[1] Boaz Barak博客Can We Obfuscate Programs?