如何锁定编译后的Java类以防止反编译?

105

如何锁定编译后的Java类以防止反编译?

我知道这可能是互联网上讨论得非常充分的话题,但在参考了它们后,我仍然没有得出任何结论。

许多人确实建议使用混淆器,但它们只是使用难以记忆的字符序列重命名类、方法和字段,那敏感的常量值呢?

例如,您基于基于密码的加密技术开发了加密和解密组件。在这种情况下,任何普通的Java人都可以使用JAD来反编译类文件,并轻松检索密码值(定义为常量)以及salt,从而通过编写小型独立程序解密数据!

或者应该在本地代码中构建此类敏感组件(例如,VC++),并通过JNI调用它们吗?


即使是反汇编的本地代码对某些人来说也相当易读,你可能需要使用一些混淆器或手工汇编来使其不明显(常见的C++编译优化已经足够易读了)。无论在用户设备上运行的代码都可以被拦截。虽然这种拦截的成本/技能要求可能相当高,例如破解智能卡“防篡改”芯片代码并非易事,只有顶级设备和技能才能做到。对于Java类...我不会过多地费心,你可能可以加密它们以足以让脚本小子望而却步,但不会更多。 - Ped7g
9个回答

103
一些高级Java字节码混淆器不仅仅进行类名混淆。例如,Zelix KlassMaster 还可以以使其难以跟踪且作为优秀代码优化器的方式来混淆您的代码流程。
此外,许多混淆器也能够混淆您的字符串常量并删除未使用的代码。
另一个可能的解决方案(不一定要排除混淆)是使用加密的JAR文件和自定义类加载器来进行解密(最好使用本地运行库)。
第三种方法(可能提供最强大的保护)是使用本地预编译器,例如GCCExcelsior JET,直接将您的Java代码编译为特定于平台的本机二进制文件。
无论如何,您必须记住爱沙尼亚的一句格言“锁只适用于动物”,意思是在运行时每一位代码都可用(已加载到内存中),而只要有足够的技能、决心和动力,人们就可以并且会反编译、解密和入侵您的代码…您的工作只是尽可能使该过程不舒适,并仍然保持其正常运行…

17
+1 表示“锁是给动物用的”的赞同。我猜这里适合用的术语是脚本小子。 - Antimony
你指的是GCJ,它已经死了。 - user207421
1
Componio jar文件加密已经过时了。请使用JarProtector代替。 - J.Profi
我并不认为加密jar文件有所帮助。这可以轻松地被Java自带的工具所破解。jhsdb可以直接从内存中提取类文件 - Richard Tingle

18
只要他们可以访问加密数据和解密软件,基本上你无法使其完全安全。此前解决该问题的方法包括使用某种外部黑匣子来处理加密/解密,如加密狗、远程认证服务器等等。但即使如此,由于用户可以完全访问自己的系统,这只会使事情变得困难,而不是不可能——除非您可以将产品直接绑定到“黑匣子”中存储的功能,比如在线游戏服务器。

15

免责声明:本人不是安全专家。

听起来像个糟糕的主意:您让某人使用您提供的“隐藏”密钥对内容进行加密。我认为这样做无法保证安全性。

也许非对称加密可以解决问题:

  • 部署带有公钥以进行解密的加密许可证
  • 允许客户创建新许可证并将其发送给您以进行加密
  • 向客户发送新许可证。

我不确定,但我相信客户实际上可以使用您提供的公钥加密许可证密钥。然后您可以使用自己的私钥解密它,再重新加密。

您可以为每个客户保留单独的公钥/私钥对,以确保您从正确的客户那里收到内容 - 现在,要负责这些密钥...


2
我以前使用过这种技术,它很有效。然而,从攻击的角度来看,第一种方法就是修改执行许可证检查并将其删除的代码。你可以做一些事情来使这种攻击向量更难,但如果你让攻击者控制硬件,那么你注定会失败,特别是对于一个有足够动机和技能的攻击者。实际上,目标只是让大多数诚实的人保持诚实。 - Jim Rush

14
无论你做什么,它都可以被“反编译”。该死,你甚至可以将其反汇编。或者查看内存转储以找到常量。你知道,计算机需要知道它们,所以你的代码也需要知道。
如何处理这个问题?
尽量不要将密钥作为硬编码常量放在你的代码中:将其保持为每个用户的设置。让用户负责保管该密钥。

9

@jatanp: 或者更好的办法是,他们可以反编译、删除授权代码并重新编译。对于Java而言,我认为没有一种适当的、防黑客攻击的解决方案。即使是一个邪恶的小型加密狗也无法防止这种情况在Java中发生。

我的业务经理担心这个问题,我也有同感。但是,我们将应用程序销售给遵守许可条件的大型企业,通常由于会计师和律师的存在,这是一个相对安全的环境。如果您的许可证书编写正确,反编译本身可能是非法的。

所以,我必须问一下,您是否真的需要像您为应用程序寻求的那样坚固的保护?您的客户群是什么样子的?(企业?还是青少年游戏玩家群体,这将更成为一个问题?)


事实上,有一种适当的、防黑客的解决方案,顺便说一下它看起来像一个加密狗:http://www.excelsior-usa.com/blog/excelsior-jet/java-bytecode-encryption-revisited/ - Dmitry Leskov
@DmitryLeskov “抗黑客”,也许吧。但对于任何想要查看代码的人来说,这只是一个小障碍。归根结底,字节码必须在未加密的主机平台上运行。就此打住。 - Stu Thompson
你没有阅读我链接的帖子。字节码被转换为加密狗的CPU代码并加密。当最终用户运行受保护的应用程序时,该加密代码被传输到“加密狗”。加密狗对其进行解密并在其CPU上运行。 - Dmitry Leskov
3
企业青少年游戏玩家。 - odiszapc

5
如果您正在寻找授权解决方案,可以查看TrueLicense API。它基于非对称密钥的使用。但这并不意味着您的应用程序无法被破解。任何应用程序都可以在付出足够努力的情况下被破解。真正重要的是,如Stu所回答的, 确定您需要多强的保护。

4

您可以毫不担心地使用字节码加密。

事实上,上述引用的论文“破解Java字节码加密”存在逻辑谬误。该论文的主要观点是,在运行之前必须对所有类进行解密并传递给ClassLoader.defineClass(...)方法。但这是不正确的。

这里错失的假设是“只要它们在真实的或标准的Java运行时环境中运行”。保护的Java应用程序不仅没有义务启动这些类,而且甚至不需要解密和传递它们给ClassLoader。换句话说,如果您使用标准JRE,则无法拦截defineClass(...)方法,因为标准Java没有用于此目的的API。如果您使用修改过的带有修补过的ClassLoader或其他任何“黑客技巧”的JRE,则无法这样做,因为受保护的Java应用程序根本无法工作,因此您将无法拦截任何东西。无论使用哪种“修补程序查找器”或黑客使用哪种技巧,这些技术细节都是完全不同的故事。


你究竟打算如何检测已打补丁的JVM?无论如何,这只会让事情稍微变得更加困难。 - Antimony
你能不能在你的应用程序启动器中找到一个调用defineClass()的地方?当你进行这个调用时,你必须传入一个解密字节数组的数组。这不是另一个原始源代码可能泄漏的地方吗? - Ascendant
5
我不完全同意这个回答。对我来说,这听起来像是:“问题:找到圆周率的最简单方法是什么?答案:取2倍的π并除以2。”我并不反对这个想法,但你能加入更多细节吗?例如,您是否希望主程序纯粹使用Java编写?这是否包括寻找修改的代码? - Patrick M

3
我认为不存在任何有效的离线防盗版方法。游戏产业曾多次尝试寻找,但他们的程序总是被破解。唯一的解决方案是程序必须在线运行连接到您的服务器,这样您可以验证许可证密钥,并且授权人员每次只能有一个活动连接。这就是魔兽世界暗黑破坏神的工作原理,即使有私人服务器来绕过安全性。
话虽如此,我不相信中大型公司使用非法复制软件,因为对他们而言,许可证的成本很小(也许,我不知道您的程序会收取多少费用),与试用版的成本相比。

0

问:如果我加密我的.class文件并使用自定义类加载器动态加载和解密它们,这样能防止反编译吗?

答:防止Java字节码反编译的问题几乎和语言本身一样古老。尽管市场上有许多混淆工具可用,但新手Java程序员仍然想出了新的聪明方法来保护他们的知识产权。在这个Java Q&A中,我揭示了一些在讨论论坛中经常被重复提到的想法的谬误。

Java .class文件可以轻松地重构为与原始文件非常相似的Java源代码,这种极易发生的情况与Java字节码设计目标和权衡有很大关系。Java字节码除了紧凑、平台无关、网络移动性和易于被字节码解释器和JIT(即时)/HotSpot动态编译器分析之外,还有其他方面的设计目标。可以说,编译后的.class文件表达了程序员的意图,因此比原始源代码更容易分析。

有几件事情可以做,即使不能完全防止反编译,至少可以使其更加困难。例如,作为后编译步骤,您可以对 .class 数据进行处理,使字节码在反编译时更难读取或更难反编译成有效的 Java 代码(或两者兼而有之)。像执行极端方法名称重载这样的技术对前者非常有效,而操纵控制流以创建无法通过 Java 语法表示的控制结构对后者非常有效。更成功的商业混淆器使用这些和其他技术的混合。

不幸的是,这两种方法都必须实际更改 JVM 将运行的代码,许多用户担心(理所当然)这种转换可能会向他们的应用程序添加新的错误。此外,方法和字段重命名可能会导致反射调用停止工作。更改实际的类和包名称可能会破坏其他几个 Java API(JNDI(Java 命名和目录接口),URL 提供程序等)。除了更改名称之外,如果类字节码偏移量与源行号之间的关联发生变化,则恢复原始异常堆栈跟踪可能会变得困难。

然后还有一种选择,就是混淆原始的 Java 源代码。但从根本上讲,这会导致类似的问题。加密而不是混淆?

也许上面的内容让你想到了:“如果我不是操作字节码,而是在编译后加密所有的类,并在JVM内部动态解密它们(可以使用自定义类加载器完成),那么JVM将执行我的原始字节码,但没有任何反编译或逆向工程可言,对吗?”
不幸的是,你的想法是错误的,既不是你首先想到这个想法,也不是它真正起作用的原因。而这个原因与你的加密方案的强度无关。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接