【问题标题】:array of characters hide password jdbc字符数组隐藏密码jdbc
【发布时间】:2015-05-28 16:15:38
【问题描述】:

我正在尝试对我的 JDBC 连接实施最佳安全实践。 我读到实现密码的最佳方法是使用字符数组,而不是字符串,因为字符串在 Java 中是不可变的。

唯一的问题是,我在 Java API 中找不到任何支持隐藏char[] 类型密码的方法。

有什么建议吗?

        import java.awt.Dimension;
        import java.awt.GridLayout;
        import java.awt.Toolkit;
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.ResultSet;
        import java.sql.Statement;

        import javax.swing.Box;
        import javax.swing.JFrame;
        import javax.swing.JPanel;
        import javax.swing.JPasswordField;
        import javax.swing.JTable;
        import javax.swing.JTextField;
        import javax.swing.JOptionPane;
        import javax.swing.JLabel;

public class DbConnect
{
    public static void main(String args[])
    {

        try
        {
                Class.forName("com.mysql.jdbc.Driver");
                Connection conn = null;
                Statement stmt = null;
                String username = "";
                String sPassword = "";
                char[] cPassword;


                JTextField usernameField = new JTextField(50);
                JPasswordField passwordField = new JPasswordField(20);

                JPanel loginPanel = new JPanel();
                loginPanel.add(new JLabel("Username:"));
                loginPanel.add(usernameField);
                loginPanel.add(Box.createHorizontalStrut(15)); 
                loginPanel.add(new JLabel("Password:"));
                loginPanel.add(passwordField);

                  int result = JOptionPane.showConfirmDialog(null, loginPanel, 
                           "Please Enter Username and Password", JOptionPane.OK_CANCEL_OPTION);

                  if (result == JOptionPane.OK_OPTION) {
                     username = usernameField.getText();
                     cPassword = passwordField.getPassword();

                     //password = passwordField.getText();

                     for(int c = 0; c < cPassword.length; c++)
                         sPassword = sPassword + cPassword[c];

                /*conn = DriverManager.getConnection("jdbc:mysql://x.x.x.x");*/
                conn = DriverManager.getConnection("jdbc:mysql://x.x.x.x", username, sPassword);
                System.out.print("Database is connected\n");

                 //STEP 4: Execute a query
                  System.out.println("Creating statement...");
                  stmt = conn.createStatement();
                  String sql;
                  sql = "SELECT Fld1, Fld2, Fld3, Fld4 FROM db.tbl";
                  ResultSet rs = stmt.executeQuery(sql);      

【问题讨论】:

  • 为什么不转换它以隐藏?
  • 不变性和密码安全有什么关系?为什么要更改密码对象?
  • @Alexander - 这个想法是密码保留在内存中,并且在垃圾收集之前不会从中删除,因此黑客可以在很长一段时间内入侵您的内存空间。使用可变对象,您实际上可以在完成后立即将内存清零。
  • 由于字符串不可变,它将在内存中浮动,直到 gc 来找它。如果将 char[] 设置为 null,则可以更快地销毁对象。
  • @TripWire - 将其设置为 null 与 String 相比将无济于事。它将每个元素设置为 \u0000 或其他可以解决问题的方法。

标签: java arrays string jdbc connection


【解决方案1】:

首先,让我们弄清楚为什么char[]String 更适合用于密码。

如果您假设黑客可以转储您的 JVM 的内存映像,或以任何其他方式搜索您的内存空间,那么只要有足够的时间,他们就可以在内存中找到密码。出于这个原因,最好将密码在内存中保留很短的时间 - 从用户那里获取密码,将其传递到需要它的任何地方,然后将其从内存中删除。

天真地,人们会做这样的事情:

String password = getPasswordFromUser();
usePassword(password);
password = null;

现在密码字符串(假设它是"myPass")已经准备好进行垃圾回收了。但它仍在记忆中。人们永远不知道它何时会被收集。也许永远不会,因为您的应用程序有足够的内存。而且即使收集起来,也不能保证记忆会被抹去。黑客需要很长时间才能彻底搜索您的内存空间以查找可能的密码。

现在,您可能听说字符数组是一个很好的解决方案。因此,您将上面的幼稚代码替换为:

char[] password = getPasswordFromUser(); // Now returning a character array
usePassword(password);
password = null;

好吧,不幸的是,这里也是如此。字符数组['m','y','P','a','s','s']还在内存中,等待垃圾回收,可能永远不会被擦除。

所以字符数组不会自动保证更好的安全性。您实际上必须执行以下操作:

char[] password = getPasswordFromUser(); // Now returning a character array
usePassword(password);
Arrays.fill(password, '\u0000');
password = null;

现在原始密码中的每个字符都已被删除并替换为零。该对象仍在等待垃圾回收,但其中不再有有意义的密码。

您不能直接对 String 执行此操作,因为实现它的 char[] 数组是私有的,您不能在其中设置字符 - 它是不可变的。所以它必须是char[]

现在我们知道了,规则很明确:它必须是一个 char[] 对象。也就是说,您无法将其转换为String 沿途的任何地方,或者克隆或复制它。如果你这样做了 - 你就失去了全部好处。

所以你的这段代码:

                for(int c = 0; c < cPassword.length; c++)
                     sPassword = sPassword + cPassword[c];

实际上违反了这条规则。它在sPassword 中创建了一个String 对象,其中包含密码并且我们失去了安全性。


这里真正的问题是连接到 JDBC 不允许您传递 char[] 的密码。 DriverManager.getConnection() 方法需要 String,而不是 char[],无论您将密码放在连接 URL 中,还是将其作为参数传递。

因此,事实上,如果您想通过 JDBC 保持这种级别的安全性,您必须找到一种替代方法来验证您的数据库,它不使用 DriverManager.getConnection(String, String, String)DriverManager.getConnection(String)

我没有测试它,但是 MySQL 提供了一种使用 SSL 和客户端证书进行身份验证的方法。可能还有其他方法可以使用其他用于 Connector/J 的身份验证插件。 但是向用户询问用户名和密码并使用DriverManager.getConnection() 的简单方法不允许您通过char[] 维护密码安全。

【讨论】:

  • 好帖子。但是,我不会太担心这种威胁场景——如果攻击者可以访问您的应用程序服务器的 JVM 的内存,她可能也可以直接从 *ds.xml 文件(或类似文件)中读取密码,它所在的位置纯文本。
  • 刚刚注意到 OP 的示例是针对 Swing(客户端)应用程序,所以这实际上可能很有用,因为 pw 没有存储在任何地方。
  • @MickMnemonic 只是好奇:在你注意到它是 Swing 之前你在说什么?
  • 我的意思是应用服务器(Tomcat/JBoss 等)的 JVM。数据库的凭据通常以纯文本形式存储在应用服务器的安装目录中。但是,我认为您可以通过在连接到数据库后调用 GC 来缓解问题。根据this thread,只有字符串文字和interned 字符串应该驻留在字符串池中。
  • @MickMnemonic 垃圾收集不能确保内存擦除。实际上,调用 gc 甚至不能保证会调用垃圾回收。对于服务器,如果您真的关心这种级别的安全性,您可能会使用证书而不是用户/密码进行身份验证。无论如何,安全级别始终是风险管理的一项功能,并不是每个人都应该追求到这个级别。
猜你喜欢
  • 2018-01-23
  • 1970-01-01
  • 1970-01-01
  • 2018-05-29
  • 2014-12-31
  • 2013-12-28
  • 1970-01-01
  • 2010-11-24
  • 1970-01-01
相关资源
最近更新 更多