Skip to content

Commit 2ff612b

Browse files
authored
Merge pull request #106 from iosdevzone/clarify-iv
Improved `StreamCryptor` demo showing IV handling.
2 parents 1c6e0ad + 8d068fe commit 2ff612b

File tree

4 files changed

+157
-46
lines changed

4 files changed

+157
-46
lines changed

README.md

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,17 @@ assert(hmacs5! == expectedRFC2202)
104104
## Using `Cryptor`
105105

106106
```swift
107+
let algorithm = Cryptor.Algorithm.aes
108+
var iv = try! Random.generateBytes(byteCount: algorithm.blockSize())
107109
var key = arrayFrom(hexString: "2b7e151628aed2a6abf7158809cf4f3c")
108110
var plainText = "The quick brown fox jumps over the lazy dog. The fox has more or less had it at this point."
109111

110-
var cryptor = Cryptor(operation:.encrypt, algorithm:.aes, options:.PKCS7Padding, key:key, iv:Array<UInt8>())
112+
var cryptor = Cryptor(operation:.encrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:iv)
111113
var cipherText = cryptor.update(plainText)?.final()
112114

113-
cryptor = Cryptor(operation:.decrypt, algorithm:.aes, options:.PKCS7Padding, key:key, iv:Array<UInt8>())
115+
cryptor = Cryptor(operation:.decrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:iv)
114116
var decryptedPlainText = cryptor.update(cipherText!)?.final()
115-
var decryptedString = decryptedPlainText!.reduce("") { $0 + String(UnicodeScalar($1)) }
117+
var decryptedString = String(bytes: decryptedPlainText!, encoding: .utf8)
116118
decryptedString
117119
assert(decryptedString == plainText)
118120
```
@@ -131,57 +133,105 @@ To encrypt a large file or a network stream use `StreamCryptor`. The `StreamCryp
131133

132134
The example below shows how to use `StreamCryptor` to encrypt and decrypt an image file.
133135
```swift
134-
func crypt(sc : StreamCryptor, inputStream: NSInputStream, outputStream: NSOutputStream, bufferSize: Int)
136+
func crypt(sc : StreamCryptor, inputStream: InputStream, outputStream: OutputStream, bufferSize: Int) -> (bytesRead: Int, bytesWritten: Int)
135137
{
136-
var inputBuffer = Array<UInt8>(count:1024, repeatedValue:0)
137-
var outputBuffer = Array<UInt8>(count:1024, repeatedValue:0)
138-
inputStream.open()
139-
outputStream.open()
138+
var inputBuffer = Array<UInt8>(repeating:0, count:1024)
139+
var outputBuffer = Array<UInt8>(repeating:0, count:1024)
140140

141-
var cryptedBytes : UInt = 0
141+
142+
var cryptedBytes : Int = 0
143+
var totalBytesWritten = 0
144+
var totalBytesRead = 0
142145
while inputStream.hasBytesAvailable
143146
{
144147
let bytesRead = inputStream.read(&inputBuffer, maxLength: inputBuffer.count)
145-
let status = sc.update(inputBuffer, byteCountIn: UInt(bytesRead), bufferOut: &outputBuffer, byteCapacityOut: UInt(outputBuffer.count), byteCountOut: &cryptedBytes)
146-
assert(status == Status.Success)
148+
totalBytesRead += bytesRead
149+
let status = sc.update(bufferIn: inputBuffer, byteCountIn: bytesRead, bufferOut: &outputBuffer, byteCapacityOut: outputBuffer.count, byteCountOut: &cryptedBytes)
150+
assert(status == Status.success)
147151
if(cryptedBytes > 0)
148152
{
149153
let bytesWritten = outputStream.write(outputBuffer, maxLength: Int(cryptedBytes))
150154
assert(bytesWritten == Int(cryptedBytes))
155+
totalBytesWritten += bytesWritten
151156
}
152157
}
153-
let status = sc.final(&outputBuffer, byteCapacityOut: UInt(outputBuffer.count), byteCountOut: &cryptedBytes)
154-
assert(status == Status.Success)
158+
let status = sc.final(bufferOut: &outputBuffer, byteCapacityOut: outputBuffer.count, byteCountOut: &cryptedBytes)
159+
assert(status == Status.success)
155160
if(cryptedBytes > 0)
156161
{
157162
let bytesWritten = outputStream.write(outputBuffer, maxLength: Int(cryptedBytes))
158163
assert(bytesWritten == Int(cryptedBytes))
164+
totalBytesWritten += bytesWritten
159165
}
160-
inputStream.close()
161-
outputStream.close()
166+
return (totalBytesRead, totalBytesWritten)
162167
}
163168

164-
let imagePath = NSBundle.mainBundle().pathForResource("Riscal", ofType:"jpg")!
165-
let tmp = NSTemporaryDirectory()
166-
let encryptedFilePath = tmp.stringByAppendingPathComponent("Riscal.xjpgx")
167-
var decryptedFilePath = tmp.stringByAppendingPathComponent("RiscalDecrypted.jpg")
169+
let imagePath = Bundle.main.path(forResource: "Riscal", ofType:"jpg")!
170+
let tmp = NSTemporaryDirectory() as NSString
171+
let encryptedFilePath = "\(tmp)/Riscal.xjpgx"
172+
var decryptedFilePath = "\(tmp)/RiscalDecrypted.jpg"
173+
174+
// Prepare the input and output streams for the encryption operation
175+
guard let imageInputStream = InputStream(fileAtPath: imagePath) else {
176+
fatalError("Failed to initialize the image input stream.")
177+
}
178+
imageInputStream.open()
179+
guard let encryptedFileOutputStream = OutputStream(toFileAtPath: encryptedFilePath, append:false) else
180+
{
181+
fatalError("Failed to open output stream.")
182+
}
183+
encryptedFileOutputStream.open()
168184

169-
var imageInputStream = NSInputStream(fileAtPath: imagePath)
170-
var encryptedFileOutputStream = NSOutputStream(toFileAtPath: encryptedFilePath, append:false)
171-
var encryptedFileInputStream = NSInputStream(fileAtPath: encryptedFilePath)
172-
var decryptedFileOutputStream = NSOutputStream(toFileAtPath: decryptedFilePath, append:false)
185+
// Generate a new, random initialization vector
186+
let initializationVector = try! Random.generateBytes(byteCount: algorithm.blockSize())
173187

174-
var sc = StreamCryptor(operation:.encrypt, algorithm:.aes, options:.PKCS7Padding, key:key, iv:Array<UInt8>())
175-
crypt(sc, imageInputStream, encryptedFileOutputStream, 1024)
188+
// A common way to communicate the initialization vector is to write it at the beginning of the encrypted data.
189+
let bytesWritten = encryptedFileOutputStream.write(initializationVector, maxLength: initializationVector.count)
190+
191+
// Now write the encrypted data
192+
var sc = StreamCryptor(operation:.encrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:initializationVector)
193+
guard bytesWritten == initializationVector.count else
194+
{
195+
fatalError("Failed to write initialization vector to encrypted output file.")
196+
}
197+
let outputResult = crypt(sc: sc, inputStream: imageInputStream, outputStream: encryptedFileOutputStream, bufferSize: 1024)
198+
encryptedFileOutputStream.close()
199+
outputResult
176200

177201
// Uncomment this line to verify that the file is encrypted
178-
//var encryptedImage = UIImage(contentsOfFile:encryptedFile)
202+
//var encryptedImage = NSImage(contentsOfFile:encryptedFile)
203+
204+
// Prepare the input and output streams for the decryption operation
205+
guard let encryptedFileInputStream = InputStream(fileAtPath: encryptedFilePath) else
206+
{
207+
fatalError("Failed to open the encrypted file for input.")
208+
}
209+
encryptedFileInputStream.open()
210+
guard let decryptedFileOutputStream = OutputStream(toFileAtPath: decryptedFilePath, append:false) else {
211+
fatalError("Failed to open the file for the decrypted output file.")
212+
}
213+
decryptedFileOutputStream.open()
214+
215+
// Read back the initialization vector.
216+
var readbackInitializationVector = Array<UInt8>(repeating: 0, count: algorithm.blockSize())
217+
let bytesRead = encryptedFileInputStream.read(&readbackInitializationVector, maxLength: readbackInitializationVector.count)
218+
219+
// Uncomment this to verify that we did indeed read back the initialization vector.
220+
//assert(readbackInitializationVector == initializationVector)
221+
222+
// Now use the read back initialization vector (along with the key) to
223+
sc = StreamCryptor(operation:.decrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:readbackInitializationVector)
224+
let inputResult = crypt(sc: sc, inputStream: encryptedFileInputStream, outputStream: decryptedFileOutputStream, bufferSize: 1024)
225+
226+
// Uncomment this to verify that decrypt operation consumed all the encrypted data
227+
// and produced the correct output of plaintext output.
228+
//assert(inputResult.bytesRead == outputResult.bytesWritten && inputResult.bytesWritten == outputResult.bytesRead)
229+
230+
var image = NSImage(named:"Riscal.jpg")
231+
var decryptedImage = NSImage(contentsOfFile:decryptedFilePath)
232+
decryptedImage
179233

180-
sc = StreamCryptor(operation:.decrypt, algorithm:.aes, options:.PKCS7Padding, key:key, iv:Array<UInt8>())
181-
crypt(sc, encryptedFileInputStream, decryptedFileOutputStream, 1024)
182234

183-
var image = UIImage(named:"Riscal.jpg")
184-
var decryptedImage = UIImage(contentsOfFile:decryptedFilePath)
185235
```
186236

187237
## Using `PBKDF`

README.playground/Contents.swift

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -137,23 +137,26 @@ assert(decryptedString == plainText)
137137

138138
The example below shows how to use `StreamCryptor` to encrypt and decrypt an image file.
139139
*/
140-
func crypt(sc : StreamCryptor, inputStream: InputStream, outputStream: OutputStream, bufferSize: Int)
140+
func crypt(sc : StreamCryptor, inputStream: InputStream, outputStream: OutputStream, bufferSize: Int) -> (bytesRead: Int, bytesWritten: Int)
141141
{
142142
var inputBuffer = Array<UInt8>(repeating:0, count:1024)
143143
var outputBuffer = Array<UInt8>(repeating:0, count:1024)
144-
inputStream.open()
145-
outputStream.open()
146144

147-
var cryptedBytes : Int = 0
145+
146+
var cryptedBytes : Int = 0
147+
var totalBytesWritten = 0
148+
var totalBytesRead = 0
148149
while inputStream.hasBytesAvailable
149150
{
150151
let bytesRead = inputStream.read(&inputBuffer, maxLength: inputBuffer.count)
152+
totalBytesRead += bytesRead
151153
let status = sc.update(bufferIn: inputBuffer, byteCountIn: bytesRead, bufferOut: &outputBuffer, byteCapacityOut: outputBuffer.count, byteCountOut: &cryptedBytes)
152154
assert(status == Status.success)
153155
if(cryptedBytes > 0)
154156
{
155157
let bytesWritten = outputStream.write(outputBuffer, maxLength: Int(cryptedBytes))
156158
assert(bytesWritten == Int(cryptedBytes))
159+
totalBytesWritten += bytesWritten
157160
}
158161
}
159162
let status = sc.final(bufferOut: &outputBuffer, byteCapacityOut: outputBuffer.count, byteCountOut: &cryptedBytes)
@@ -162,34 +165,77 @@ func crypt(sc : StreamCryptor, inputStream: InputStream, outputStream: OutputSt
162165
{
163166
let bytesWritten = outputStream.write(outputBuffer, maxLength: Int(cryptedBytes))
164167
assert(bytesWritten == Int(cryptedBytes))
168+
totalBytesWritten += bytesWritten
165169
}
166-
inputStream.close()
167-
outputStream.close()
170+
return (totalBytesRead, totalBytesWritten)
168171
}
169172

170173
let imagePath = Bundle.main.path(forResource: "Riscal", ofType:"jpg")!
171174
let tmp = NSTemporaryDirectory() as NSString
172175
let encryptedFilePath = "\(tmp)/Riscal.xjpgx"
173176
var decryptedFilePath = "\(tmp)/RiscalDecrypted.jpg"
174177

175-
var imageInputStream = InputStream(fileAtPath: imagePath)
176-
var encryptedFileOutputStream = OutputStream(toFileAtPath: encryptedFilePath, append:false)
177-
var encryptedFileInputStream = InputStream(fileAtPath: encryptedFilePath)
178-
var decryptedFileOutputStream = OutputStream(toFileAtPath: decryptedFilePath, append:false)
178+
// Prepare the input and output streams for the encryption operation
179+
guard let imageInputStream = InputStream(fileAtPath: imagePath) else {
180+
fatalError("Failed to initialize the image input stream.")
181+
}
182+
imageInputStream.open()
183+
guard let encryptedFileOutputStream = OutputStream(toFileAtPath: encryptedFilePath, append:false) else
184+
{
185+
fatalError("Failed to open output stream.")
186+
}
187+
encryptedFileOutputStream.open()
179188

180189
// Generate a new, random initialization vector
181-
iv = try! Random.generateBytes(byteCount: algorithm.blockSize())
182-
var sc = StreamCryptor(operation:.encrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:iv)
183-
crypt(sc: sc, inputStream: imageInputStream!, outputStream: encryptedFileOutputStream!, bufferSize: 1024)
190+
let initializationVector = try! Random.generateBytes(byteCount: algorithm.blockSize())
191+
192+
// A common way to communicate the initialization vector is to write it at the beginning of the encrypted data.
193+
let bytesWritten = encryptedFileOutputStream.write(initializationVector, maxLength: initializationVector.count)
194+
195+
// Now write the encrypted data
196+
var sc = StreamCryptor(operation:.encrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:initializationVector)
197+
guard bytesWritten == initializationVector.count else
198+
{
199+
fatalError("Failed to write initialization vector to encrypted output file.")
200+
}
201+
let outputResult = crypt(sc: sc, inputStream: imageInputStream, outputStream: encryptedFileOutputStream, bufferSize: 1024)
202+
encryptedFileOutputStream.close()
203+
outputResult
184204

185205
// Uncomment this line to verify that the file is encrypted
186-
//var encryptedImage = UIImage(contentsOfFile:encryptedFile)
206+
//var encryptedImage = NSImage(contentsOfFile:encryptedFile)
187207

188-
sc = StreamCryptor(operation:.decrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:iv)
189-
crypt(sc: sc, inputStream: encryptedFileInputStream!, outputStream: decryptedFileOutputStream!, bufferSize: 1024)
208+
// Prepare the input and output streams for the decryption operation
209+
guard let encryptedFileInputStream = InputStream(fileAtPath: encryptedFilePath) else
210+
{
211+
fatalError("Failed to open the encrypted file for input.")
212+
}
213+
encryptedFileInputStream.open()
214+
guard let decryptedFileOutputStream = OutputStream(toFileAtPath: decryptedFilePath, append:false) else {
215+
fatalError("Failed to open the file for the decrypted output file.")
216+
}
217+
decryptedFileOutputStream.open()
218+
219+
// Read back the initialization vector.
220+
var readbackInitializationVector = Array<UInt8>(repeating: 0, count: algorithm.blockSize())
221+
let bytesRead = encryptedFileInputStream.read(&readbackInitializationVector, maxLength: readbackInitializationVector.count)
222+
223+
// Uncomment this to verify that we did indeed read back the initialization vector.
224+
//assert(readbackInitializationVector == initializationVector)
225+
226+
// Now use the read back initialization vector (along with the key) to
227+
sc = StreamCryptor(operation:.decrypt, algorithm:algorithm, options:.PKCS7Padding, key:key, iv:readbackInitializationVector)
228+
let inputResult = crypt(sc: sc, inputStream: encryptedFileInputStream, outputStream: decryptedFileOutputStream, bufferSize: 1024)
229+
230+
// Uncomment this to verify that decrypt operation consumed all the encrypted data
231+
// and produced the correct output of plaintext output.
232+
//assert(inputResult.bytesRead == outputResult.bytesWritten && inputResult.bytesWritten == outputResult.bytesRead)
190233

191234
var image = NSImage(named:"Riscal.jpg")
192235
var decryptedImage = NSImage(contentsOfFile:decryptedFilePath)
236+
decryptedImage
237+
238+
193239
/*:
194240

195241
## Using `PBKDF`

README.playground/playground.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

0 commit comments

Comments
 (0)