diff --git a/Filesystem/Compression.vb b/Filesystem/Compression.vb
new file mode 100644
index 00000000..9edb4517
--- /dev/null
+++ b/Filesystem/Compression.vb
@@ -0,0 +1,70 @@
+Imports System.IO
+Imports System.IO.Compression
+Imports DigitalData.Modules.Logging
+
+Friend Class Compression
+ Private ReadOnly _logger As Logger
+
+ Public Sub New(LogConfig As LogConfig)
+ _logger = LogConfig.GetLogger()
+ End Sub
+
+ Public Async Function CompressAsync(data As Byte()) As Task(Of Byte())
+ Return Await Task.Run(Function() As Byte()
+ Return Compress(data)
+ End Function)
+ End Function
+
+ Public Function Compress(data As Byte()) As Byte()
+ Try
+ ' ByteArray in Stream umwandeln
+ Using originalStream As New MemoryStream(data)
+ ' Ziel Stream erstellen
+ Using compressedStream As New MemoryStream()
+ ' Gzip-Stream erstellen, der alle Daten komprimiert und zu compressedStream durchleitet
+ '
+ ' > MemoryStream > GzipStream > MemoryStream
+ ' originalStream --> compressionStream --> compressedFileStream
+ '
+ Using compressionStream As New GZipStream(compressedStream, CompressionMode.Compress)
+ originalStream.CopyTo(compressionStream)
+ compressionStream.Close()
+ Return compressedStream.ToArray()
+ End Using
+ End Using
+ End Using
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Function
+
+ Public Async Function DecompressAsync(data As Byte()) As Task(Of Byte())
+ Return Await Task.Run(Function() As Byte()
+ Return Decompress(data)
+ End Function)
+ End Function
+
+ Public Function Decompress(data As Byte()) As Byte()
+ Try
+ ' ByteArray in Stream umwandeln
+ Using compressedStream As New MemoryStream(data)
+ ' Ziel Stream erstellen
+ Using decompressedStream As New MemoryStream()
+ ' Gzip-Stream erstellen, der alle Daten komprimiert und zu compressedStream durchleitet
+ '
+ ' > MemoryStream > GzipStream > MemoryStream
+ ' compressedStream --> decompressionStream --> decompressedStream
+ '
+ Using decompressionStream As New GZipStream(compressedStream, CompressionMode.Decompress)
+ decompressionStream.CopyTo(decompressedStream)
+ Return decompressedStream.ToArray()
+ End Using
+ End Using
+ End Using
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Function
+End Class
diff --git a/Filesystem/Encryption.vb b/Filesystem/Encryption.vb
new file mode 100644
index 00000000..c660167d
--- /dev/null
+++ b/Filesystem/Encryption.vb
@@ -0,0 +1,119 @@
+Imports System.IO
+Imports System.Security.Cryptography
+Imports DigitalData.Modules.Logging
+
+'''
+''' https://stackoverflow.com/questions/10168240/encrypting-decrypting-a-string-in-c-sharp
+'''
+Friend Class Encryption
+ ' This constant is used to determine the keysize of the encryption algorithm in bits.
+ ' We divide this by 8 within the code below to get the equivalent number of bytes.
+ Private Const KEYSIZE As Integer = 256
+ ' This constant determines the number of iterations for the password bytes generation function.
+ Private Const DERIVATION_ITERATIONS As Integer = 1000
+ Private ReadOnly _password As String
+ Private _logger As Logger
+
+ Public Sub New(LogConfig As LogConfig, Password As String)
+ _logger = LogConfig.GetLogger()
+
+ If IsNothing(Password) Then
+ Throw New ArgumentNullException("Password")
+ End If
+
+ _password = Password
+ End Sub
+
+ Public Async Function EncryptAsync(oPlainTextBytes As Byte()) As Task(Of Byte())
+ Return Await Task.Run(Function() As Byte()
+ Return Encrypt(oPlainTextBytes)
+ End Function)
+ End Function
+
+ Public Function Encrypt(oPlainTextBytes As Byte()) As Byte()
+ Try
+ ' Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
+ ' so that the same Salt and IV values can be used when decrypting.
+ Dim oSaltStringBytes = Generate256BitsOfRandomEntropy()
+ Dim oIvStringBytes = Generate256BitsOfRandomEntropy()
+ Using oPassword = New Rfc2898DeriveBytes(_password, oSaltStringBytes, DERIVATION_ITERATIONS)
+ Dim oKeyBytes = oPassword.GetBytes(KEYSIZE / 8)
+ Using oSymmetricKey = New RijndaelManaged()
+ oSymmetricKey.BlockSize = 256
+ oSymmetricKey.Mode = CipherMode.CBC
+ oSymmetricKey.Padding = PaddingMode.PKCS7
+ Using oEncryptor = oSymmetricKey.CreateEncryptor(oKeyBytes, oIvStringBytes)
+ Using oMemoryStream = New MemoryStream()
+ Using oCryptoStream = New CryptoStream(oMemoryStream, oEncryptor, CryptoStreamMode.Write)
+ oCryptoStream.Write(oPlainTextBytes, 0, oPlainTextBytes.Length)
+ oCryptoStream.FlushFinalBlock()
+ ' Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
+ Dim oCipherTextBytes = oSaltStringBytes
+ oCipherTextBytes = oCipherTextBytes.Concat(oIvStringBytes).ToArray()
+ oCipherTextBytes = oCipherTextBytes.Concat(oMemoryStream.ToArray()).ToArray()
+ oMemoryStream.Close()
+ oCryptoStream.Close()
+ Return oCipherTextBytes
+ End Using
+ End Using
+ End Using
+ End Using
+ End Using
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Function
+
+ Public Async Function DecryptAsync(CipherTextBytesWithSaltAndIv As Byte()) As Task(Of Byte())
+ Return Await Task.Run(Function() As Byte()
+ Return Decrypt(CipherTextBytesWithSaltAndIv)
+ End Function)
+ End Function
+
+ Public Function Decrypt(cipherTextBytesWithSaltAndIv As Byte()) As Byte()
+ Try
+ ' Get the complete stream of bytes that represent:
+ ' [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
+ ' Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
+ Dim oSaltStringBytes = cipherTextBytesWithSaltAndIv.Take(KEYSIZE / 8).ToArray()
+ ' Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
+ Dim oIvStringBytes = cipherTextBytesWithSaltAndIv.Skip(KEYSIZE / 8).Take(KEYSIZE / 8).ToArray()
+ ' Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
+ Dim oCipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((KEYSIZE / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((KEYSIZE / 8) * 2)).ToArray()
+
+ Using oPassword = New Rfc2898DeriveBytes(_password, oSaltStringBytes, DERIVATION_ITERATIONS)
+ Dim oKeyBytes = oPassword.GetBytes(KEYSIZE / 8)
+ Using oSymmetricKey = New RijndaelManaged()
+ oSymmetricKey.BlockSize = 256
+ oSymmetricKey.Mode = CipherMode.CBC
+ oSymmetricKey.Padding = PaddingMode.PKCS7
+ Using oDecryptor = oSymmetricKey.CreateDecryptor(oKeyBytes, oIvStringBytes)
+ Using oMemoryStream = New MemoryStream(oCipherTextBytes)
+ Using oCryptoStream = New CryptoStream(oMemoryStream, oDecryptor, CryptoStreamMode.Read)
+ Dim oPlainTextBytes = New Byte(oCipherTextBytes.Length - 1) {}
+ Dim oDecryptedByteCount = oCryptoStream.Read(oPlainTextBytes, 0, oPlainTextBytes.Length)
+ oMemoryStream.Close()
+ oCryptoStream.Close()
+ Return oPlainTextBytes
+ End Using
+ End Using
+ End Using
+ End Using
+ End Using
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Function
+
+ Private Shared Function Generate256BitsOfRandomEntropy() As Byte()
+ Dim oRandomBytes = New Byte(31) {}
+ ' 32 Bytes will give us 256 bits.
+ Using oRNGCsp = New RNGCryptoServiceProvider()
+ ' Fill the array with cryptographically secure random bytes.
+ oRNGCsp.GetBytes(oRandomBytes)
+ End Using
+ Return oRandomBytes
+ End Function
+End Class
diff --git a/Filesystem/FileContainer.vb b/Filesystem/FileContainer.vb
new file mode 100644
index 00000000..7015e7b2
--- /dev/null
+++ b/Filesystem/FileContainer.vb
@@ -0,0 +1,284 @@
+Imports System.IO
+Imports System.Runtime.Serialization.Formatters.Binary
+Imports DigitalData.Modules.Logging
+
+''' FileContainer
+''' 0.0.0.1
+''' 16.11.2018
+'''
+''' File Container for securely saving files
+'''
+'''
+''' NLog, >= 4.5.8
+'''
+'''
+''' LogConfig, DigitalData.Module.Logging.LogConfig
+''' A LogConfig object
+'''
+'''
+'''
+'''
+'''
+'''
+'''
+Public Class FileContainer
+ Private _files As List(Of FileEntry)
+ Private _crypto As Encryption
+ Private _compression As Compression
+ Private _formatter As BinaryFormatter
+ Private _containerId As Guid
+ Private _inner As FileContainerInner
+ Private _logger As Logger
+ Private _logConfig As LogConfig
+
+
+ Public Class FileEntry
+ Public FileId As String
+ Public Contents As Byte()
+ Public CreatedAt As DateTime
+ Public UpdatedAt As DateTime
+ End Class
+
+
+ Public Class FileContainerInner
+ Public Files As List(Of FileEntry)
+ Public ContainerID As Guid
+ Public CreatedAt As DateTime
+ Public UpdatedAt As DateTime
+ End Class
+
+ Public ReadOnly Property ContainerId As String
+ Get
+ If _inner Is Nothing Then
+ Return Nothing
+ End If
+
+ Return _inner.ContainerID.ToString
+ End Get
+ End Property
+
+
+ '''
+ ''' Gibt eine Auflistung der Dateien in Container zurück.
+ '''
+ ''' Eine Liste von Objekten der Klasse FileContainer.FileEntry
+ Public ReadOnly Property Files As List(Of FileEntry)
+ Get
+ If _inner Is Nothing Then
+ Return Nothing
+ End If
+
+ Return _inner.Files
+ End Get
+ End Property
+
+ '''
+ ''' Erstellt eine Representation eines Datei Containers, der mit dem angegebenen Passwort geschützt ist.
+ ''' Ist für das speichern von neuen Containern und für das laden vorhandener Container nötig.
+ '''
+ ''' Das Passwort, mit dem der Container ver- und entschlüsselt wird
+ '''
+ ''' Dim password = "meinpasswort"
+ ''' Dim container = new FileContainer(password)
+ '''
+ Public Sub New(LogConfig As LogConfig, Password As String)
+ _logger = LogConfig.GetLogger()
+ _crypto = New Encryption(LogConfig, Password)
+ _compression = New Compression(LogConfig)
+ _formatter = New BinaryFormatter
+ _inner = New FileContainerInner() With {
+ .Files = New List(Of FileEntry),
+ .ContainerID = Guid.NewGuid(),
+ .CreatedAt = New DateTime()
+ }
+ End Sub
+
+ '''
+ ''' Speichert einen Datei Container am angegebenen Pfad.
+ ''' Davor werden die Dateien in Files komprimiert und dann verschlüsselt.
+ '''
+ ''' Der Pfad mit Dateiname, unter dem der Container abgelegt wird.
+ '''
+ '''
+ ''' container.AddFile("datei1.txt")
+ ''' container.AddFile("datei2.pdf")
+ ''' container.Save("container.enc")
+ '''
+ Public Sub Save(Path As String)
+ Try
+ ' 1. Serialize
+ Dim oFileContainerInner As Byte() = Serialize(_inner)
+ ' 2. Compress
+ Dim oFileContainerCompressed As Byte() = _compression.Compress(oFileContainerInner)
+ ' 3. Encrypt
+ Dim oFileContainerEncrypted As Byte() = _crypto.Encrypt(oFileContainerCompressed)
+
+ BytesToFile(oFileContainerEncrypted, Path)
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Sub
+
+ '''
+ ''' Speichert einen Datei Container am angegebenen Pfad.
+ ''' Davor werden die Dateien in Files komprimiert und dann verschlüsselt.
+ '''
+ ''' Der Pfad mit Dateiname, unter dem der Container abgelegt wird.
+ '''
+ ''' Public Async Sub foobar()
+ ''' ...
+ ''' container.AddFile("datei1.txt")
+ ''' container.AddFile("datei2.pdf")
+ ''' await container.SaveAsync("container.enc")
+ ''' ...
+ ''' End Sub
+ '''
+ Public Async Function SaveAsync(Path As String) As Task
+ Try
+ ' 1. Serialize
+ Dim oFileContainerInner As Byte() = Serialize(_inner)
+ ' 2. Compress
+ Dim oFileContainerCompressed As Byte() = Await _compression.CompressAsync(oFileContainerInner)
+ 'Dim bFilesCompressed = bFiles
+ ' 3. Encrypt
+ Dim oFileContainerEncrypted As Byte() = Await _crypto.EncryptAsync(oFileContainerCompressed)
+
+ BytesToFileAsync(oFileContainerEncrypted, Path)
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Function
+
+ '''
+ ''' Lädt einen Datei Container aus dem angegebenen Pfad.
+ ''' Anschließend werden die enthaltenen Dateien entschlüsselt, dekomprimiert und in Files abgelegt.
+ '''
+ ''' Der Pfad mit Dateiname, von dem der Container geladen wird.
+ '''
+ '''
+ ''' container.Load("container.enc")
+ ''' Dim files As List(Of FileContainer.FileEntry) = container.Files
+ '''
+ Public Sub Load(Path As String)
+ Try
+ Dim oContainerInnerEncrypted As Byte() = FileToBytes(Path)
+
+ ' 1. Decrypt
+ Dim oFileContainerCompressed As Byte() = _crypto.Decrypt(oContainerInnerEncrypted)
+ ' 2. Decompress
+ Dim oFileContainerInner As Byte() = _compression.Decompress(oFileContainerCompressed)
+ ' 3. Deserialize
+
+ _inner = Deserialize(oFileContainerInner)
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Sub
+
+ '''
+ ''' Lädt einen Datei Container aus dem angegebenen Pfad.
+ ''' Anschließend werden die enthaltenen Dateien entschlüsselt, dekomprimiert und in Files abgelegt.
+ '''
+ ''' Der Pfad mit Dateiname, von dem der Container geladen wird.
+ '''
+ ''' Public Async Sub foobar()
+ ''' ...
+ ''' await container.LoadAsync("container.enc")
+ ''' Dim files As List(Of FileContainer.FileEntry) = container.Files
+ ''' ...
+ ''' End Sub
+ '''
+ Public Async Function LoadAsync(Path As String) As Task
+ Try
+ Dim oContainerInnerEncrypted As Byte() = Await FileToBytesAsync(Path)
+
+ ' 1. Decrypt
+ Dim oFileContainerCompressed As Byte() = Await _crypto.DecryptAsync(oContainerInnerEncrypted)
+ ' 2. Decompress
+ Dim oFileContainerInner As Byte() = Await _compression.DecompressAsync(oFileContainerCompressed)
+ ' 3. Deserialize
+ _inner = Deserialize(oFileContainerInner)
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Function
+
+ '''
+ ''' Fügt einem Datei Container eine neue Datei hinzu
+ '''
+ ''' Der Pfad zur Datei, die im Container gespeichert werden soll
+ '''
+ Public Sub AddFile(FilePath As String)
+ Try
+ Dim oFileInfo As New FileInfo(FilePath)
+
+ If oFileInfo.Exists = False Then
+ Throw New FileNotFoundException($"{FilePath} is not a valid path.")
+ End If
+
+ AddFile(IO.File.ReadAllBytes(FilePath))
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Sub
+
+ Public Sub AddFile(FileContents As Byte())
+ Try
+ Dim oFileEntry As New FileEntry With {
+ .Contents = FileContents,
+ .FileId = Guid.NewGuid().ToString()
+ }
+
+ _inner.Files.Add(oFileEntry)
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Sub
+
+ Public Function GetFile(FileId As String) As FileEntry
+ Return _inner.Files.Where(Function(f) f.FileId = FileId).FirstOrDefault()
+ End Function
+
+ Private Function Serialize(InnerData As FileContainerInner) As Byte()
+ Using oStream As New MemoryStream
+ _formatter.Serialize(oStream, InnerData)
+ Return oStream.ToArray()
+ End Using
+ End Function
+
+ Private Function Deserialize(InnerData As Byte()) As FileContainerInner
+ Using oStream As New MemoryStream(InnerData)
+ Dim oInner = TryCast(_formatter.Deserialize(oStream), FileContainerInner)
+ Return oInner
+ End Using
+ End Function
+
+ Private Sub BytesToFile(Data As Byte(), FilePath As String)
+ IO.File.WriteAllBytes(FilePath, Data)
+ End Sub
+
+ Private Function FileToBytes(FilePath As String) As Byte()
+ Return IO.File.ReadAllBytes(FilePath)
+ End Function
+
+ Private Async Function FileToBytesAsync(FilePath As String) As Task(Of Byte())
+ Using oFileStream = New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, True)
+ Dim oBuffer As Byte() = New Byte(oFileStream.Length - 1) {}
+ Await oFileStream.ReadAsync(oBuffer, 0, oFileStream.Length)
+ oFileStream.Close()
+ Return oBuffer
+ End Using
+ End Function
+
+ Private Async Sub BytesToFileAsync(Data As Byte(), FilePath As String)
+ Using oSourceStream As New FileStream(FilePath, FileMode.Append, FileAccess.Write, FileShare.None, bufferSize:=4096, useAsync:=True)
+ Await oSourceStream.WriteAsync(Data, 0, Data.Length)
+ End Using
+ End Sub
+End Class
diff --git a/Filesystem/Filesystem.vbproj b/Filesystem/Filesystem.vbproj
index 48eaf125..75bfd148 100644
--- a/Filesystem/Filesystem.vbproj
+++ b/Filesystem/Filesystem.vbproj
@@ -72,7 +72,10 @@
+
+
+
True