Manchmal möchte man ein Geheimnis in der Applikation speichern. Nun muss aber die Applikation die Möglichkeit besitzen das Geheimnis zu lesen.Und genau das ist der Knackpunkt.
Wenn irgendwo so was steht
const string password = "CP3wCUrRJhEd5iXahgq4Qpz3";
kann man davon ausgehen, dass jeder Idiot dieses Password auslesen kann. Z.b. mit dotPeek
https://www.jetbrains.com/decompiler/
Ich dachte mir, ich versuche mich mal an einer Lösung welche den Key Store vom Betriebssystem nutzt.
Test First
[TestMethod]
public void EncryptDecrypt()
{
var input = GetData();
var identifier = Guid.NewGuid().ToString();
var container = CredentialStorage.CredentialStorage.Encrypt(identifier, input);
var decrypted = CredentialStorage.CredentialStorage.Decrypt(identifier, container);
CredentialStorage.CredentialStorage.RSADeleteKeyInCSP(identifier);
for (int i = 0; i < decrypted.Length; i++)
{
if (decrypted[i] != input[i])
{
Assert.Fail("Input and output are not equal.");
}
}
}
Byte[] input hält nur ein Haufen voller zufälligen Bytes.
string identifier soll im Produktiv-System ein eindeutiger Name sein.
Nach dem verschlüsseln entschlüsseln wir die Daten und hoffen auf gleiche Daten.
Verschlüsseln
Wir holen uns einen RsaCryptoServiceProvider (siehe unten "CSP Key Storage Helper") und verschlüsseln damit einen AES Schlüssel. Mit diesen AES Schlüssel verschlüsseln wir die Datem und geben diesen zurück.
public static StorageContainer Encrypt(string identifier, byte[] data)
{
var provider = GetRsaCryptoServiceProvider(identifier);
byte[] encryptedData;
byte[] encryptedKeywithRsa;
byte[] iv;
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.KeySize = 256;
rijAlg.GenerateKey();
rijAlg.GenerateIV();
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(data, 0, data.Length);
csEncrypt.FlushFinalBlock();
}
encryptedData = msEncrypt.ToArray();
}
encryptedKeywithRsa = provider.Encrypt(rijAlg.Key, true);
iv = rijAlg.IV;
}
var container = new StorageContainer {Iv = iv, EncryptedKeywithRsa = encryptedKeywithRsa, EncryptedData = encryptedData};
return container;
}
Entschlüsseln
So trivial wie das verschlüssseln, nur dass geprüft wird ob im Windows Key Storage der Schlüssel mit diesen Identifier vorhanden ist.
public static byte[] Decrypt(string identifier, StorageContainer container)
{
if (!DoesKeyExists(identifier))
{
throw new Exception(string.Format("No Key in CSP Container \"{0}\" ", identifier));
}
var provider = GetRsaCryptoServiceProvider(identifier);
byte[] encryptedData;
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.KeySize = 256;
rijAlg.Key = provider.Decrypt(container.EncryptedKeywithRsa, true);
rijAlg.IV = container.Iv;
ICryptoTransform encryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(container.EncryptedData, 0, container.EncryptedData.Length);
}
encryptedData = msEncrypt.ToArray();
}
}
return encryptedData;
}
CSP Key Storage Helper
private static RSACryptoServiceProvider GetRsaCryptoServiceProvider(string identifier)
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = identifier;
var provider = new RSACryptoServiceProvider(cspParams);
provider.PersistKeyInCsp = true;
provider.KeySize = 4096;
return provider;
}
public static bool DoesKeyExists(string containerName)
{
var cspParams = new CspParameters
{
Flags = CspProviderFlags.UseExistingKey,
KeyContainerName = containerName
};
try
{
var provider = new RSACryptoServiceProvider(cspParams);
}
catch (Exception e)
{
return false;
}
return true;
}
public static void RSADeleteKeyInCSP(string ContainerName)
{
CspParameters cspParams = new CspParameters
{
KeyContainerName = ContainerName
};
RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider(cspParams);
RSAalg.PersistKeyInCsp = false;
RSAalg.Clear();
Console.WriteLine("The RSA key was deleted from the container, \"{0}\".", ContainerName);
}
Download
CredentialStoragePublic.7z
https://db.tt/NDFhKbgG
Fazit
Ich bin ein Feind von Security through obscurity, aber ich glaube dass ich mit dieser Art von Verschlüsselung zumindest ein paar Use Cases gut bedienen kann. Es gibt noch die Möglichkeit, dass der Benutzer beim Zugriff aus den Key Storage seine Windows Credentials angeben muss. Um das zu realisieren müssen nur die CSP Parameter entsprechend anspassen.
Interessante Links
How Private Keys Are Stored
http://technet.microsoft.com/en-us/library/cc962112.aspx
Key Storage and Retrieval
http://msdn.microsoft.com/en-us/library/windows/desktop/bb204778%28v=vs.85%29.aspx
Keine Kommentare:
Kommentar veröffentlichen