13 Commits

Author SHA1 Message Date
Ihar Katkavets 5747e62549 main main Fix issue reported at https://github.com/iharkatkavets/StreamKit/issues/5 2024-05-04 21:42:51 +02:00
Ihar Katkavets d1b0bfe2a7 main Rename md files 2023-08-26 18:29:03 +02:00
Ihar Katkavets 36e337b163 main Merge branch 'main' of github.com:iharkatkavets/StreamKit 2023-08-26 18:28:29 +02:00
Ihar Katkavets ef3625cfd1 main Update tests 2023-08-26 18:28:14 +02:00
Ihar Katkavets e6ccf4371f Update GzipOutputStream.md 2023-08-26 18:28:00 +02:00
Ihar Katkavets 0c84995793 Update README.md 2023-08-26 17:50:05 +02:00
Ihar Katkavets 53b0ff8665 main Merge branch 'main' of github.com:iharkatkavets/StreamKit 2023-08-26 17:34:11 +02:00
Ihar Katkavets 6c786edae8 main Make some constants public, add tests to cover TwoFish encryting 2023-08-26 17:33:40 +02:00
Ihar Katkavets 7acd623e0e Update README.md 2023-08-26 17:33:12 +02:00
Ihar Katkavets 87c222daa3 Update README.md 2023-08-26 17:31:34 +02:00
Ihar Katkavets 7547213dfc Update README.md 2023-08-26 17:30:39 +02:00
Ihar Katkavets 5404afa248 Update README.md 2023-08-26 17:24:22 +02:00
Ihar Katkavets e20fb9d9f9 Update README.md 2023-08-26 16:58:47 +02:00
10 changed files with 386 additions and 34 deletions
+28 -4
View File
@@ -1,8 +1,32 @@
The `windowBits` parameter is the base two logarithm of the window size
(the size of the history buffer). It should be in the range 8..15 for this
version of the library. Larger values of this parameter result in better
compression at the expense of memory usage.
# GzipOutputStream
Compress the data and write to a file
```swift
...
let compressStream = GzipOutputStream(writingTo: fileOutputStream)
try compressStream.open()
try compressStream.write("Hello world", ofEncoding: .utf8)
try! compressStream.close()
...
```
# GzipInputStream
Decompress the data
```swift
...
let decompressStream = GzipInputStream(readingFrom: fileInputStream)
try decompressStream.open()
var result = Array<UInt8>()
let tmpBufLen = 1<<16
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufLen)
while decompressStream.hasBytesAvailable {
let readLen = try decompressStream.read(&tmpBuffer, maxLength: tmpBufLen)
result.append(contentsOf: tmpBuffer.prefix(readLen))
}
...
```
# Configuration parameters
`windowBits` is used to control the compression level and the format of the
compressed data. According to the zlib manual, the windowBits parameter can
have the following values:
+177 -2
View File
@@ -1,10 +1,185 @@
# StreamKit
This open-source Swift library offers a comprehensive collection of cryptographic algorithms. These ciphers can be structured into chains, facilitating the seamless flow of output from one cipher stream to another. This architecture enables concurrent tasks, such as encrypting data while writing the encrypted result to a file. Integrate "StreamKit" into your projects to efficiently utilize these cryptographic functionalities.
# The available streams
- [FileStream](FileStream.md)
- [GzipStream](GzipStream.md)
- [AesStream](AesStream.md)
- [Salsa20Stream](Salsa20Stream.md)
- [ChaCha20Stream](ChaCha20Stream.md)
- [TwoFishStream](TwoFishStream.md)
# How to add the library to a project
In the Xcode press `File` -> `Add Packages` -> In the search field insert `https://github.com/iharkatkavets/StreamKit.git`.
It might require to setup `GitHub Account` in the Xcode
# How to use the library in the project
First import the library
```swift
import StreamKit
```
## How to read a file using `FileInputStream`
```swift
let fileURL: URL = ...
guard let fileInputStream = FileInputStream(with: fileURL) else {
return
}
try fileInputStream.open()
let fileContent: Data = try fileInputStream.readToEnd()
// or read chunk by chunk
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while fileInputStream.hasBytesAvailable {
let readLen = fileInputStream.read(&tmpBuffer, maxLength: tmpBufferLen)
// process tmpBuffer
}
inputFileStream.close()
```
## How to write to a file using `FileOutputStream`
```swift
let fileURL: URL = ...
guard let fileOutputStream = FileOutputStream(with: fileURL) else {
return
}
try fileOutputStream.open()
// write string to a file
try fileOutputStream.write("Hello world", ofEncoding: .utf8)
// or write data to a file
try fileOutputStream.write(Data([0,1,2,3]))
// or write some buffer to a file
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
try compressingStream.write(tmpBuffer, length: readLen)
try fileOutputStream.close()
```
## How to perform encrypting using `AesOutputStream`
```swift
...
let encryptingStream = AesOutputStream(writingTo: fileOutputStream,
key: key,
iv: iv)
try encryptingStream.open()
try encryptingStream.write("Hello world", ofEncoding: .utf8)
try encryptingStream.close()
...
```
## How to perform decrypting using `AesInputStream`
```swift
...
let decryptingStream = AesInputStream(readingFrom: inputFileStream,
key: key,
iv: iv)
try decryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while decryptingStream.hasBytesAvailable {
let readLen = try decryptingStream.read(&tmpBuffer, maxLength: tmpBufferLen)
// process buffer
}
decryptingStream.close()
...
```
## How to perform encrypting using `Salsa20OutputStream`
```swift
...
let encryptingStream = Salsa20OutputStream(writingTo: fileOutputStream,
key: key,
iv: iv)
try encryptingStream.open()
try encryptingStream.write("Hello world", ofEncoding: .utf8)
try encryptingStream.close()
...
```
## How to perform decrypting using `Salsa20InputStream`
```swift
...
let decryptingStream = Salsa20InputStream(readingFrom: inputFileStream,
key: key,
iv: iv)
try decryptingStream.open()
let decryptedData = try decryptingStream.readToEnd()
decryptingStream.close()
...
```
## How to perform encrypting using `ChaCha20OutputStream`
```swift
...
let encryptingStream = ChaCha20OutputStream(writingTo: fileOutputStream,
key: key,
iv: iv)
try encryptingStream.open()
try encryptingStream.write("Hello world", ofEncoding: .utf8)
try encryptingStream.close()
...
```
## How to perform decrypting using `ChaCha20InputStream`
```swift
...
let decryptingStream = ChaCha20InputStream(readingFrom: inputFileStream,
key: key,
iv: iv)
try decryptingStream.open()
let decryptedData = try decryptingStream.readToEnd()
decryptingStream.close()
...
```
## How to perform encrypting using `TwoFishOutputStream`
```swift
...
let encryptingStream = TwoFishOutputStream(writingTo: fileOutputStream,
key: key,
iv: iv)
try encryptingStream.open()
try encryptingStream.write("Hello world", ofEncoding: .utf8)
try encryptingStream.close()
...
```
## How to perform decrypting using `TwoFishInputStream`
```swift
...
let decryptingStream = TwoFishInputStream(readingFrom: inputFileStream,
key: key,
iv: iv)
try decryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while decryptingStream.hasBytesAvailable {
let readLen = try decompressingStream.read(&tmpBuffer, maxLength: tmpBufferLen)
// process or store bytes in `tmpBuffer`
}
decryptingStream.close()
...
```
# How to use it
For example it's possible to encrypt some data and simulteniously write the encrypted result to a file.
@@ -56,5 +231,5 @@ inputFileStream.close()
```
### Sponsored by [KeePassium](https://github.com/keepassium)
# Sponsorship
### The library was developed in collaboration with [KeePassium](https://github.com/keepassium)
+1 -2
View File
@@ -41,7 +41,6 @@ public final class AesInputStream: InputStream {
private var decryptedBufferAvailableLen: Int
private var status: Int32 = 0
private let options: AesOptions
private let blockSize = 16
public init(readingFrom nestedStream: InputStream,
key: [UInt8],
@@ -72,7 +71,7 @@ public final class AesInputStream: InputStream {
guard !isOpen else { fatalError("The stream can be opened only once") }
isOpen = true
guard iv.count == blockSize else {
guard iv.count == AesIVSize else {
throw AesStreamError(kind: .ivSizeError)
}
+1 -2
View File
@@ -38,7 +38,6 @@ public final class AesOutputStream: OutputStream {
private var isOpen = false
private var status: Int32 = 0
private let options: AesOptions
private let blockSize = 16
public init(writingTo outputStream: OutputStream,
key: [UInt8],
@@ -66,7 +65,7 @@ public final class AesOutputStream: OutputStream {
guard !isOpen else { fatalError("The stream can be opened only once") }
isOpen = true
guard iv.count == blockSize else {
guard iv.count == AesIVSize else {
throw AesStreamError(kind: .ivSizeError)
}
+2
View File
@@ -26,6 +26,8 @@
import Foundation
import CommonCrypto
public let AesIVSize = 16
public typealias AesOptions = Int
public extension AesOptions {
static let CBCMode = 0
+2 -2
View File
@@ -25,8 +25,8 @@
import Foundation
let ChaCha20IVSize = 12
let ChaCha20KeySize = 32
public let ChaCha20KeySize = 32
public let ChaCha20IVSize = 12
public struct ChaCha20StreamError: LocalizedError {
public enum Kind {
+3 -3
View File
@@ -25,9 +25,9 @@
import Foundation
let Salsa20IVSize = 8
let Salsa20KeySize128 = 16
let Salsa20KeySize256 = 32
public let Salsa20KeySize128 = 16
public let Salsa20KeySize256 = 32
public let Salsa20IVSize = 8
public struct Salsa20StreamError: LocalizedError {
public enum Kind {
+1 -1
View File
@@ -25,7 +25,7 @@
import Foundation
let TwoFishIVSize = 16
public let TwoFishIVSize = 16
public struct TwoFishStreamError: LocalizedError {
public enum Kind {
+4 -4
View File
@@ -108,11 +108,11 @@ extension GzipStreamTests {
try decompressStream.open()
var result = Array<UInt8>()
let bufLen = 1<<16
var readBuffer = Array<UInt8>(repeating: 0, count: bufLen)
let tmpBufLen = 1<<16
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufLen)
while decompressStream.hasBytesAvailable {
let readLen = try decompressStream.read(&readBuffer, maxLength: bufLen)
result.append(contentsOf: readBuffer.prefix(readLen))
let readLen = try decompressStream.read(&tmpBuffer, maxLength: tmpBufLen)
result.append(contentsOf: tmpBuffer.prefix(readLen))
}
return result
}
+167 -14
View File
@@ -41,7 +41,7 @@ final class StreamKitTests: XCTestCase {
func testEncryptingDecrypting1MBFileUsingSalsa20Streams() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(8)
let iv = genBufferOfLen(Salsa20IVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try encryptFileUsingSalsa20(originalFileURL, key, iv)
@@ -53,7 +53,7 @@ final class StreamKitTests: XCTestCase {
func testCompressEncryptDecryptDecompress1MBFileUsingSalsa20Stream() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(8)
let iv = genBufferOfLen(Salsa20IVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try compressAndEncryptFileUsingSalsa20(originalFileURL, key, iv)
@@ -63,9 +63,21 @@ final class StreamKitTests: XCTestCase {
XCTAssertEqual(md5(originalFileURL), md5(decryptedFileURL))
}
func testCompressEncryptDecryptDecompressPlainTextFileUsingSalsa20Stream() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(Salsa20IVSize)
let originalFileURL = fileURL("PlainText")!
let encryptedFileURL = try compressAndEncryptFileUsingSalsa20(originalFileURL, key, iv)
let decryptedFileURL = try decryptAndDecompressFileUsingSalsa20(encryptedFileURL, key, iv)
XCTAssertNotEqual(md5(originalFileURL), md5(encryptedFileURL))
XCTAssertEqual(md5(originalFileURL), md5(decryptedFileURL))
}
func testEncryptingDecrypting1MBFileUsingChaCha20Streams() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(12)
let iv = genBufferOfLen(ChaCha20IVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try encryptFileUsingChaCha20(originalFileURL, key, iv)
@@ -77,7 +89,7 @@ final class StreamKitTests: XCTestCase {
func testCompressEncryptDecryptDecompress1MBFileUsingChaCha20Stream() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(12)
let iv = genBufferOfLen(ChaCha20IVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try compressAndEncryptFileUsingChaCha20(originalFileURL, key, iv)
@@ -89,7 +101,7 @@ final class StreamKitTests: XCTestCase {
func testEncryptingDecrypting1MBFileUsingAesStream() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(16)
let iv = genBufferOfLen(AesIVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try encryptFileUsingAes(originalFileURL, key, iv)
@@ -101,7 +113,7 @@ final class StreamKitTests: XCTestCase {
func testCompressEncryptDecryptDecompress1MBFileUsingAesStream() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(16)
let iv = genBufferOfLen(AesIVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try compressAndEncryptFileUsingAes(originalFileURL, key, iv)
@@ -128,6 +140,31 @@ final class StreamKitTests: XCTestCase {
XCTAssertNotEqual(md5(originalFileURL), md5(compressedFileURL))
XCTAssertEqual(md5(originalFileURL), md5(decompressedFileURL))
}
func testEncryptingDecrypting1MBFileUsingTwoFishStreams() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(TwoFishIVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try encryptFileUsingTwoFish(originalFileURL, key, iv)
let decryptedFileURL = try decryptFileUsingTwoFish(encryptedFileURL, key, iv)
XCTAssertNotEqual(md5(originalFileURL), md5(encryptedFileURL))
XCTAssertEqual(md5(originalFileURL), md5(decryptedFileURL))
}
func testCompressEncryptDecryptDecompress1MBFileUsingTwoFishStream() throws {
let key = genBufferOfLen(32)
let iv = genBufferOfLen(TwoFishIVSize)
let originalFileURL = fileURL("1MB")!
let encryptedFileURL = try compressAndEncryptFileUsingTwoFish(originalFileURL, key, iv)
let decryptedFileURL = try decryptAndDecompressFileUsingTwoFish(encryptedFileURL, key, iv)
XCTAssertNotEqual(md5(originalFileURL), md5(encryptedFileURL))
XCTAssertEqual(md5(originalFileURL), md5(decryptedFileURL))
}
}
extension StreamKitTests {
@@ -143,7 +180,7 @@ extension StreamKitTests {
key: key,
iv: iv)
try encryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -176,7 +213,7 @@ extension StreamKitTests {
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while decryptingStream.hasBytesAvailable {
while decompressingStream.hasBytesAvailable {
let readLen = try decompressingStream.read(&tmpBuffer, maxLength: tmpBufferLen)
try outputFileStream.write(tmpBuffer, length: readLen)
}
@@ -204,7 +241,7 @@ extension StreamKitTests {
let compressingStream = GzipOutputStream(writingTo: encryptingStream)
try compressingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -258,7 +295,7 @@ extension StreamKitTests {
key: key,
iv: iv)
try encryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -315,7 +352,7 @@ extension StreamKitTests {
let compressingStream = GzipOutputStream(writingTo: encryptingStream)
try compressingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -373,7 +410,7 @@ extension StreamKitTests {
key: key,
iv: iv)
try encryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -404,7 +441,7 @@ extension StreamKitTests {
let compressingStream = GzipOutputStream(writingTo: encryptingStream)
try compressingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -485,7 +522,7 @@ extension StreamKitTests {
let compressingStream = GzipOutputStream(writingTo: outputFileStream)
try compressingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
@@ -523,4 +560,120 @@ extension StreamKitTests {
inputFileStream.close()
return decompressedFileURL
}
func encryptFileUsingTwoFish(_ originalFileURL: URL, _ key: [UInt8], _ iv: [UInt8]) throws -> URL {
let inputFileStream = FileInputStream(with: try! FileHandle(forReadingFrom: originalFileURL))
try inputFileStream.open()
let encryptedFileURL = createTmpFileURL(Self.tmpDir)
let outputFileStream = FileOutputStream(with: try! FileHandle(forWritingTo: encryptedFileURL))
try outputFileStream.open()
let encryptingStream = TwoFishOutputStream(writingTo: outputFileStream,
key: key,
iv: iv)
try encryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
let readLen = inputFileStream.read(&tmpBuffer, maxLength: tmpBufferLen)
try encryptingStream.write(tmpBuffer, length: readLen)
}
try encryptingStream.close()
try outputFileStream.close()
inputFileStream.close()
return encryptedFileURL
}
func decryptFileUsingTwoFish(_ encryptedFileURL: URL, _ key: [UInt8], _ iv: [UInt8]) throws -> URL {
let inputFileStream = FileInputStream(with: try! FileHandle(forReadingFrom: encryptedFileURL))
try inputFileStream.open()
let decryptedFileURL = createTmpFileURL(Self.tmpDir)
let outputFileStream = FileOutputStream(with: try! FileHandle(forWritingTo: decryptedFileURL))
try outputFileStream.open()
let decryptingStream = TwoFishInputStream(readingFrom: inputFileStream,
key: key,
iv: iv)
try decryptingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while decryptingStream.hasBytesAvailable {
let readLen = try decryptingStream.read(&tmpBuffer, maxLength: tmpBufferLen)
try outputFileStream.write(tmpBuffer, length: readLen)
}
decryptingStream.close()
try outputFileStream.close()
inputFileStream.close()
return decryptedFileURL
}
func compressAndEncryptFileUsingTwoFish(_ originalFileURL: URL, _ key: [UInt8], _ iv: [UInt8]) throws -> URL {
let inputFileStream = FileInputStream(with: try! FileHandle(forReadingFrom: originalFileURL))
try inputFileStream.open()
inputFileStream.close()
let encryptedFileURL = createTmpFileURL(Self.tmpDir)
let outputFileStream = FileOutputStream(with: try! FileHandle(forWritingTo: encryptedFileURL))
try outputFileStream.open()
let encryptingStream = TwoFishOutputStream(writingTo: outputFileStream,
key: key,
iv: iv)
try encryptingStream.open()
let compressingStream = GzipOutputStream(writingTo: encryptingStream)
try compressingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while inputFileStream.hasBytesAvailable {
let readLen = inputFileStream.read(&tmpBuffer, maxLength: tmpBufferLen)
try compressingStream.write(tmpBuffer, length: readLen)
}
try compressingStream.close()
try encryptingStream.close()
try outputFileStream.close()
inputFileStream.close()
return encryptedFileURL
}
func decryptAndDecompressFileUsingTwoFish(_ encryptedFileURL: URL, _ key: [UInt8], _ iv: [UInt8]) throws -> URL {
let inputFileStream = FileInputStream(with: try! FileHandle(forReadingFrom: encryptedFileURL))
try inputFileStream.open()
let decryptedFileURL = createTmpFileURL(Self.tmpDir)
let outputFileStream = FileOutputStream(with: try! FileHandle(forWritingTo: decryptedFileURL))
try outputFileStream.open()
let decryptingStream = TwoFishInputStream(readingFrom: inputFileStream,
key: key,
iv: iv)
try decryptingStream.open()
let decompressingStream = GzipInputStream(readingFrom: decryptingStream)
try decompressingStream.open()
let tmpBufferLen = 1<<16 // 65KB buffer
var tmpBuffer = Array<UInt8>(repeating: 0, count: tmpBufferLen)
while decryptingStream.hasBytesAvailable {
let readLen = try decompressingStream.read(&tmpBuffer, maxLength: tmpBufferLen)
try outputFileStream.write(tmpBuffer, length: readLen)
}
decompressingStream.close()
decryptingStream.close()
try outputFileStream.close()
inputFileStream.close()
return decryptedFileURL
}
}