我正在用Java编写安全的文件共享应用程序。一般架构如下所示:
这是捕获。用户的私钥必须加密并存储在我们数据库的服务器上,以便可以从多个位置访问这些文件。在上传到服务器之前,私钥将在客户端上使用用户选择的密码进行加密。
我想使用AES 256位加密来做到这一点。而且我想在不依赖BouncyCastle库或任何第三方库的情况下完成整个过程。它需要使用标准的Java 5库,这就是我选择使用AES 256加密和RSA而不是像PGP这样的原因。
任何人都可以通过这种方法找到任何本质上不安全的东西,或者想一个更有效的方法来做到这一点吗?
编辑:
好的,我正在更新问题,因为我得到的所有答案都表明我没有将私钥传输到服务器。我需要服务器上的私钥的原因是因为用户需要能够从多个客户端和多个位置(即:他们的iphone,他们的ipad,他们的工作笔记本电脑,他们的家用电脑)访问他们的数据。他们不希望必须在设备之间管理和复制他们的密钥,这比将密钥存储在我们的服务器上更加不安全,因为他们最终会在那时通过电子邮件发送给他们自己。
答案 0 :(得分:5)
这个问题的主要问题是使用UUID。虽然UUID(有点)保证是唯一的,但它们所包含的相当多的东西是可以预测的;在一台机器上生成的所有UUID中,大量数量保持不变。因此,如果一个人可以访问(例如)他们自己的密钥,他们可能很容易猜到很多其他人的密钥。
另一个有问题的部分是将用户的私钥存储在服务器上。这使得整个方案的其余部分相对脆弱,因为访问这些密钥显然可以访问所有其余数据。它(显然)意味着您通常会解密服务器上的数据,因此当用户通过网络访问该数据时,它需要重新加密以进行传输,并在用户的计算机上解密,或者否则你将以明文形式传输数据(从而使大部分加密无效)。
编辑:至于我认为我会这样做:
我在服务器上有一个 public 键列表。当客户端想要与其他客户端共享文件时,它从服务器获取这些客户端的公钥。然后它生成一个安全的随机密钥,并使用该密钥加密数据。然后,它使用应该能够访问数据的所有其他客户端的公钥来加密随机密钥。将它们放在一起,然后将它们传输到服务器。然后,其他客户端可以下载流,使用其私钥解密密钥,并使用它来解密数据本身。
这意味着每个客户的私钥仍然是私密的 - 它永远不必以任何形式离开他们的机器。他们与世界其他地方分享的只是他们的公钥(根据定义,它不应该导致安全问题)。
有了这个,两个明显的攻击线是针对随机数生成器,而不是针对RSA本身。对于随机数生成器,我使用Java的SecureRandom - 这正是它的目的,如果内存服务它已经过仔细检查,并且对它的重大突破似乎不太可能。
我不会试图评论RSA本身的安全性。目前,我认为您首要关注的是协议,而不是加密算法。可以说,如果RSA被严重破坏,你显然需要更改你的代码,但你有一个很多的公司。
有了这个,客户端可以安全地存储他们的私钥。我喜欢那份工作的智能卡,但有很多选择。从服务器和协议的角度来看,它根本不再是一个真正的因素。
编辑2:至于处理多个设备,我想我只是将每个设备视为一个单独的用户,拥有自己的公钥/私钥对。然后我(可能)将这些组合在一起由实际用户组成,所以我可以轻松地选择“Joe Blow”来让他访问他的所有设备 - 但是通过分层显示,我也可以很容易地限制对子集的访问其中,如果我想在他的办公室机器上与乔分享它,但它足够灵敏,我不希望它在看到它的时候看到他的肩膀,我也很容易做到这一点。
这使用户的生活变得简单,但保留了相同的基本安全模型(即私钥保持私密)。
答案 1 :(得分:2)
您概述的方案等同于CMS(标准底层S / MIME)和PGP;从根本上说,它是安全的。在CMS中,此模式称为“密钥传输”。您还可以使用多方“密钥协议”,使用DH或ECDH等算法。
唯一的问题是您使用选择不当的密钥进行AES。
我想不出有任何理由使用包含非随机位的随机UUID。只需使用Java Cryptography Architecture的常规密钥生成机制。密钥,明文和密文都应该表示为字节序列,除非您需要容纳一些只能容纳文本的外部存储或传输。
Iterable<Certificate> recipients = null;
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(256);
SecretKey contentEncryptionKey = gen.generateKey();
初始化AES密码,让提供商选择IV。
Cipher contentCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
contentCipher.init(Cipher.ENCRYPT_MODE, contentEncryptionKey);
AlgorithmParameters params = contentCipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
对于每个收件人,初始化RSA密码并加密AES密钥。
Cipher keyEncryptionCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
for (Certificate recipient : recipients) {
keyEncryptionCipher.init(Cipher.WRAP_MODE, recipient);
byte[] encryptedKey = keyEncryptionCipher.wrap(contentEncryptionKey);
/* Store the encryptedKey with an identifier for the recipient... */
}
/* Store the IV... */
/* Encrypt the file... */
让用户选择并记住提供256位有效强度的密码是不合理的。要获得这种优势,您必须随机选择密码,将其编码为文本,并让用户将其写在卡上。如果你真的需要那么多力量,你可以查看一个基于智能卡的解决方案来存储用户的RSA密钥。
我强烈建议您使用CMS库来存储文件。它将增加您使用的协议是安全的,您正在使用的代码有更多的审查,以及其他工具,库和系统可以与加密的消息互操作的机会。 BouncyCastle的API有点模糊,但它可能值得学习。
(我不记得Java 5是否支持“RSA / ECB / OAEPWithSHA-512AndMGF1Padding”;如果支持,你应该使用它而不是PKCS1Padding。)
答案 2 :(得分:1)
好的,这个问题是要求进行协议讨论,所以它并不完全符合stackoverflow的标准。那就是说,让我们看看我们是否可以发表一些评论:):
答案 3 :(得分:-1)
这完全取决于您希望加密的“安全”程度。显然,RSA是PKI的良好文档/接受标准。话虽这么说,但只要你提供明文和加密文本,黑客就会更容易解密知道部分明文的密文。在这里,你正是这样做的。虽然您只传输加密的UUID,但通过使用多个密钥加密相同的明文,攻击者可以深入了解有效负载。此外,如果被黑客实际上是其中一个收件人,他就能够解码UUID,从而自动知道其他用户的公钥正在加密的明文。
这对您来说不太可能是一个关键问题,但我想我会指出一个安全风险。
但是,我不完全确定为什么需要存储用户的私钥。此外,通过使用简单的密码加密私钥,您基本上将整个系统的整体安全性降低到用户密码的强度。最后,如果用户丢失了密码,他就会敬酒;无法恢复任何数据。
我过去做了类似的事情,但将结果存储在数据库中。但是,我当时使用了BouncyCastle库,所以不知道如何在没有它们的情况下实现这一目标。