在 Java 中使用对称加密保护磁盘上的私钥
我有一个分布式应用程序,它轮询来自中央服务器的命令,这些命令的内容由私钥“签名”,公钥与每个远程站点的应用程序一起部署。每个命令都使用私钥进行签名,并且在执行命令之前会验证签名。
这是我在 java 中生成公钥/私钥对的方法。 (我意识到,我们应该做的比 1024 多)
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(1024, random);
KeyPair pair = keyGen.generateKeyPair();
新的要求是,除了发送简单的相对安全的命令之外,我们现在还想发送指示我们的软件下载并执行安装程序的命令来更新自己。如果做得不正确,这可能会造成一个大洞。 “执行更新安装程序”命令将一如既往地进行签名,并且还将包含将下载并运行的可执行文件的 md5。因此,命令上的签名必须正确(并且将包含 md5),然后在执行之前计算出的下载文件的 md5 也必须正确。这应该可以解决我能想到的大多数攻击向量。 还有其他我应该关心的吗?
因此,现在我将注意力集中在这些命令发出的服务器上的私钥的安全上。如果获得了该私钥,则游戏结束。我应该如何保护磁盘上的密钥?
我的想法是使用带有密码的对称加密来保护该私钥。
我当前的解决方案如下:
char[] passphrase; // Actual passphrase
PrivateKey privateKeyObj; // Actual Private Key
byte[] privateKeyBytes = privateKeyObj.getEncoded();
byte[] salt = new byte[8];
new SecureRandom().nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase, salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal(privateKeyBytes);
FileOutputStream outputFileStream = new FileOutputStream(outputFile);
outputFileStream.write(salt);
outputFileStream.write(iv);
outputFileStream.write(ciphertext);
outputFileStream.close();
(为了清楚起见,删除了例外)
这基本上随机生成一个盐,使用密码来得出密钥,然后将生成的盐、IV 和密文存储在一个文件中,以供使用解密。
然而,我觉得这里缺少了一些东西。就像我还应该以某种方式在密钥上包含某种类型的 MAC,以便我知道我得到了有效的描述?也许这很简单,只需在私钥之前放置 5 或 6 个字节的已知文本?现在,错误的密码只会导致错误的填充异常,但我读到,情况可能并不总是如此,一些错误的密码会解码并导致垃圾。我觉得我需要警惕这一点。
有人可以让我知道我是否走在正确的轨道上并提供一些反馈吗?重要的是我要尽可能安全地做到这一点。
I have a distributed application which polls for commands from a central server and the content of those commands are "signed" by a private key with the public key deployed along with the application at each remote site. Each command is signed with the private key and that signature is verified before the command is executed.
Here is how I generate my public/private key pairs in java. (I realize, we should be doing more then 1024)
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(1024, random);
KeyPair pair = keyGen.generateKeyPair();
The new requirement is that in addition to sending simple relatively safe commands, we now want to send commands which will instruct our software to download and execute an installer to update itself. This has the potential to open up a big hole if not done correctly. The "execute update installer" command will be signed as always and will also include the md5 of executable which will be downloaded and run. So, the signature on the command must be correct (and will include the md5) and then calculated md5 on the downloaded file will need to be correct, before the execution will occur. This should take care of most attack vectors I can think of. Any others I should be concerned about?
So, now I am directing my attention to securing the private key on the server where these commands originate. If that private key is obtained, it is game over. How should I secure that key on disk?
My thought is to use symmetric encryption with a passphrase to secure that private key.
My current solution is as follows:
char[] passphrase; // Actual passphrase
PrivateKey privateKeyObj; // Actual Private Key
byte[] privateKeyBytes = privateKeyObj.getEncoded();
byte[] salt = new byte[8];
new SecureRandom().nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase, salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal(privateKeyBytes);
FileOutputStream outputFileStream = new FileOutputStream(outputFile);
outputFileStream.write(salt);
outputFileStream.write(iv);
outputFileStream.write(ciphertext);
outputFileStream.close();
(exceptions removed for clarity)
This basically randomly generates a salt, uses passphrase to come up with key and then stores the resulting salt, IV and cipher text in a file ready for decryption.
However, I feel something is missing here. Like I should also somehow include some type of MAC on the key so that I know I get a valid description? Maybe it is a simple as just putting 5 or 6 bytes of known text before the private key? A bad passphrase right now just results in bad padding exception, but I read that this might not always be the case and some bad passphrases will decode and result in junk. I feel I need to guard against this.
Can someone let me know if I am on the right track here and provide some feedback. It is important that I get this as secure as possible..
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
考虑到您想知道是否输错了密码,包含哈希值或 HMAC 是合理的。否则,你的解决方案对我来说看起来很合理。
为了尽可能安全地存储它,您还可以做另一件事:将其离线。将副本放在几个 USB 密钥上,将一个存储在安全的地方但可以拿到手上,并将备份放在保险箱中或交给律师。人们经常听说,如果物理上无法访问密钥,就可以获取密钥。
Including a hash or an HMAC is reasonable, given you want to know if you mistyped your passphrase. Otherwise, your solution looks reasonable to me.
One other thing you can do to store it as securely as possible: take it offline. Put copies on a couple of USB keys, store one somewhere safe but to hand, and the backup in a safety deposit box or with a lawyer. It's very heard to get the secret key if it's physically inaccessible.
在某些情况下,不使用 HMAC 或众所周知的前缀可能会使攻击者更难破解。请注意,如果您不包含 HMAC,在某些情况下您仍然会遇到解码异常(由损坏的填充引起),这足以识别错误的密码。
Not using a HMAC or a well known prefix might be used in some situations to make it harder for the attacker to crack. Note that if you do not include a HMAC you still get decoding exceptions in some situations (caused by a broken padding) which will be enough to recognize a wrong password.