diff --git a/Filesystem.Test/FileContainerTest.vb b/Filesystem.Test/FileContainerTest.vb index 2e6e5f92..67a3a024 100644 --- a/Filesystem.Test/FileContainerTest.vb +++ b/Filesystem.Test/FileContainerTest.vb @@ -3,7 +3,7 @@ Imports System.IO Imports Microsoft.VisualStudio.TestTools.UnitTesting Imports DigitalData.Modules.Filesystem Imports DigitalData.Modules.Logging -Imports DigitalData.Modules.Filesystem.FileContainer +Imports DigitalData.Modules.Filesystem.FileContainerOld Public Class FileContainerTest @@ -43,18 +43,18 @@ Imports DigitalData.Modules.Filesystem.FileContainer Public Sub TestConstructorPasswordNothing() - Dim oContainer = New FileContainer(_logConfig, Nothing) + Dim oContainer = New FileContainerOld(_logConfig, Nothing) End Sub Public Sub TestConstructorValidPassword() - Dim oContainer = New FileContainer(_logConfig, "foobar") + Dim oContainer = New FileContainerOld(_logConfig, "foobar") End Sub Public Sub TestAddFileNonExistentFilePath() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(FILENAME_NONEXISTENT) End Sub @@ -62,21 +62,21 @@ Imports DigitalData.Modules.Filesystem.FileContainer Public Sub TestAddFileNothingFilePath() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(Nothing) End Sub Public Sub TestAddFileValidFilePath() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(FILENAME_VALID) End Sub Public Sub TestFilesPropertyCount() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(FILENAME_VALID) Assert.AreEqual(1, oContainer.Files.Count) @@ -84,7 +84,7 @@ Imports DigitalData.Modules.Filesystem.FileContainer Public Sub TestFilesPropertyType() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(FILENAME_VALID) Assert.IsInstanceOfType(oContainer.Files.First(), GetType(FileEntry)) @@ -93,7 +93,7 @@ Imports DigitalData.Modules.Filesystem.FileContainer Public Sub TestSaveValidPath() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(FILENAME_VALID) oContainer.Save(CONTAINER_FILE_VALID) @@ -104,7 +104,7 @@ Imports DigitalData.Modules.Filesystem.FileContainer Public Sub TestSavePathNotWritable() - Dim oContainer As New FileContainer(_logConfig, PASSWORD) + Dim oContainer As New FileContainerOld(_logConfig, PASSWORD) oContainer.AddFile(FILENAME_VALID) oContainer.Save(CONTAINER_FILE_NOWRITE) @@ -112,13 +112,13 @@ Imports DigitalData.Modules.Filesystem.FileContainer Public Sub TestSaveLoadSameContents() - Dim oContainer As FileContainer + Dim oContainer As FileContainerOld Dim oFileContents As String = "dasisteintest" ' Test String in Textdatei schreiben IO.File.WriteAllText(FILENAME_VALID, oFileContents) - oContainer = New FileContainer(_logConfig, PASSWORD) + oContainer = New FileContainerOld(_logConfig, PASSWORD) ' Textdatei zu einem Container hinzufügen oContainer.AddFile(FILENAME_VALID) @@ -131,7 +131,7 @@ Imports DigitalData.Modules.Filesystem.FileContainer IO.File.Delete(FILENAME_VALID) oContainer = Nothing - oContainer = New FileContainer(_logConfig, PASSWORD) + oContainer = New FileContainerOld(_logConfig, PASSWORD) ' Container wieder laden oContainer.Load(CONTAINER_FILE_VALID) diff --git a/Filesystem/Encryption.vb b/Filesystem/Encryption.vb index c660167d..77f3631e 100644 --- a/Filesystem/Encryption.vb +++ b/Filesystem/Encryption.vb @@ -8,12 +8,19 @@ Imports DigitalData.Modules.Logging 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 + Private Const KEY_SIZE As Integer = 256 ' This constant determines the number of iterations for the password bytes generation function. Private Const DERIVATION_ITERATIONS As Integer = 1000 + Private Const BLOCK_SIZE As Integer = 256 + + Private _paddingMode As PaddingMode = PaddingMode.Zeros + Private _cipherMode As CipherMode = CipherMode.CBC + Private ReadOnly _password As String Private _logger As Logger + + Public Sub New(LogConfig As LogConfig, Password As String) _logger = LogConfig.GetLogger() @@ -37,11 +44,12 @@ Friend Class Encryption Dim oSaltStringBytes = Generate256BitsOfRandomEntropy() Dim oIvStringBytes = Generate256BitsOfRandomEntropy() Using oPassword = New Rfc2898DeriveBytes(_password, oSaltStringBytes, DERIVATION_ITERATIONS) - Dim oKeyBytes = oPassword.GetBytes(KEYSIZE / 8) + Dim oKeyBytes = oPassword.GetBytes(KEY_SIZE / 8) Using oSymmetricKey = New RijndaelManaged() - oSymmetricKey.BlockSize = 256 - oSymmetricKey.Mode = CipherMode.CBC - oSymmetricKey.Padding = PaddingMode.PKCS7 + oSymmetricKey.BlockSize = BLOCK_SIZE + oSymmetricKey.Mode = _cipherMode + oSymmetricKey.Padding = _paddingMode + Using oEncryptor = oSymmetricKey.CreateEncryptor(oKeyBytes, oIvStringBytes) Using oMemoryStream = New MemoryStream() Using oCryptoStream = New CryptoStream(oMemoryStream, oEncryptor, CryptoStreamMode.Write) @@ -76,18 +84,18 @@ Friend Class Encryption ' 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() + Dim oSaltStringBytes = cipherTextBytesWithSaltAndIv.Take(KEY_SIZE / 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() + Dim oIvStringBytes = cipherTextBytesWithSaltAndIv.Skip(KEY_SIZE / 8).Take(KEY_SIZE / 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() + Dim oCipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((KEY_SIZE / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((KEY_SIZE / 8) * 2)).ToArray() Using oPassword = New Rfc2898DeriveBytes(_password, oSaltStringBytes, DERIVATION_ITERATIONS) - Dim oKeyBytes = oPassword.GetBytes(KEYSIZE / 8) + Dim oKeyBytes = oPassword.GetBytes(KEY_SIZE / 8) Using oSymmetricKey = New RijndaelManaged() - oSymmetricKey.BlockSize = 256 - oSymmetricKey.Mode = CipherMode.CBC - oSymmetricKey.Padding = PaddingMode.PKCS7 + oSymmetricKey.BlockSize = BLOCK_SIZE + oSymmetricKey.Mode = _cipherMode + oSymmetricKey.Padding = _paddingMode Using oDecryptor = oSymmetricKey.CreateDecryptor(oKeyBytes, oIvStringBytes) Using oMemoryStream = New MemoryStream(oCipherTextBytes) Using oCryptoStream = New CryptoStream(oMemoryStream, oDecryptor, CryptoStreamMode.Read) diff --git a/Filesystem/FileContainer.vb b/Filesystem/FileContainer.vb index e0319f49..054a6c82 100644 --- a/Filesystem/FileContainer.vb +++ b/Filesystem/FileContainer.vb @@ -1,10 +1,10 @@ Imports System.IO -Imports System.Runtime.Serialization.Formatters.Binary Imports DigitalData.Modules.Logging +Imports ProtoBuf ''' FileContainer -''' 0.0.0.1 -''' 16.11.2018 +''' 0.0.0.2 +''' 21.11.2018 ''' ''' File Container for securely saving files ''' @@ -13,277 +13,191 @@ Imports DigitalData.Modules.Logging ''' ''' ''' LogConfig, DigitalData.Module.Logging.LogConfig -''' A LogConfig object +''' A LogConfig object +''' Password, String +''' The Password to Encrypt +''' Path, String +''' The Path to save/load the container ''' -''' -''' ''' +''' +''' +''' +''' +''' dim oContainer = Container.Create(logConfig, "pass", "E:\some.container") +''' dim oContainer = Container.Load(logConfig, "pass", "E:\some.container") +''' +''' dim oContainer = new Container(logConfig, "pass", "E:\some.container") +''' oContainer.Save() +''' +''' dim oContainer = new Container(logConfig, "pass", "E:\some.container") +''' oContainer.Contents = oSomeData +''' oContainer.Save() +''' +''' dim oContainer = new Container(logConfig, "pass", "E:\some.container") +''' oContainer.Load() +''' dim oContents = oContainer.Contents +''' +''' dim oContainer = new Container(logConfig, "pass", "E:\some.container") +''' oContainer.Load() +''' oContainer.Contents = oSomeOtherData +''' oContainer.Save() +''' oContainer.SaveAs("E:\some2.container") ''' -''' -''' 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 + Private _path As String - - 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 + Public Property Contents As Byte() Get - If _inner Is Nothing Then - Return Nothing - End If - - Return _inner.ContainerID.ToString + Return _inner.Contents + End Get + Set(value As Byte()) + _inner.Contents = value + End Set + End Property + Public Property Extension As String + Get + Return _inner.Extension + End Get + Set(value As String) + _inner.Extension = value + End Set + End Property + Public ReadOnly Property FileId As String + Get + Return _inner.FileId + End Get + End Property + Public ReadOnly Property CreatedAt As String + Get + Return _inner.CreatedAt + End Get + End Property + Public ReadOnly Property UpdatedAt As String + Get + Return _inner.UpdatedAt 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 + Public Shared Function Create(LogConfig As LogConfig, Password As String) As FileContainer + Dim oContainer = New FileContainer(LogConfig, Password) + Return oContainer + End Function - Return _inner.Files - End Get - End Property + Public Shared Function Load(LogConfig As LogConfig, Password As String, Path As String) As FileContainer + Dim oContainer = New FileContainer(LogConfig, Password, Path) + oContainer.Load() + Return oContainer + End Function + + Public Sub New(LogConfig As LogConfig, Password As String, Path As String) + _logger = LogConfig.GetLogger() + _crypto = New Encryption(LogConfig, Password) + _compression = New Compression(LogConfig) + _inner = New FileContainerInner() + _path = Path + End Sub - ''' - ''' 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() - } + _inner = New FileContainerInner() 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) + Public Sub SetFile(Contents As Byte(), Extension As String) + _inner.Contents = Contents + _inner.Extension = Extension + End Sub - BytesToFile(oFileContainerEncrypted, Path) + Public Sub Save() + If IsNothing(_path) Then + Throw New ArgumentException("Path not set") + End If + + SaveAs(_path) + End Sub + + Public Sub SaveAs(Path As String) + Try + WriteBytesToFile(TransformToBytes(_inner), 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 + Public Sub Load() + If IsNothing(_path) Then + Throw New ArgumentException("Path not set") + End If + + LoadFrom(_path) + End Sub + + Public Sub LoadFrom(Path As String) 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) + _inner = TransformToObject(ReadBytesFromFile(_path)) 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 + Private Function TransformToBytes([Object] As FileContainerInner) As Byte() + Dim oBytes = Serialize([Object]) + Dim oCompressed = _compression.Compress(oBytes) + Dim oEncrypted = _crypto.Encrypt(oCompressed) + Return oEncrypted End Function - ''' - ''' Fügt einem Datei Container eine neue Datei hinzu - ''' - ''' Der Pfad zur Datei, die im Container gespeichert werden soll - ''' - Public Function AddFile(FilePath As String) 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 - - Return AddFileAsByteArray(IO.File.ReadAllBytes(FilePath)) - Catch ex As Exception - _logger.Error(ex) - Throw ex - End Try - End Function - - Public Function AddFileAsByteArray(FileContents As Byte()) As String - Try - Dim oFileEntry As New FileEntry With { - .Contents = FileContents, - .FileId = Guid.NewGuid().ToString() - } - - _inner.Files.Add(oFileEntry) - - Return oFileEntry.FileId - Catch ex As Exception - _logger.Error(ex) - Throw ex - End Try - End Function - - Public Function GetFile(FileId As String) As FileEntry - Return _inner.Files.Where(Function(f) f.FileId = FileId).FirstOrDefault() + Private Function TransformToObject(Bytes As Byte()) As FileContainerInner + Dim oDecrypted = _crypto.Decrypt(Bytes) + Dim oDecompressed = _compression.Decompress(oDecrypted) + Dim oObject = Deserialize(oDecompressed) + Return oObject End Function Private Function Serialize(InnerData As FileContainerInner) As Byte() + Dim oBinaryData As Byte() + Using oStream As New MemoryStream - _formatter.Serialize(oStream, InnerData) - Return oStream.ToArray() + Serializer.Serialize(oStream, InnerData) + oBinaryData = oStream.ToArray() End Using + + Return oBinaryData End Function Private Function Deserialize(InnerData As Byte()) As FileContainerInner + Dim oObject As FileContainerInner + Using oStream As New MemoryStream(InnerData) - Dim oInner = TryCast(_formatter.Deserialize(oStream), FileContainerInner) - Return oInner + oObject = Serializer.Deserialize(Of FileContainerInner)(oStream) End Using + + Return oObject End Function - Private Sub BytesToFile(Data As Byte(), FilePath As String) - Using oSourceStream As New FileStream(FilePath, FileMode.Append, FileAccess.Write, FileShare.None) + Private Sub WriteBytesToFile(Data As Byte(), FilePath As String) + Using oSourceStream As New FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None) oSourceStream.Write(Data, 0, Data.Length) oSourceStream.Flush() End Using 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) + Private Function ReadBytesFromFile(FilePath As String) As Byte() + Using oFileStream = New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096) Dim oBuffer As Byte() = New Byte(oFileStream.Length - 1) {} - Await oFileStream.ReadAsync(oBuffer, 0, oFileStream.Length) + oFileStream.Read(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, 4096, True) - Await oSourceStream.WriteAsync(Data, 0, Data.Length) - Await oSourceStream.FlushAsync() - End Using - End Sub End Class diff --git a/Filesystem/FileContainerInner.vb b/Filesystem/FileContainerInner.vb new file mode 100644 index 00000000..0a77b30f --- /dev/null +++ b/Filesystem/FileContainerInner.vb @@ -0,0 +1,22 @@ +Imports ProtoBuf + + + +Public Class FileContainerInner + + Public FileId As String + + Public Contents As Byte() + + Public CreatedAt As DateTime + + Public UpdatedAt As DateTime + + Public Extension As String + + Public Sub New() + FileId = Guid.NewGuid.ToString + CreatedAt = Date.Now + End Sub + +End Class \ No newline at end of file diff --git a/Filesystem/Filesystem.vbproj b/Filesystem/Filesystem.vbproj index 75bfd148..83bf4193 100644 --- a/Filesystem/Filesystem.vbproj +++ b/Filesystem/Filesystem.vbproj @@ -47,6 +47,9 @@ ..\packages\NLog.4.5.10\lib\net45\NLog.dll + + ..\packages\protobuf-net.2.4.0\lib\net40\protobuf-net.dll + @@ -73,9 +76,10 @@ + - + True diff --git a/Filesystem/packages.config b/Filesystem/packages.config index 9764f16f..72e63d58 100644 --- a/Filesystem/packages.config +++ b/Filesystem/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file