Mit Public-Private-Keys/-Zertifikaten können Daten verschlüsselt und entschlüsselt werden. Der öffentliche Schlüssel wird verwendet zum Verschlüsseln, und später wird der geheime private Schlüssel genutzt, um verschlüsselte Daten zu entschlüsseln.
Nach sehr viel ausprobieren und recherchieren habe ich nachfolgende Funktion zum Verschlüsseln und entschlüsseln gefunden/entwickelt.
Verschlüsseln
private static void doEncryptFile(
string sourceFilePath,
string destinationFilePath)
{
var collection = new X509Certificate2Collection();
collection.Import(
encryptPublicKeyFilePath,
null,
X509KeyStorageFlags.PersistKeySet);
var certificate = collection[0];
var data = File.ReadAllBytes(sourceFilePath);
var contentInfo = new ContentInfo(data);
var envelopedCms = new EnvelopedCms(contentInfo);
envelopedCms.Encrypt(new CmsRecipient(certificate));
var encryptedData = envelopedCms.Encode();
File.WriteAllBytes(destinationFilePath, encryptedData);
}
encryptPublicKeyFilePath
ist hier der Dateipfad zu einem öffentlichen Schlüssel (als *.cer-Datei)
Entschlüsseln
private static void doDecryptFile(
string sourceFilePath,
string destinationFilePath)
{
var collection = new X509Certificate2Collection();
collection.Import(
encryptPrivateKeyFilePath,
encryptPassword,
X509KeyStorageFlags.PersistKeySet);
var encryptedData = File.ReadAllBytes(sourceFilePath);
var envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedData);
envelopedCms.Decrypt(collection);
var data = envelopedCms.ContentInfo.Content;
File.WriteAllBytes(destinationFilePath, data);
}
encryptPrivateKeyFilePath
ist hier der Dateipfad zu einem privaten Schlüssel (als *.pfx-Datei), der mit encryptPassword
geschützt ist.
Große Dateien
Ich habe festgestellt, dass obiger Code jeweils nur bei Dateien bis ca. 100 MB funktioniert. Das sei wohl eine Einschränkung von der .NET-Implementierung bei Microsoft.
Die zugrundeliegende Win32-Crypto-Bibliothek hat einen so genannten „Streaming mode“, der auch größere Dateien verarbeiten kann. Hier gibt es eine 10 Jahre alte Implementation, die ich leider beim Entschlüsseln nicht zum Laufen gebracht habe.
Ebenso habe ich es nach viel Ausprobieren, Googeln und Fluchen nicht geschafft, mit BouncyCastle C# die Funktionalität umzusetzen.
Ich bin deshalb einen anderen Weg gegangen:
Eine große zu verschlüsselnde Datei behandle ich wie folgt:
- Datei in Blöcke von maximal 70 MB aufsplitten
- Jeden Block-Datei einzeln verschlüsseln
- Alle verschlüsselten Block-Dateien in einer ZIP-Datei zusammen fassen
Umgekehrt mache ich es beim Entschlüsseln:
- ZIP-Datei entpacken
- Die einzelnen Block-Dateien entschlüsseln
- Alle entschlüsselten Block-Dateien in der richtigen Reihenfolge zu einer Datei hintereinander anhängen
Code fürs Verschlüsseln:
private static void encryptFile(
string sourceFilePath,
string destinationFilePath)
{
const int splitAtMB = 70;
const int splitAtBytes = splitAtMB * 1024 * 1024;
var tempSplittedFolder =
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(@"N"));
var tempEncryptedFolder =
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(@"N"));
Directory.CreateDirectory(tempSplittedFolder);
Directory.CreateDirectory(tempEncryptedFolder);
try
{
splitFile(sourceFilePath, splitAtBytes, tempSplittedFolder);
foreach (var sfp in Directory.GetFiles(tempSplittedFolder))
{
var dfp = Path.Combine(tempEncryptedFolder, Path.GetFileName(sfp));
doEncryptFile(sfp, dfp);
}
// ZIP bauen.
if (File.Exists(destinationFilePath)) File.Delete(destinationFilePath);
ZipFile.CreateFromDirectory(
tempEncryptedFolder,
destinationFilePath,
CompressionLevel.NoCompression,
false);
}
finally
{
Directory.Delete(tempSplittedFolder, true);
Directory.Delete(tempEncryptedFolder, true);
}
}
Code zum Entschlüsseln:
private static void decryptFile(
string sourceFilePath,
string destinationFilePath)
{
var tempSplittedFolder =
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(@"N"));
var tempEncryptedFolder =
Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(@"N"));
Directory.CreateDirectory(tempSplittedFolder);
Directory.CreateDirectory(tempEncryptedFolder);
try
{
ZipFile.ExtractToDirectory(
sourceFilePath,
tempEncryptedFolder);
foreach (var sfp in Directory.GetFiles(tempEncryptedFolder))
{
var dfp = Path.Combine(tempSplittedFolder, Path.GetFileName(sfp));
doDecryptFile(sfp, dfp);
}
// Zusammenbauen.
if (File.Exists(destinationFilePath)) File.Delete(destinationFilePath);
joinFiles(
Directory.GetFiles(tempSplittedFolder).OrderBy(f => f).ToArray(),
destinationFilePath);
}
finally
{
Directory.Delete(tempSplittedFolder, true);
Directory.Delete(tempEncryptedFolder, true);
}
}
Schlüssel erzeugen
Das Erzeugen der privaten und öffentlichen Schlüssel habe ich auch per Code machen wollen:
private static void makeCert(
string publicKeyFilePath,
string privateKeyFilePath,
string password,
string cn)
{
var ecdsa = RSA.Create(); // generate asymmetric key pair
var req = new CertificateRequest($@"cn={cn}", ecdsa,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var cert = req.CreateSelfSigned(
DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
// Create PFX (PKCS #12) with private key
File.WriteAllBytes(privateKeyFilePath,
cert.Export(X509ContentType.Pkcs12, password));
// Create CER (public key only)
File.WriteAllBytes(publicKeyFilePath,
cert.Export(X509ContentType.Cert));
}