如何保护MySQL用户名和密码不被反编译?

89

Java .class 文件可以很容易地被反编译。如果我必须在代码中使用登录数据,那么该如何保护我的数据库?


6
请注意,你使用Java并不是重点。在任何语言中,硬编码密码都会带来相同的问题("strings thebinary"可以像在C程序中一样显示字符串常量)。 - Joachim Sauer
@saua:没错,但也许会有人发布一些Java中如何解耦用户名和密码的示例代码。如果我有时间的话,我甚至可能会自己做这件事。 - William Brendel
我添加了一些演示此功能的Java代码。这些代码非常基础,但足以让某人入门。 - William Brendel
1
我注意到很多答案认为你试图在未经授权的用户查看时隐藏用户名/密码,而运行应用程序的用户是可以的。我相信你想要从所有人那里隐藏密码。请在问题中澄清这一点。 - pek
1
例如,假设凭据用于连接到除应用程序之外的任何人都无法登录的服务器。 - pek
显示剩余2条评论
6个回答

126

不要在代码中硬编码密码。这个问题最近在“最危险的25个编程错误”中被提出:

将秘密账户和密码硬编码进你的软件非常方便,但对于技术娴熟的逆向工程师来说却不是问题。如果密码在所有软件中都相同,当密码被知道后,每一个客户端都会变得不安全。并且因为它是硬编码的,修复起来非常麻烦。

你应该将配置信息(包括密码)存储在一个单独的文件中,在应用程序启动时读取该文件。这是防止密码由于反编译而泄漏的唯一真正方法(根本就不要将其编译进二进制文件中)。

了解更多关于这个常见错误的信息,你可以阅读CWE-259文章。这篇文章包含了更详细的定义,示例以及有关该问题的大量其他信息。

在Java中,最简单的方法之一是使用Preferences类。它旨在存储各种程序设置,其中一些可能包括用户名和密码。

import java.util.prefs.Preferences;

public class DemoApplication {
  Preferences preferences = 
      Preferences.userNodeForPackage(DemoApplication.class);

  public void setCredentials(String username, String password) {
    preferences.put("db_username", username);
    preferences.put("db_password", password);
  }

  public String getUsername() {
    return preferences.get("db_username", null);
  }

  public String getPassword() {
    return preferences.get("db_password", null);
  }

  // your code here
}
在上面的代码中,您可以在显示要求输入用户名和密码的对话框后调用setCredentials方法。当需要连接到数据库时,只需使用getUsernamegetPassword方法检索存储的值即可。登录凭据不会硬编码到二进制文件中,因此反编译不会造成安全风险。
重要说明:首选项文件只是纯文本XML文件。确保采取适当措施防止未经授权的用户查看原始文件(UNIX权限、Windows权限等等)。至少在Linux中,这不是问题,因为调用Preferences.userNodeForPackage将在当前用户的主目录中创建XML文件,其他用户无法读取该目录。在Windows中,情况可能不同。
更重要的说明:在这个答案和其他答案的评论中已经有很多关于这种情况的正确架构讨论。原始问题并没有真正提到应用程序使用的上下文,因此我将谈论我能想到的两种情况。第一种情况是使用程序的人已经知道(并且被授权知道)数据库凭据。第二种情况是您作为开发人员尝试从使用程序的人那里隐藏数据库凭据。
第一种情况:用户被授权知道数据库登录凭据
在这种情况下,我提到的解决方案将起作用。Java Preference类将以纯文本形式存储用户名和密码,但首选项文件只能被授权用户读取。用户只需打开偏好设置XML文件并阅读登录凭据即可,但这不会构成安全风险,因为用户从一开始就知道凭据。
第二种情况:尝试隐藏用户的登录凭据
这是更复杂的情况:用户不应该知道登录凭据,但仍需要访问数据库。在这种情况下,运行应用程序的用户直接访问数据库,这意味着程序需要事先知道登录凭据。我提到的解决方案不适用于此情况。您可以将数据库登录凭据存储在首选项文件中,但用户将能够读取该文件,因为他们是所有者。实际上,没有好的方式以安全的方式使用此案例。
正确方案:使用多层架构
正确的方法是在数据库服务器和客户端应用程序之间建立一个中间层,对单个用户进行身份验证并允许执行有限的操作。每个用户都有自己的登录凭据,但不是针对数据库服务器的。凭据将允许访问中间层(业务逻辑层),并且对于每个用户是不同的。
每个用户都有自己的用户名和密码,可以在首选项文件中本地存储而不会有任何安全风险。这称为三层体系结构(三个层次是您的数据库服务器、业务逻辑服务器和客户端应用程序)。它更加复杂,但确实是做这种事情最安全的方式。
操作的基本顺序是:
1.客户端使用用户的个人用户名/密码进行身份验证。该用户名和密码已知于用户,并且与数据库登录凭据无关。 2.如果身份验证成功,则客户端将向业务逻辑层发出请求,要求从数据库中获取一些信息。例如,产品清单库存。请注意,客户端的请求不是SQL查询;它是一个远程过

4
这样做确切地如何防止他人获取用户名和密码?难道不可以直接从文件中读取吗? - Joe Phillips
@Michael - 加密并不能防止用户运行应用程序时获取密码。它只是将问题转移到保护密钥上,而且无论如何,用户都需要在某个地方使用 API 来使用密码。 - frankodwyer
7
如果你要向用户部署一个数据库编辑应用程序,但不希望他们知道数据库的用户名和密码,那么你的解决方案架构是错误的。客户端软件应该通过与服务器(例如通过 web 服务)通信来进行数据库操作。 - JeeBee
我不太明白你的意思:“客户端使用用户的个人用户名/密码与业务逻辑层进行身份验证。用户名和密码为用户所知,与数据库登录凭据无关。”业务逻辑层是一个Web服务(用Java、PHP等编写)吗?如果是这样,用户凭据存储在哪里?也许在Web服务平台的配置文件中?但是,如果我们想创建新帐户以注册新用户呢?那怎么办?虽然可以对这些凭据进行哈希处理,并通过网络发送给业务逻辑层, - John Astralidis
我认为你错过了引用文章的主要观点。你不能只是把密码存储在文件或数据库中,你必须“将密码存储在一个强加密的配置文件或数据库中”。否则,如果你可以在密码文件上设置正确的权限,那么你也可以在不想让人反编译的.class文件上设置相同的权限。 - Tristan
显示剩余8条评论

15

将密码放入应用程序将读取的文件中。绝不要将密码嵌入源文件中。

Ruby有一个鲜为人知的模块叫做DBI::DBRC,用于这种用途。我毫不怀疑Java有相应的模块。无论如何,编写一个类似的模块并不难。


8
虽然这使得以后更改密码稍微容易一些,但并没有解决基本的安全问题。 - Brian Knoblauch
是的,它确实有。还可以参考William Brendel的答案。 - Keltia
1
Keltia和我指出的方法是处理这个问题的公认方式。将登录凭据与编译代码分离是最基本的软件安全实践之一。将登录凭据放在单独的文件中是实现此目的的有效方法。 - William Brendel
5
好的,那么该文件只能被运行程序的用户读取。很好,这解决不了问题。 :-) 我,也就是我们试图对密码保密的用户,可以像应用程序一样轻松地读取它... - Brian Knoblauch
这种方法会使访问更加困难,但绝不会保护任何东西。即使数据在文件上加密,代码仍然可以被反编译(这是我目前的主要问题)。因此,我必须将敏感数据放在文件中,加密并混淆处理加密/解密的代码... - marcolopes
显示剩余2条评论

3
无论你做什么,敏感信息都将存储在某个文件中。你的目标是尽可能地使获取难度增加。你能够实现多少取决于你的项目、需求和公司钱包的厚度。
最好的方法是不在任何地方存储密码。这可以通过使用哈希函数生成和存储密码哈希值来实现:
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366

哈希算法是单向函数。它们将任意数量的数据转换为固定长度的“指纹”,无法反转。它们还具有这样的属性:即使输入发生微小变化,产生的哈希也完全不同(见上面的示例)。这对于保护密码非常有用,因为我们希望以一种保护密码的形式存储密码,即使密码文件本身被破坏,但同时,我们需要能够验证用户的密码是否正确。
无关的注释:在互联网早期,当你点击“忘记密码”链接时,网站会通过电子邮件发送给你明文密码。他们可能在某个数据库中存储了这些密码。当黑客访问他们的数据库时,他们将获得所有密码的访问权限。由于许多用户在多个网站中使用相同的密码,这是一个巨大的安全问题。幸运的是,现在这不是常见的做法。
现在的问题是:什么是存储密码的最佳方法?我认为this (认证和用户管理服务stormpath的)解决方案是非常理想的。
  1. 用户输入凭据,然后根据密码哈希值进行验证
  2. 生成和存储密码哈希值,而不是密码本身
  3. 哈希值被多次执行
  4. 使用随机生成的盐来生成哈希值
  5. 使用私钥加密哈希值
  6. 私钥存储在与哈希值物理上不同的位置
  7. 私钥按时间更新
  8. 加密哈希值被分成块
  9. 这些块被存储在物理上不同的位置

显然,您不是Google或银行,因此这对您来说是一种过度解决方案。但接下来的问题是:您的项目需要多少安全性,您有多少时间和金钱?

对于许多应用程序,虽然不建议,但将硬编码密码存储在代码中可能是足够好的解决方案。但是,通过从上述列表中轻松添加几个额外的安全步骤,您可以使应用程序更加安全。

例如,假设步骤1对于您的项目不是可接受的解决方案。您不希望用户每次都输入密码,或者您甚至不想/不需要让用户知道密码。但仍然有敏感信息存在,您希望保护这些信息。您有一个简单的应用程序,没有服务器来存储您的文件,或者这对于您的项目来说太麻烦了。您的应用程序在无法安全存储文件的环境中运行。这是最糟糕的情况之一,但是通过一些额外的安全措施,您可以拥有更安全的解决方案。例如,您可以将敏感信息存储在文件中,并对文件进行加密。您可以在代码中硬编码加密私钥。您可以混淆代码,使其更难以破解。有许多库可用于此目的,请参见this link。(我要再次警告您,这并不是100%安全的。聪明的黑客使用正确的知识和工具可以破解它。但根据您的要求和需求,这可能是足够好的解决方案)。

3
如果您正在编写一个Web应用程序,那么请使用JNDI将其配置为应用程序外部。您可以在此处查看概述:这里
JNDI提供了一种统一的方式,使应用程序能够在网络上查找和访问远程服务。远程服务可以是任何企业服务,包括消息服务或特定于应用程序的服务,但是JDBC应用程序主要关注数据库服务。创建并向JNDI命名服务注册DataSource对象后,应用程序可以使用JNDI API访问该DataSource对象,然后可以使用它来连接表示数据源的数据源。

提供的链接无效。 - James Oravec

1

1
在源代码中仍需要解密加密的密码以创建连接,这被认为密码仍然存在。 - Bear0x3f

0

MD5是一种哈希算法,而不是加密算法。简而言之,您无法恢复哈希的内容,只能进行比较。 最好在存储用户认证信息时使用它,而不是数据库用户名和密码。 应该将数据库用户名和密码加密并保存在配置文件中,以尽可能减少风险。


我听说过有些人会生成所有可能的字符串组合并存储它们对应的MD5哈希值。因此,当他们找到某个人的MD5哈希值时,只需找到他们存储的哈希值,就可以得到相应的字符串。 - Nav

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