在clickonce应用程序中安全地存储服务帐户凭据 [英] Storing service account credentials securely in clickonce application
问题描述
//用于生成解密的acct凭据
private void EncryptText(string plaintext)
{
string outsrt = null;
RijndaelManaged aesAlg = null;
尝试
{
//从密钥和盐生成密钥
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedsecret,_salt);
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key,aesAlg.IV);
使用(MemoryStream mEncrypt = new MemoryStream())
{
//前置IV
mEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length) ,0,sizeof(int));
mEncrypt.Write(aesAlg.IV,0,aesAlg.IV.Length);
using(CryptoStream csEncrypt = new CryptoStream(mEncrypt,encryptor,CryptoStreamMode.Write))
{
using(StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//将所有数据写入流
swEncrypt.Write(plaintext);
}
}
outsrt = Convert.ToBase64String(mEncrypt.ToArray());
}
}
finally
{
if(aesAlg!= null)
aesAlg.Clear();
}
Console.WriteLine(outsrt);
}
这是解密方法:
私有字符串GetServiceAcctPW()
{
//声明RijndaelManaged对象
//用于解密数据。
RijndaelManaged aesAlg = null;
//声明用于保存
//解密文本的字符串。
string plaintext = null;
尝试
{
//从共享密钥和盐生成密钥
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedsecret,_salt);
//创建用于解密的流。
byte [] bytes = Convert.FromBase64String(EncryptedValueHere);
使用(MemoryStream msDecrypt = new MemoryStream(bytes))
{
//使用指定的键创建一个RijndaelManaged对象
//和IV。
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
//从加密流获取初始化向量
aesAlg.IV = ReadByteArray(msDecrypt);
//创建一个解密器来执行流转换。
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key,aesAlg.IV);
using(CryptoStream csDecrypt = new CryptoStream(msDecrypt,decryptor,CryptoStreamMode.Read))
{
using(StreamReader srDecrypt = new StreamReader(csDecrypt))
/ /从解密流
//读取解密的字节,并将它们放在字符串中。
plaintext = srDecrypt.ReadToEnd();
}
}
}
catch(异常e)
{
Console.WriteLine(Error decrypting password);
Console.WriteLine(e.StackTrace);
logger.WriteToLog(Logger.LogCodes.ERROR,解密服务帐户密码错误);
MessageBox.Show(尝试启动安装过程时发生错误请联系服务台获取进一步的帮助);
}
finally
{
//清除RijndaelManaged对象。
if(aesAlg!= null)
aesAlg.Clear();
}
返回明文;
}
此代码工作正常,但是我知道这不安全。我的代码评论家说他能够在一小时内用dotPeek解决它,因为它只是添加了一层混淆。在应用程序中存储这些凭证的最佳/正确的方法是什么?
加密密钥在专用服务器上。
密码与要加密的ID一起发送到服务器,并为DB存储返回加密的密码。
当需要密码时,请求具有ID的专用服务器,并返回解密的密码。
密码永远不会保存到磁盘,密钥永远不可用于专用服务器。
专用服务器是一种类似于穷人的HSM。
这是加密,不是哈希。加密密钥是一个随机的IV,它与专用服务器上的id一起保存。密钥不可用,与密码无关,所以没有比强力对抗加密密钥更好的攻击,该加密密钥本质上是大到被强力攻击的。
服务器需要非常安全,只需要两个因素登录,而不能上网。
I'm writing a ClickOnce application that runs a batch file process with service account credentials. I need to store the service account credentials so that the program can add the username/password to the process.startinfo property before running the process. The users do not know this password, so there's no prompt for them to enter in a password. I believe this means I cannot store the hash and verify the password that way, the hash value I generate must be reversible so that it can add the correct password to the startinfo property. I searched around this site and came up with a Frankenstein-type solution that works, but it's not very secure. Currently, I used this method to encrypt the password, stored the encrypted value, then use the decrypt method to obtain the password during runtime (the encrypt method is never ran during runtime, I ran it in Visual Studio during debug, copied the value, then used that value in the decrypt method below this):
// used to generate decrypted acct creds
private void EncryptText(string plaintext)
{
string outsrt = null;
RijndaelManaged aesAlg = null;
try
{
// generate key from secret and salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedsecret, _salt);
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream mEncrypt = new MemoryStream())
{
// prepend the IV
mEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length), 0, sizeof(int));
mEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);
using (CryptoStream csEncrypt = new CryptoStream(mEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
// write all data to the stream
swEncrypt.Write(plaintext);
}
}
outsrt = Convert.ToBase64String(mEncrypt.ToArray());
}
}
finally
{
if (aesAlg != null)
aesAlg.Clear();
}
Console.WriteLine(outsrt);
}
Here's the decrypt method:
private string GetServiceAcctPW()
{
// Declare the RijndaelManaged object
// used to decrypt the data.
RijndaelManaged aesAlg = null;
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
try
{
// generate the key from the shared secret and the salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedsecret, _salt);
// Create the streams used for decryption.
byte[] bytes = Convert.FromBase64String("EncryptedValueHere");
using (MemoryStream msDecrypt = new MemoryStream(bytes))
{
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
// Get the initialization vector from the encrypted stream
aesAlg.IV = ReadByteArray(msDecrypt);
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
catch(Exception e)
{
Console.WriteLine("Error decrypting password");
Console.WriteLine(e.StackTrace);
logger.WriteToLog(Logger.LogCodes.ERROR, "Error decrypting service account password");
MessageBox.Show("An error occurred while trying to start the installation process\nPlease contact the Service Desk for further assistance");
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
return plaintext;
}
This code works just fine, however, I know it's not secure. My code review guy said he was able to crack it with dotPeek in an hour because it's only adding a layer of obfuscation. What would be the best/proper way to store these credentials within the application?
The encryption key is on a dedicated server.
The password is sent to the server along with an id to be encrypted and the encrypted password returned for DB storage.
When the the password is needed a request is made to the dedicated server with the id and a decrypted password is returned.
The password is never saved to disk and the key is never available off the dedicated server.
The dedicated server is kind-of-like a poor-mans HSM.
This is encryption, not hashing. The encryption key is secret along with a random IV that that is saved with the id on the dedicated server. The key is not available and not related to the password so there is no better attack than brute force against the encryption key which is essentially to large to be attacked by brute force.
The server needs to be very secure, only a couple of two factor logins and not available to the Internet.
这篇关于在clickonce应用程序中安全地存储服务帐户凭据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!