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