MYCryptor.m
author Jens Alfke <jens@mooseyard.com>
Fri Aug 07 11:24:53 2009 -0700 (2009-08-07)
changeset 28 54b373aa65ab
parent 3 1dfe820d7ebe
permissions -rw-r--r--
Fixed iPhone OS build. (issue 3)
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
}
snej@14
   342
snej@14
   343
snej@14
   344
snej@14
   345
/*
snej@14
   346
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@14
   347
 
snej@14
   348
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@14
   349
 provided that the following conditions are met:
snej@14
   350
 
snej@14
   351
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@14
   352
 and the following disclaimer.
snej@14
   353
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@14
   354
 and the following disclaimer in the documentation and/or other materials provided with the
snej@14
   355
 distribution.
snej@14
   356
 
snej@14
   357
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@14
   358
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@14
   359
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@14
   360
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@14
   361
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@14
   362
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@14
   363
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@14
   364
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@14
   365
 */