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