jj: Add filecontainer

This commit is contained in:
Jonathan Jenne 2018-11-16 11:51:24 +01:00
parent 734becb971
commit 4858bb2be5
4 changed files with 476 additions and 0 deletions

70
Filesystem/Compression.vb Normal file
View File

@ -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

119
Filesystem/Encryption.vb Normal file
View File

@ -0,0 +1,119 @@
Imports System.IO
Imports System.Security.Cryptography
Imports DigitalData.Modules.Logging
''' <summary>
''' https://stackoverflow.com/questions/10168240/encrypting-decrypting-a-string-in-c-sharp
''' </summary>
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

284
Filesystem/FileContainer.vb Normal file
View File

@ -0,0 +1,284 @@
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Imports DigitalData.Modules.Logging
''' <module>FileContainer</module>
''' <version>0.0.0.1</version>
''' <date>16.11.2018</date>
''' <summary>
''' File Container for securely saving files
''' </summary>
''' <dependencies>
''' NLog, >= 4.5.8
''' </dependencies>
''' <params>
''' LogConfig, DigitalData.Module.Logging.LogConfig
''' A LogConfig object
''' </params>
''' <props>
''' </props>
''' <example>
''' </example>
''' <remarks>
''' </remarks>
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
<Serializable>
Public Class FileEntry
Public FileId As String
Public Contents As Byte()
Public CreatedAt As DateTime
Public UpdatedAt As DateTime
End Class
<Serializable>
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
''' <summary>
''' Gibt eine Auflistung der Dateien in Container zurück.
''' </summary>
''' <returns>Eine Liste von Objekten der Klasse FileContainer.FileEntry</returns>
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
''' <summary>
''' 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.
''' </summary>
''' <param name="Password">Das Passwort, mit dem der Container ver- und entschlüsselt wird</param>
''' <example>
''' Dim password = "meinpasswort"
''' Dim container = new FileContainer(password)
''' </example>
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
''' <summary>
''' Speichert einen Datei Container am angegebenen Pfad.
''' Davor werden die Dateien in Files komprimiert und dann verschlüsselt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, unter dem der Container abgelegt wird.</param>
''' <see cref="Files"/>
''' <example>
''' container.AddFile("datei1.txt")
''' container.AddFile("datei2.pdf")
''' container.Save("container.enc")
''' </example>
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
''' <summary>
''' Speichert einen Datei Container am angegebenen Pfad.
''' Davor werden die Dateien in Files komprimiert und dann verschlüsselt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, unter dem der Container abgelegt wird.</param>
''' <example>
''' Public Async Sub foobar()
''' ...
''' container.AddFile("datei1.txt")
''' container.AddFile("datei2.pdf")
''' await container.SaveAsync("container.enc")
''' ...
''' End Sub
''' </example>
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
''' <summary>
''' Lädt einen Datei Container aus dem angegebenen Pfad.
''' Anschließend werden die enthaltenen Dateien entschlüsselt, dekomprimiert und in Files abgelegt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, von dem der Container geladen wird.</param>
''' <see cref="Files"/>
''' <example>
''' container.Load("container.enc")
''' Dim files As List(Of FileContainer.FileEntry) = container.Files
''' </example>
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
''' <summary>
''' Lädt einen Datei Container aus dem angegebenen Pfad.
''' Anschließend werden die enthaltenen Dateien entschlüsselt, dekomprimiert und in Files abgelegt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, von dem der Container geladen wird.</param>
''' <example>
''' Public Async Sub foobar()
''' ...
''' await container.LoadAsync("container.enc")
''' Dim files As List(Of FileContainer.FileEntry) = container.Files
''' ...
''' End Sub
''' </example>
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
''' <summary>
''' Fügt einem Datei Container eine neue Datei hinzu
''' </summary>
''' <param name="FilePath">Der Pfad zur Datei, die im Container gespeichert werden soll</param>
''' <exception cref="FileNotFoundException" />
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

View File

@ -72,7 +72,10 @@
<Import Include="System.Threading.Tasks" />
</ItemGroup>
<ItemGroup>
<Compile Include="Compression.vb" />
<Compile Include="Encryption.vb" />
<Compile Include="File.vb" />
<Compile Include="FileContainer.vb" />
<Compile Include="My Project\AssemblyInfo.vb" />
<Compile Include="My Project\Application.Designer.vb">
<AutoGen>True</AutoGen>