snej@0
|
1 |
//
|
snej@0
|
2 |
// Cryptor.m
|
snej@0
|
3 |
// MYCrypto
|
snej@0
|
4 |
//
|
snej@0
|
5 |
// Created by Jens Alfke on 3/21/09.
|
snej@0
|
6 |
// Copyright 2009 Jens Alfke. All rights reserved.
|
snej@0
|
7 |
//
|
snej@0
|
8 |
|
snej@0
|
9 |
#import "MYCryptor.h"
|
snej@0
|
10 |
#import "MYDigest.h"
|
snej@0
|
11 |
#import "Test.h"
|
snej@0
|
12 |
|
snej@2
|
13 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@0
|
14 |
#import <Security/SecRandom.h>
|
snej@0
|
15 |
#else
|
snej@0
|
16 |
#import "MYCrypto_Private.h"
|
snej@0
|
17 |
#import "MYKeychain.h"
|
snej@0
|
18 |
#import <stdlib.h>
|
snej@0
|
19 |
#endif
|
snej@0
|
20 |
|
snej@0
|
21 |
|
snej@0
|
22 |
NSString* const CryptorErrorDomain = @"CCCryptor";
|
snej@0
|
23 |
|
snej@2
|
24 |
#if !MYCRYPTO_USE_IPHONE_API
|
snej@0
|
25 |
static BOOL generateRandomBytes(CSSM_CSP_HANDLE module, uint32_t lengthInBytes, void *dstBytes);
|
snej@0
|
26 |
#endif
|
snej@0
|
27 |
|
snej@0
|
28 |
|
snej@0
|
29 |
@interface MYCryptor ()
|
snej@0
|
30 |
@property (readwrite, retain) NSError *error;
|
snej@0
|
31 |
@end
|
snej@0
|
32 |
|
snej@0
|
33 |
|
snej@0
|
34 |
|
snej@0
|
35 |
@implementation MYCryptor
|
snej@0
|
36 |
|
snej@0
|
37 |
|
snej@2
|
38 |
+ (NSData*) randomKeyOfLength: (size_t)lengthInBits {
|
snej@2
|
39 |
size_t lengthInBytes = (lengthInBits + 7)/8;
|
snej@2
|
40 |
NSParameterAssert(lengthInBytes<100000);
|
snej@2
|
41 |
uint8_t *bytes = malloc(lengthInBytes);
|
snej@0
|
42 |
if (!bytes) return nil;
|
snej@2
|
43 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@2
|
44 |
BOOL ok = SecRandomCopyBytes(kSecRandomDefault, lengthInBytes,bytes) >= 0;
|
snej@0
|
45 |
#else
|
snej@2
|
46 |
BOOL ok = generateRandomBytes([[MYKeychain defaultKeychain] CSPHandle], lengthInBytes, bytes);
|
snej@0
|
47 |
#endif
|
snej@0
|
48 |
if (ok)
|
snej@2
|
49 |
return [NSData dataWithBytesNoCopy: bytes length: lengthInBytes freeWhenDone: YES];
|
snej@0
|
50 |
else {
|
snej@0
|
51 |
free(bytes);
|
snej@0
|
52 |
return nil;
|
snej@0
|
53 |
}
|
snej@0
|
54 |
}
|
snej@0
|
55 |
|
snej@2
|
56 |
+ (NSData*) keyOfLength: (size_t)lengthInBits
|
snej@2
|
57 |
fromPassphrase: (NSString*)passphrase
|
snej@2
|
58 |
salt: (id)salt
|
snej@0
|
59 |
{
|
snej@3
|
60 |
// This follows algorithm PBKDF1 from PKCS#5 v2.0, with Hash=SHA-256 and c=13.
|
snej@2
|
61 |
Assert(passphrase);
|
snej@2
|
62 |
Assert(salt);
|
snej@2
|
63 |
passphrase = $sprintf(@"MYCrypto|%@|%@", passphrase, salt);
|
snej@0
|
64 |
size_t lengthInBytes = (lengthInBits + 7)/8;
|
snej@0
|
65 |
MYDigest *digest = [[passphrase dataUsingEncoding: NSUTF8StringEncoding] my_SHA256Digest];
|
snej@3
|
66 |
for (int i=0; i<12; i++)
|
snej@3
|
67 |
digest = digest.asData.my_SHA256Digest;
|
snej@0
|
68 |
if (lengthInBytes <= digest.length)
|
snej@0
|
69 |
return [digest.asData subdataWithRange: NSMakeRange(0,lengthInBytes)];
|
snej@0
|
70 |
else
|
snej@0
|
71 |
return nil;
|
snej@0
|
72 |
}
|
snej@0
|
73 |
|
snej@0
|
74 |
|
snej@0
|
75 |
- (id) initWithKey: (NSData*)key
|
snej@0
|
76 |
algorithm: (CCAlgorithm)algorithm
|
snej@0
|
77 |
operation: (CCOperation)op {
|
snej@0
|
78 |
self = [super init];
|
snej@0
|
79 |
if (self) {
|
snej@0
|
80 |
NSParameterAssert(key);
|
snej@0
|
81 |
_key = [key copy];
|
snej@0
|
82 |
_operation = op;
|
snej@0
|
83 |
_algorithm = algorithm;
|
snej@0
|
84 |
_options = kCCOptionPKCS7Padding;
|
snej@0
|
85 |
}
|
snej@0
|
86 |
return self;
|
snej@0
|
87 |
}
|
snej@0
|
88 |
|
snej@0
|
89 |
- (id) initEncryptorWithKey: (NSData*)key algorithm: (CCAlgorithm)algorithm {
|
snej@0
|
90 |
return [self initWithKey: key algorithm: algorithm operation: kCCEncrypt];
|
snej@0
|
91 |
}
|
snej@0
|
92 |
|
snej@0
|
93 |
- (id) initDecryptorWithKey: (NSData*)key algorithm: (CCAlgorithm)algorithm {
|
snej@0
|
94 |
return [self initWithKey: key algorithm: algorithm operation: kCCDecrypt];
|
snej@0
|
95 |
}
|
snej@0
|
96 |
|
snej@0
|
97 |
- (void) dealloc
|
snej@0
|
98 |
{
|
snej@0
|
99 |
if (_cryptor)
|
snej@0
|
100 |
CCCryptorRelease(_cryptor);
|
snej@0
|
101 |
[_key autorelease];
|
snej@0
|
102 |
[_output autorelease];
|
snej@0
|
103 |
[_outputStream release];
|
snej@0
|
104 |
[super dealloc];
|
snej@0
|
105 |
}
|
snej@0
|
106 |
|
snej@2
|
107 |
- (void) finalize
|
snej@2
|
108 |
{
|
snej@2
|
109 |
if (_cryptor)
|
snej@2
|
110 |
CCCryptorRelease(_cryptor);
|
snej@2
|
111 |
[super finalize];
|
snej@2
|
112 |
}
|
snej@2
|
113 |
|
snej@0
|
114 |
|
snej@0
|
115 |
@synthesize key=_key, algorithm=_algorithm, options=_options,
|
snej@0
|
116 |
outputStream=_outputStream, error=_error;
|
snej@0
|
117 |
|
snej@0
|
118 |
|
snej@0
|
119 |
- (BOOL) _check: (CCCryptorStatus)status {
|
snej@0
|
120 |
if (status == kCCSuccess)
|
snej@0
|
121 |
return YES;
|
snej@0
|
122 |
else {
|
snej@0
|
123 |
Warn(@"MYCryptor: CCCryptor error %i", status);
|
snej@0
|
124 |
self.error = [NSError errorWithDomain: CryptorErrorDomain code: status userInfo: nil];
|
snej@0
|
125 |
return NO;
|
snej@0
|
126 |
}
|
snej@0
|
127 |
}
|
snej@0
|
128 |
|
snej@0
|
129 |
|
snej@0
|
130 |
- (BOOL) _outputBytes: (const void*)bytes length: (size_t)length {
|
snej@0
|
131 |
if (_outputStream) {
|
snej@3
|
132 |
size_t written = [_outputStream write: bytes maxLength: length];
|
snej@0
|
133 |
if (written < 0) {
|
snej@0
|
134 |
self.error = _outputStream.streamError;
|
snej@0
|
135 |
if (_error)
|
snej@0
|
136 |
Warn(@"MYCryptor: NSOutputStream error %@", _error);
|
snej@0
|
137 |
else
|
snej@0
|
138 |
[self _check: kMYCryptorErrorOutputStreamChoked];
|
snej@0
|
139 |
return NO;
|
snej@0
|
140 |
} else if (written < length) {
|
snej@0
|
141 |
[self _check: kMYCryptorErrorOutputStreamChoked];
|
snej@0
|
142 |
return NO;
|
snej@0
|
143 |
}
|
snej@0
|
144 |
} else if (length > 0) {
|
snej@0
|
145 |
[_output appendBytes: bytes length: length];
|
snej@0
|
146 |
}
|
snej@0
|
147 |
return YES;
|
snej@0
|
148 |
}
|
snej@0
|
149 |
|
snej@0
|
150 |
|
snej@0
|
151 |
- (BOOL) _start {
|
snej@0
|
152 |
if (!_cryptor && !_error) {
|
snej@0
|
153 |
if ([self _check: CCCryptorCreate(_operation, _algorithm, _options,
|
snej@0
|
154 |
_key.bytes, _key.length, NULL, &_cryptor)]) {
|
snej@0
|
155 |
_output = [[NSMutableData alloc] initWithCapacity: 1024];
|
snej@0
|
156 |
}
|
snej@0
|
157 |
}
|
snej@0
|
158 |
return !_error;
|
snej@0
|
159 |
}
|
snej@0
|
160 |
|
snej@0
|
161 |
|
snej@0
|
162 |
- (BOOL) addBytes: (const void*)bytes length: (size_t)length {
|
snej@0
|
163 |
if (length > 0) {
|
snej@0
|
164 |
NSParameterAssert(bytes!=NULL);
|
snej@0
|
165 |
if(!_error && (_cryptor || [self _start])) {
|
snej@0
|
166 |
size_t outputLength = CCCryptorGetOutputLength(_cryptor,length,false);
|
snej@0
|
167 |
void *output = malloc(outputLength);
|
snej@0
|
168 |
if ([self _check: CCCryptorUpdate(_cryptor, bytes, length,
|
snej@0
|
169 |
output, outputLength, &outputLength)]) {
|
snej@0
|
170 |
[self _outputBytes: output length: outputLength];
|
snej@0
|
171 |
}
|
snej@0
|
172 |
free(output);
|
snej@0
|
173 |
}
|
snej@0
|
174 |
}
|
snej@0
|
175 |
return !_error;
|
snej@0
|
176 |
}
|
snej@0
|
177 |
|
snej@0
|
178 |
- (BOOL) addData: (NSData*)data
|
snej@0
|
179 |
{
|
snej@0
|
180 |
return [self addBytes: data.bytes length: data.length];
|
snej@0
|
181 |
}
|
snej@0
|
182 |
|
snej@0
|
183 |
- (BOOL) addString: (NSString*)str {
|
snej@0
|
184 |
return [self addData: [str dataUsingEncoding: NSUTF8StringEncoding]];
|
snej@0
|
185 |
}
|
snej@0
|
186 |
|
snej@0
|
187 |
|
snej@0
|
188 |
- (BOOL) addFromStream: (NSInputStream*)input
|
snej@0
|
189 |
{
|
snej@0
|
190 |
uint8_t inputBuffer[1024];
|
snej@0
|
191 |
size_t avail;
|
snej@0
|
192 |
while (!_error && input.hasBytesAvailable) {
|
snej@0
|
193 |
avail = sizeof(inputBuffer);
|
snej@0
|
194 |
NSInteger nRead = [input read: inputBuffer maxLength: sizeof(inputBuffer)];
|
snej@0
|
195 |
if (nRead < 0) {
|
snej@0
|
196 |
self.error = input.streamError;
|
snej@0
|
197 |
return NO;
|
snej@0
|
198 |
} else if (nRead == 0) {
|
snej@0
|
199 |
break;
|
snej@0
|
200 |
} else if (![self addBytes: inputBuffer length: nRead])
|
snej@0
|
201 |
return NO;
|
snej@0
|
202 |
}
|
snej@0
|
203 |
return YES;
|
snej@0
|
204 |
}
|
snej@0
|
205 |
|
snej@0
|
206 |
|
snej@0
|
207 |
- (BOOL) finish
|
snej@0
|
208 |
{
|
snej@0
|
209 |
if(!_error && (_cryptor || [self _start])) {
|
snej@0
|
210 |
size_t outputLength = 100; //CCCryptorGetOutputLength(_cryptor,1,true);
|
snej@0
|
211 |
void *output = malloc(outputLength);
|
snej@0
|
212 |
if ([self _check: CCCryptorFinal(_cryptor, output, outputLength, &outputLength)]) {
|
snej@0
|
213 |
[self _outputBytes: output length: outputLength];
|
snej@0
|
214 |
}
|
snej@0
|
215 |
free(output);
|
snej@0
|
216 |
}
|
snej@0
|
217 |
CCCryptorRelease(_cryptor);
|
snej@0
|
218 |
_cryptor = NULL;
|
snej@0
|
219 |
return !_error;
|
snej@0
|
220 |
}
|
snej@0
|
221 |
|
snej@0
|
222 |
|
snej@0
|
223 |
- (NSData*) outputData {
|
snej@0
|
224 |
if (_cryptor) [self finish];
|
snej@0
|
225 |
if(_error) {
|
snej@0
|
226 |
[_output release];
|
snej@0
|
227 |
_output = nil;
|
snej@0
|
228 |
}
|
snej@0
|
229 |
return _output;
|
snej@0
|
230 |
}
|
snej@0
|
231 |
|
snej@0
|
232 |
- (NSString*) outputString {
|
snej@0
|
233 |
NSData *output = self.outputData;
|
snej@0
|
234 |
if (output) {
|
snej@0
|
235 |
NSString *str = [[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding];
|
snej@0
|
236 |
return [str autorelease];
|
snej@0
|
237 |
} else
|
snej@0
|
238 |
return nil;
|
snej@0
|
239 |
}
|
snej@0
|
240 |
|
snej@0
|
241 |
|
snej@0
|
242 |
// NSStream delegate method
|
snej@0
|
243 |
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
|
snej@0
|
244 |
switch (eventCode) {
|
snej@0
|
245 |
case NSStreamEventHasBytesAvailable:
|
snej@0
|
246 |
[self addFromStream: (NSInputStream*)stream];
|
snej@0
|
247 |
break;
|
snej@0
|
248 |
case NSStreamEventEndEncountered:
|
snej@0
|
249 |
[self finish];
|
snej@0
|
250 |
break;
|
snej@0
|
251 |
case NSStreamEventErrorOccurred:
|
snej@0
|
252 |
if (!_error)
|
snej@0
|
253 |
self.error = stream.streamError;
|
snej@0
|
254 |
break;
|
snej@0
|
255 |
default:
|
snej@0
|
256 |
break;
|
snej@0
|
257 |
}
|
snej@0
|
258 |
}
|
snej@0
|
259 |
|
snej@0
|
260 |
|
snej@0
|
261 |
|
snej@0
|
262 |
@end
|
snej@0
|
263 |
|
snej@0
|
264 |
|
snej@0
|
265 |
|
snej@0
|
266 |
|
snej@2
|
267 |
#if !MYCRYPTO_USE_IPHONE_API
|
snej@0
|
268 |
static BOOL generateRandomBytes(CSSM_CSP_HANDLE module, uint32_t lengthInBytes, void *dstBytes) {
|
snej@0
|
269 |
// Adapted from code in Keychain.framework's KeychainUtils.m by Wade Tregaskis.
|
snej@0
|
270 |
CSSM_CC_HANDLE ccHandle;
|
snej@0
|
271 |
if (!checkcssm(CSSM_CSP_CreateRandomGenContext(module, CSSM_ALGID_APPLE_YARROW, NULL,
|
snej@0
|
272 |
lengthInBytes, &ccHandle),
|
snej@0
|
273 |
@"CSSM_CSP_CreateRandomGenContext"))
|
snej@0
|
274 |
return NO;
|
snej@0
|
275 |
CSSM_DATA data = {.Data=dstBytes, .Length=lengthInBytes};
|
snej@0
|
276 |
BOOL ok = checkcssm(CSSM_GenerateRandom(ccHandle, &data), @"CSSM_GenerateRandom");
|
snej@0
|
277 |
CSSM_DeleteContext(ccHandle);
|
snej@0
|
278 |
return ok;
|
snej@0
|
279 |
}
|
snej@0
|
280 |
#endif
|
snej@0
|
281 |
|
snej@0
|
282 |
|
snej@0
|
283 |
|
snej@0
|
284 |
|
snej@0
|
285 |
TestCase(MYCryptor) {
|
snej@0
|
286 |
// Encryption:
|
snej@2
|
287 |
NSData *key = [MYCryptor randomKeyOfLength: 256];
|
snej@0
|
288 |
Log(@"Key = %@",key);
|
snej@0
|
289 |
MYCryptor *enc = [[MYCryptor alloc] initEncryptorWithKey: key algorithm: kCCAlgorithmAES128];
|
snej@0
|
290 |
CAssert(enc);
|
snej@0
|
291 |
CAssert([enc addString: @"This is a test. "]);
|
snej@0
|
292 |
CAssert([enc addString: @"This is only a test."]);
|
snej@0
|
293 |
CAssertEqual(enc.error, nil);
|
snej@0
|
294 |
NSData *encrypted = enc.outputData;
|
snej@0
|
295 |
CAssertEqual(enc.error, nil);
|
snej@0
|
296 |
CAssert(encrypted.length > 0);
|
snej@0
|
297 |
[enc release];
|
snej@0
|
298 |
Log(@"Encrypted = %@", encrypted);
|
snej@0
|
299 |
|
snej@0
|
300 |
// Decryption:
|
snej@0
|
301 |
MYCryptor *dec = [[MYCryptor alloc] initDecryptorWithKey: key algorithm: kCCAlgorithmAES128];
|
snej@0
|
302 |
CAssert(dec);
|
snej@0
|
303 |
CAssert([dec addData: encrypted]);
|
snej@0
|
304 |
NSString *decrypted = dec.outputString;
|
snej@0
|
305 |
CAssertEqual(dec.error, nil);
|
snej@0
|
306 |
[dec release];
|
snej@0
|
307 |
Log(@"Decrypted = '%@'", decrypted);
|
snej@0
|
308 |
CAssertEqual(decrypted, @"This is a test. This is only a test.");
|
snej@0
|
309 |
|
snej@0
|
310 |
// Encryption to stream:
|
snej@2
|
311 |
key = [MYCryptor randomKeyOfLength: 256];
|
snej@0
|
312 |
Log(@"Key = %@",key);
|
snej@0
|
313 |
enc = [[MYCryptor alloc] initEncryptorWithKey: key algorithm: kCCAlgorithmAES128];
|
snej@0
|
314 |
CAssert(enc);
|
snej@0
|
315 |
enc.outputStream = [NSOutputStream outputStreamToMemory];
|
snej@0
|
316 |
[enc.outputStream open];
|
snej@0
|
317 |
CAssert([enc addString: @"This is a test. "]);
|
snej@0
|
318 |
CAssert([enc addString: @"This is only a test."]);
|
snej@0
|
319 |
CAssert([enc finish]);
|
snej@0
|
320 |
CAssertEqual(enc.error, nil);
|
snej@0
|
321 |
encrypted = [[enc.outputStream propertyForKey: NSStreamDataWrittenToMemoryStreamKey] retain];
|
snej@0
|
322 |
CAssert(encrypted.length > 0);
|
snej@0
|
323 |
[enc release];
|
snej@0
|
324 |
Log(@"Encrypted = %@", encrypted);
|
snej@0
|
325 |
|
snej@0
|
326 |
dec = [[MYCryptor alloc] initDecryptorWithKey: key algorithm: kCCAlgorithmAES128];
|
snej@0
|
327 |
CAssert(dec);
|
snej@0
|
328 |
dec.outputStream = [NSOutputStream outputStreamToMemory];
|
snej@0
|
329 |
[dec.outputStream open];
|
snej@0
|
330 |
CAssert([dec addData: encrypted]);
|
snej@0
|
331 |
CAssert([dec finish]);
|
snej@0
|
332 |
CAssertEqual(dec.error, nil);
|
snej@0
|
333 |
NSData *decryptedData = [dec.outputStream propertyForKey: NSStreamDataWrittenToMemoryStreamKey];
|
snej@0
|
334 |
[dec release];
|
snej@0
|
335 |
decrypted = [[NSString alloc] initWithData: decryptedData
|
snej@0
|
336 |
encoding: NSUTF8StringEncoding];
|
snej@0
|
337 |
Log(@"Decrypted = '%@'", decrypted);
|
snej@0
|
338 |
CAssertEqual(decrypted, @"This is a test. This is only a test.");
|
snej@0
|
339 |
[encrypted release];
|
snej@0
|
340 |
[decrypted release];
|
snej@0
|
341 |
}
|