klu*_*utt 1 java passwords postgresql password-protection
我知道这char[]比String密码更可取,因为它String是不可变的。但是我一直无法弄清楚如何将它传递给准备好的语句。考虑这个代码:
Connection con = MyDBManager.connect();
PreparedStatement stm = con.prepareStatement(
"INSERT INTO my_table(username, password) VALUES(? ?)");
String username = "Jonny";
stm.setString(username);
char[] password = new char[] {'a','b','c','d'};
stm.SetString(password.toString());
Run Code Online (Sandbox Code Playgroud)
它有效,但我怀疑调用会password.toString()破坏使用 a 的全部目的char[],那么我该怎么办?
是的,我知道密码不应该以明文形式存储。密码是散列的,但我仍然想使用char[]
这似乎有效,而且我还将数据库中的字段从 更改text为bytea并且(我认为)理论上应该提供与char[]预期相同的好处,但我强烈感到我做得不对:
byte[] password = new byte[] {'a','b','c','d'};
stm.SetBytes(password);
Run Code Online (Sandbox Code Playgroud)
也许我的方法完全错误,但我的要求是密码哈希永远不应存在于不可变对象中。我对将其发送到数据库的其他方式持开放态度PreparedStatement
我从@rzwitserloot 的回答中学到了很多东西,但不幸的是它没有帮助。永远不要在不可变对象中使用密码哈希是不可协商的要求。
我知道 char[] 在密码方面比 String 更可取,因为 String 是不可变的。
如果您的意思是:所以现在您无法更改密码。那是不正确的。
也许您指的是无法将它们擦干净的事实,但以防万一,让我解释一下:
只有一个原因char[]比 String 更可取,而且非常可疑:使用 a char[],您可以“擦除”数组。也就是说,一旦您的进程(您的 java 代码)不再需要密码,您就可以显式运行Arrays.fill(thePassword, (char) 0);,现在 RAM 不再包含密码。你不能用字符串来做这件事——你可以把你的引用清零,但这只是“清除藏宝图”。不是挖宝,把宝物砸成碎片。愿意挖掘整个海滩的人仍然会找到它。
这听起来很棒,并且是为什么一大堆基于密码的 API 处理char[]而不是处理的唯一解释String。然而,这是一个非常可疑的原则,你绝对不应该依赖它:
就其价值而言,我不会判断任何在密码中存储字符串的代码是不安全的。事实上,我害怕以 char[] 形式存储它们的代码 - 我担心作者会误解这提供的保护,或者他们直接忘记将其归零:它使代码的读者做出错误的假设(即,密码被清除,这可能不是真的,这意味着如果有任何东西设法获得内存内容的转储,密码将无法恢复,这也可能不是真的)。
注意:为了安全起见,养成编写詹姆斯邦德电影剧本的习惯是个好主意。这是给你的电影剧本:
您在虚拟化 PC 上的云托管环境中运行您的服务器。这个云的运营商搞砸了,在服务器删除后(这实际上只是在运行数百台 PC 的主机上运行的虚拟 PC 的终止),他们不会清除进程的内存。有人决定尝试滥用此功能:他们要求云托管商给他们一台装有 linux 的 PC,然后他们安装一个简单的应用程序,该应用程序扫描虚拟 PC 自己的内存中是否有任何看起来像密码的内容并返回报告,然后关闭机器关闭并终止它,并要求另一个。
通过使用char[] AND清除密码,您可以更安全地避免这种情况......整个社区普遍同意是正确的做事方式),在这种情况下,某些密码恰好处于 char[] 阶段存在风险。更不用说内存中的所有其他地方了。
了解债券级脚本如何帮助澄清问题?它表明清除 char 数组有一定的意义,但这并不是万无一失的。
散列它也没有好处。至少,取决于您对“哈希”一词的含义。
正确的方法包括两件事:
“盐” - 问题是,很多人都有iloveyou密码。你使用什么散列算法并不重要,散列算法本质上将相同的输入散列到相同的输出,所以如果我得到你的数据库的完整转储,我需要做的就是运行SELECT passhash, COUNT(*) AS ct FROM accounts GROUP BY passhash ORDER BY ct DESC LIMIT 1,瞧——那个散列?那是iloveyou,然后我可以做SELECT username FROM accounts WHERE passhash = ?,列表中的每个人?我可以通过 pass 以他们的身份登录iloveyou。就那么简单。salt 解决了这个问题:这个想法是,你为每个帐户生成一个随机数(在帐户创建时),然后将这个随机数存储在数据库中。您存储的哈希值是对值进行哈希处理的结果:CONCATENATE(salt, password)。现在每个白痴用户都使用iloveyou因为密码以不同的哈希结束。要检查密码,您需要输入密码,检索盐,重新创建 CONCAT(salt, pass),对其进行哈希处理,并确认它与数据库条目匹配。
你想要一个缓慢而奇怪的散列算法。例如,比特币使用 SHA-256,因此有大量的专用机器可以为 SHA-256 一毫秒生成数十亿个哈希值,并且那里有专门的硬件(您没有的硬件)它比您拥有的任何计算机都要快得多。那很糟糕,你不希望黑客有优势。因此,您需要一个非常慢的散列算法,并且不能(轻松)针对自定义硬件进行优化。BCrypt、SCrypt 和 PBKDF 是常用的变体。它们都很好(BCrypt 更旧,从这个意义上说“更糟”,但也更简单和经过验证。选择你想要的任何一个)。
请注意,大多数库已经处理了整个 salt 业务,无需显式生成和存储这些(这些库的 API 很简单:“给我一个东西来为这个密码放入数据库”和“用户输入了这个密码,这就是你之前告诉我要放入数据库的东西。它是匹配的吗?”。“放入数据库的东西”包含盐和哈希结果组合成一个字符串。
它通过一根电线或至少一个本地套接字进入 postgres WAL 日志,然后到处都是。这件事也确实就在数据库中。
您可以以某种方式从系统硬件中消除此散列值的想法是完全不可能的。
所以只需制作一个java.lang.String。没关系。