88

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

4

6 回答 6

125

切勿将密码硬编码到您的代码中。最近在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 中,情况可能会有所不同。

更重要的说明:在这个答案和其他人的评论中有很多关于这种情况下正确架构的讨论。最初的问题并没有真正提到应用程序正在使用的上下文,所以我将谈谈我能想到的两种情况。第一种情况是使用该程序的人已经知道(并且被授权知道)数据库凭据。第二种情况是开发人员试图对使用该程序的人保密数据库凭据。

第一种情况:用户被授权知道数据库登录凭据

在这种情况下,我上面提到的解决方案将起作用。JavaPreference类将以纯文本形式存储用户名和密码,但首选项文件只能由授权用户读取。用户可以简单地打开首选项 XML 文件并读取登录凭据,但这不是安全风险,因为用户知道开始时的凭据。

第二种情况:试图向用户隐藏登录凭据

这是更复杂的情况:用户不应该知道登录凭据,但仍需要访问数据库。在这种情况下,运行应用程序的用户可以直接访问数据库,这意味着程序需要提前知道登录凭据。我上面提到的解决方案不适用于这种情况。您可以将数据库登录凭据存储在首选项文件中,但他的用户将能够读取该文件,因为他们将是所有者。事实上,确实没有什么好的方法可以安全地使用这种情况。

正确案例:使用多层架构

正确的做法是在数据库服务器和客户端应用程序之间有一个中间层,它对单个用户进行身份验证并允许执行一组有限的操作。每个用户都有自己的登录凭据,但数据库服务器没有。凭据将允许访问中间层(业务逻辑层),并且对于每个用户来说都是不同的。

每个用户都有自己的用户名和密码,这些用户名和密码可以本地存储在首选项文件中,没有任何安全风险。这称为三层架构(这些层是您的数据库服务器、业务逻辑服务器和客户端应用程序)。它更复杂,但它确实是做这类事情的最安全的方法。

操作的基本顺序是:

  1. 客户端使用用户的个人用户名/密码向业务逻辑层进行身份验证。用户名和密码是用户已知的,并且与数据库登录凭据没有任何关系。
  2. 如果身份验证成功,客户端会向业务逻辑层发出请求,从数据库中获取一些信息。例如,产品库存。注意客户端的请求不是SQL查询;它是一个远程过程调用,例如getInventoryList.
  3. 业务逻辑层连接到数据库并检索请求的信息。业务逻辑层负责根据用户的请求形成安全的 SQL 查询。应该对 SQL 查询的任何参数进行清理,以防止 SQL 注入攻击。
  4. 业务逻辑层将清单列表发送回客户端应用程序。
  5. 客户端向用户显示库存列表。

请注意,在整个过程中,客户端应用程序从不直接连接到数据库。业务逻辑层接收来自经过身份验证的用户的请求,处理客户端对库存列表的请求,然后才执行 SQL 查询。

于 2009-01-14T13:08:42.410 回答
15

将密码放入应用程序将读取的文件中。切勿在源文件中嵌入密码。时期。

Ruby 有一个鲜为人知的模块DBI::DBRC用于这种用途。我毫不怀疑Java有一个等价物。反正写一个也不难。

于 2009-01-14T13:07:38.013 回答
3

您正在编写 Web 应用程序吗?如果是这样,请使用 JNDI 在应用程序外部对其进行配置。此处提供概述:

JNDI 为应用程序提供了一种通过网络查找和访问远程服务的统一方式。远程服务可以是任何企业服务,包括消息传递服务或特定于应用程序的服务,但当然,JDBC 应用程序主要对数据库服务感兴趣。一旦创建了 DataSource 对象并将其注册到 JNDI 命名服务,应用程序就可以使用 JNDI API 访问该 DataSource 对象,然后可以使用该对象连接到它所代表的数据源。

于 2009-01-14T13:28:15.447 回答
2

无论您做什么,敏感信息都将存储在某个文件中的某个地方。你的目标是让它尽可能难以获得。您可以实现多少取决于您的项目、需求和公司钱包的厚度。

最好的方法是不要在任何地方存储任何密码。这是通过使用散列函数来生成和存储密码散列来实现的:

hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366

哈希算法是单向函数。它们将任意数量的数据转换为无法反转的固定长度“指纹”。它们还具有这样的特性,即如果输入发生一点点变化,生成的哈希值就会完全不同(参见上面的示例)。这对于保护密码非常有用,因为我们希望以一种即使密码文件本身被泄露也能保护密码的形式存储密码,但同时,我们需要能够验证用户的密码是否正确。

无关说明:在互联网的旧时代,当您单击“忘记密码”链接时,网站会通过电子邮件将您的纯文本密码发送给您。他们可能将这些存储在某个数据库中。当黑客获得对其数据库的访问权限时,他们将获得对所有密码的访问权限。由于许多用户会在多个网站上使用相同的密码,这是一个巨大的安全问题。幸运的是,现在这不是常见的做法。

现在问题来了:存储密码的最佳方式是什么?我认为这个(身份验证和用户管理服务stormpath的)解决方案非常理想:

  1. 您的用户输入凭据,并根据密码哈希进行验证
  2. 生成和存储密码哈希,而不是密码
  3. 哈希执行多次
  4. 使用随机生成的盐生成哈希
  5. 哈希使用私钥加密
  6. 私钥存储在与哈希不同的物理位置
  7. 私钥按时间更新
  8. 加密的哈希被分成块
  9. 这些块存储在物理上独立的位置

显然你不是谷歌或银行,所以这对你来说是一个矫枉过正的解决方案。但随之而来的问题是:您的项目需要多少安全性,您有多少时间和金钱?

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

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

于 2017-10-19T11:48:02.710 回答
1

这个问题显示了如何在加密文件中存储密码和其他数据:Java 256-bit AES Password-Based Encryption

于 2014-10-08T07:31:28.540 回答
0

MD5是散列算法,不是加密算法,总之你不能取回你的散列,你只能比较。理想情况下,它应该在存储用户身份验证信息而不是数据库用户名和密码时使用。db username 和 pwd 应该被加密并保存在一个配置文件中,至少这样做。

于 2009-01-14T13:29:32.120 回答