-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathViewController.swift
More file actions
executable file
·343 lines (288 loc) · 14 KB
/
ViewController.swift
File metadata and controls
executable file
·343 lines (288 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
//
// ViewController.swift
// SwiftVideoPlayer
//
// Created by Ivo Vacek on 05/05/16.
// Copyright © 2016 Ivo Vacek. All rights reserved.
//
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController, AVPlayerViewControllerDelegate, AVAssetResourceLoaderDelegate {
var videoUrl: NSURL!
var videoAsset: AVURLAsset!
let queue = dispatch_queue_create("delegate", DISPATCH_QUEUE_SERIAL)
var key: Rabbit.Key?
var nonce: Rabbit.IV?
var decryptor: ([UInt8]->[UInt8])?
let nonceSize = Int64(sizeof(Rabbit.IV.self))
var blockNumber: Int64 = 0 // current block to process
var blockSize: Int64 = 0 // size of (nonce + payload)
var headerSize: Int64 = 0 // size of (headerSizeInfo + blockSizeInfo + metaData)
var blockCount: Int64 = 0 // must be calculated and checked
@IBOutlet weak var playEncryptedButton: UIButton!
@IBOutlet weak var log: UITextView!
@IBAction func playVideo(sender: UIButton) {
// free sample from from http://www.sample-videos.com
videoUrl = NSBundle.mainBundle().URLForResource("big_buck_bunny_360p_50mb", withExtension: "mp4")!
performSegueWithIdentifier("seguePlayVideo", sender: self)
}
@IBAction func encrypt(sender: UIButton) {
self.playEncryptedButton.enabled = false
// clear log
self.log.text = ""
// WARNING !!! Don't do this in main thread
let queue = dispatch_queue_create("encrypt", DISPATCH_QUEUE_SERIAL)
dispatch_async(queue) {
func log(txt: String) {
dispatch_async(dispatch_get_main_queue()) {
self.log.text.appendContentsOf(txt)
self.log.font = UIFont(name: "Menlo-Bold", size: 11.0)!
let c = self.log.text.characters.count
if c > 0 {
let bottom = NSMakeRange(c - 1, 1)
self.log.scrollRangeToVisible(bottom)
}
}
}
// encryption 'secret'
let key: Rabbit.Key = s2K("0F62B5085BAE0154A7FA4DA0F34699EC")
var encrypted = NSHomeDirectory()
encrypted.appendContentsOf("/Documents/encrypted.dat")
// free sample for testing from http://www.sample-videos.com
//
// (1) encryption time depends on size of the file (it is really fast and smooth proces)
// (2) size and content of a file which you would like to encrypt
// is limited only by your time and disk space :-)
// (3) 3 GB full HD video could be encrypted and then played (real time decription)
// very easy on mac book late 2008 simulator or first iPad mini retina
// (4) use release builds for performance testing and working with big files!
// (5) for debugging purpose use smal sized video and small block size
let normal = NSBundle.mainBundle().pathForResource("big_buck_bunny_360p_50mb", ofType: "mp4")!
let fr = FStream(path: normal, mode: "r")
let fw = FStream(path: encrypted, mode: "w")
// all data in container are in big endian (network) byte order
//
// offset | size | data
// ------------------------------------------------------------
// 0 | 2 | 16 bit header size (HS)
// 2 | 3 | 24 bit block size (BS)
// 5 | any | meta data, actual size is header size - 5
// -------------------------------------------------------------
// HS | 16 | nonce (initial vector for stream decoder)
// HS+16 | BS-16 | any pay load data (encrypted)
// -------------------------------------------------------------
// ..... others blocks as necesary
// -------------------------------------------------------------
// N*BS+5 | 16 | last nonce
// N*BS+21| LBS | LBS (last block size) 0..<(BS-16)
// EOF
//
// NOTE: (1) N represent number of blocks
// (2) all blocks (except tha last one) have the same size
// (3) only payload data are encrypted
let metaData = Array("some meta data".utf8)
// NOTE: (1) max blockSize is 2^24 - 1 (16 MB)
// (2) optimal value depends on your needs
// (3) it is NOT critical parameter, general value cca 1MB will almost work
// (4) very small values increase to much resulting container
// and could lead to unplayable video (you can still decrypt it)
// TIP: use small BS when debugging!
let blockSize = 1024 * 1024 // 1024 KB
let blockSizeData = [
UInt8(blockSize & 0xff),
UInt8((blockSize & 0xff00) >> 8),
UInt8((blockSize & 0xff0000) >> 16)
]
let headerSize = 2 + blockSizeData.count + metaData.count
let headerSizeData = [
UInt8(headerSize & 0xff),
UInt8((headerSize & 0xff00) >> 8)
]
// write header, not encrypted
guard fw.write(headerSizeData) == true &&
fw.write(blockSizeData) &&
fw.write(metaData) else {
log("error to write header\n")
return
}
func rndNonce()->(nonce: Rabbit.IV, bytes:[UInt8]) {
let byteCount = sizeof(Rabbit.IV.self)
let buffer = UnsafeMutablePointer<UInt8>.alloc(byteCount)
defer { buffer.destroy(byteCount) }
arc4random_buf(buffer, byteCount)
var bytes: [UInt8] = []
for i in 0..<byteCount {
bytes.append((buffer + i).memory)
}
let nonce = (UnsafePointer<UInt32>(buffer).memory, UnsafePointer<UInt32>(buffer + sizeof(UInt32.self)).memory)
return (nonce: nonce, bytes: bytes)
}
// encrypt sample video
// variable block is NOT part of encryption routine
var block = 0
let start = NSDate()
log("wait, encrypting sample video ...\n")
repeat {
let nonce = rndNonce()
// create and write one block
if let data = fr.read(blockSize - nonce.bytes.count) where !data.isEmpty {
fw.write(nonce.bytes) // not encrypted !!!
let encryptor = cryptGenerator(key, iv: nonce.nonce, offset: 0)
guard fw.write(encryptor(data)) == true else {
log("error to write data\n")
return
}
} else {
break
}
log("nonce: \(nonce.bytes.hex) block: \(block)\n")
block += 1
} while true
let time = NSDate().timeIntervalSinceDate(start)
log("encrypted: \(encrypted) in: \(time) seconds\n")
log("\n")
dispatch_async(dispatch_get_main_queue(), {
self.playEncryptedButton.enabled = true
})
}
}
@IBAction func playEncryptedVideo(sender: UIButton) {
videoUrl = NSURL(string: "encrypted:///encrypted.dat;public.mpeg-4")
performSegueWithIdentifier("seguePlayVideo", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguePlayVideo" {
let destination = segue.destinationViewController as! AVPlayerViewController
videoAsset = AVURLAsset(URL: videoUrl)
videoAsset.resourceLoader.setDelegate(self, queue: queue)
let item = AVPlayerItem(asset: videoAsset)
destination.player = AVPlayer(playerItem: item)
}
}
// MARK: - AVAssetResourceLoderDelegate
func resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
func log(txt: String) {
dispatch_async(dispatch_get_main_queue()) {
self.log.text.appendContentsOf(txt)
self.log.font = UIFont(name: "Menlo-Bold", size: 11.0)!
let c = self.log.text.characters.count
if c > 0 {
let bottom = NSMakeRange(c - 1, 1)
self.log.scrollRangeToVisible(bottom)
}
}
}
// preconditions
guard let url = loadingRequest.request.URL where url.scheme == "encrypted" else {
log("error: no url with scheme 'encrypred'\n")
return false
}
guard let path = url.path else {
log("error: no path\n")
return false
}
guard let type = url.parameterString /* where .... check type here */else {
log("error: unknown media UTI\n")
return false
}
// encrypted file path
var encrypted = NSHomeDirectory()
encrypted.appendContentsOf("/Documents")
encrypted.appendContentsOf(path)
// stream for reading encrypted data
let fr = FStream(path: encrypted, mode: "r")
if let contentRequest = loadingRequest.contentInformationRequest {
// all data in container are in big endian (network) byte order
//
// offset | size | data
// ------------------------------------------------------------
// 0 | 2 | 16 bit header size (HS)
// 2 | 3 | 24 bit block size (BS)
// 5 | any | meta data, actual size is header size - 5
// -------------------------------------------------------------
// HS | 16 | nonce (initial vector for stream decoder)
// HS+16 | BS-16 | any pay load data (encrypted)
// -------------------------------------------------------------
// ..... others blocks as necesary
// -------------------------------------------------------------
// N*BS+5 | 16 | last nonce
// N*BS+21| LBS | LBS (last block size) 0..<(BS-16)
// EOF
//
// NOTE: (1) N represent number of blocks
// (2) all blocks (except tha last one) have the same size
// (3) only payload data are encrypted
if headerSize == 0 {
fr.seek(0)
if let header = fr.read(5) {
headerSize = Int64(header[0])
headerSize += Int64(header[1]) << 8
blockSize = Int64(header[2])
blockSize += Int64(header[3]) << 8
blockSize += Int64(header[4]) << 16
}
if let meta = fr.read(Int(headerSize) - 5) {
let metadata = UTF8.decode(meta)
log("header: (size: \(headerSize), block size: \(blockSize), meta data: \(metadata))\n")
} else {
log("error: error to read from container\n")
}
}
// use the same 'secret' as for encryption
// MARK: should be based on some user input
key = key ?? s2K("0F62B5085BAE0154A7FA4DA0F34699EC")
let size = fr.size()
blockCount = (size - headerSize) / blockSize
let containerSize = blockCount * nonceSize + headerSize
contentRequest.contentLength = size - containerSize
// must be proper UTI !!!!!!! ( "video/mp4" is NOT valid UTI !!!)
contentRequest.contentType = type
contentRequest.byteRangeAccessSupported = true
log("block count: \(blockCount) file size: \(size) content size: \(size - containerSize)\n")
}
if let dataRequest = loadingRequest.dataRequest {
// use this for decryptor set up !! (payload space)
let requestedOffset = dataRequest.requestedOffset // content offset
// need to calculate from where and how much we can read in this data request
blockNumber = requestedOffset / (blockSize - nonceSize)
// file positions (where to read from encrypted container)
let nonceOffset = headerSize + blockNumber * blockSize
let dataOffset = requestedOffset + blockNumber * nonceSize + headerSize + nonceSize
let maxDataCount = nonceOffset + blockSize - dataOffset
// reposition in file and read the current block's nonce
fr.seek(nonceOffset)
if let nonceData = fr.read(Int(nonceSize)) {
let nonce = s2IV(nonceData.hex)
// offset must be in payload space in current block
decryptor = cryptGenerator(key, iv: nonce, offset: requestedOffset - blockNumber * (blockSize - nonceSize))
} else {
log("error: invalid nonce\n")
return false
}
let requestedLength = dataRequest.requestedLength
// all decrypted data must be in the same block!
let chunkSize = min(requestedLength, Int(maxDataCount))
// seek in file to proper position corresponding to requestedOffset in payload
fr.seek(dataOffset)
if let encrypted = fr.read(chunkSize) where !encrypted.isEmpty {
if let decrypted = decryptor?(encrypted) {
dataRequest.respondWithData(NSData(bytes: decrypted, length: decrypted.count))
loadingRequest.finishLoading()
} else {
log("error: invalid decryptor\n")
return false
}
} else {
return false
}
}
return true
}
}