350 lines
10 KiB
Objective-C
350 lines
10 KiB
Objective-C
#import "ARTCrypto+Private.h"
|
|
|
|
#import <CommonCrypto/CommonCrypto.h>
|
|
|
|
#define ART_CBC_BLOCK_LENGTH (16)
|
|
|
|
@interface ARTCipherParams ()
|
|
|
|
- (BOOL)ccAlgorithm:(CCAlgorithm *)algorithm error:(NSError **)error;
|
|
|
|
@end
|
|
|
|
@interface ARTCrypto ()
|
|
|
|
@property (nonatomic, strong) ARTLog * logger;
|
|
|
|
@end
|
|
|
|
@interface ARTCbcCipher ()
|
|
|
|
@property CCAlgorithm algorithm;
|
|
|
|
@end
|
|
|
|
@implementation NSString (ARTCipherKeyCompatible)
|
|
|
|
- (NSData *)toData {
|
|
NSString *key = self;
|
|
key = [key stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
|
|
key = [key stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
|
|
return [[NSData alloc] initWithBase64EncodedString:key options:0];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSData (ARTCipherKeyCompatible)
|
|
|
|
- (NSData *)toData {
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ARTCipherParams
|
|
|
|
- (instancetype)initWithAlgorithm:(NSString *)algorithm key:(id<ARTCipherKeyCompatible>)key {
|
|
NSData *keyData = [key toData];
|
|
return [self initWithAlgorithm:algorithm key:keyData iv:nil];
|
|
}
|
|
|
|
- (instancetype)initWithAlgorithm:(NSString *)algorithm key:(id<ARTCipherKeyCompatible>)key iv:(NSData *)iv {
|
|
self = [super init];
|
|
if (self) {
|
|
_algorithm = algorithm;
|
|
_key = [key toData];
|
|
_keyLength = [_key length] * 8;
|
|
_iv = iv;
|
|
|
|
CCAlgorithm ccAlgorithm;
|
|
NSError *error = nil;
|
|
if (![self ccAlgorithm:&ccAlgorithm error:&error]) {
|
|
[ARTException raise:NSInvalidArgumentException format:@"%@", error.userInfo[NSLocalizedFailureReasonErrorKey]];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSString *)getMode {
|
|
return @"CBC";
|
|
}
|
|
|
|
- (BOOL)ccAlgorithm:(CCAlgorithm *)algorithm error:(NSError **)error {
|
|
NSString *errorMsg;
|
|
if (NSOrderedSame == [self.algorithm compare:@"AES" options:NSCaseInsensitiveSearch]) {
|
|
if (self.iv != nil && [self.iv length] != ART_CBC_BLOCK_LENGTH) {
|
|
errorMsg = [NSString stringWithFormat:@"iv length expected to be %d, got %d instead", ART_CBC_BLOCK_LENGTH, (int)[self.iv length]];
|
|
} else if (self.keyLength != 128 && self.keyLength != 256) {
|
|
errorMsg = [NSString stringWithFormat:@"invalid key length for AES algorithm: %d", (int)self.keyLength];
|
|
} else {
|
|
*algorithm = kCCAlgorithmAES;
|
|
}
|
|
} else if (NSOrderedSame == [self.algorithm compare:@"DES" options:NSCaseInsensitiveSearch]) {
|
|
*algorithm = kCCAlgorithmDES;
|
|
} else if (NSOrderedSame == [self.algorithm compare:@"3DES" options:NSCaseInsensitiveSearch]) {
|
|
*algorithm = kCCAlgorithm3DES;
|
|
} else if (NSOrderedSame == [self.algorithm compare:@"CAST" options:NSCaseInsensitiveSearch]) {
|
|
*algorithm = kCCAlgorithmCAST;
|
|
} else if (NSOrderedSame == [self.algorithm compare:@"RC4" options:NSCaseInsensitiveSearch]) {
|
|
*algorithm = kCCAlgorithmRC4;
|
|
} else if (NSOrderedSame == [self.algorithm compare:@"RC2" options:NSCaseInsensitiveSearch]) {
|
|
*algorithm = kCCAlgorithmRC2;
|
|
} else {
|
|
errorMsg = [NSString stringWithFormat:@"unknown algorithm: %@", self.algorithm];
|
|
}
|
|
|
|
if (errorMsg) {
|
|
[self.logger error:@"ARTCrypto.ccAlgorithm: %@", errorMsg];
|
|
if (error) *error = [NSError errorWithDomain:ARTAblyErrorDomain code:0 userInfo:@{NSLocalizedFailureReasonErrorKey: errorMsg}];
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (ARTCipherParams *)toCipherParams {
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSDictionary (ARTCipherParamsCompatible)
|
|
|
|
- (ARTCipherParams *)toCipherParams {
|
|
return [ARTCrypto getDefaultParams:self];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ARTCbcCipher
|
|
|
|
- (id)initWithCipherParams:(ARTCipherParams *)cipherParams {
|
|
self = [super init];
|
|
if (self) {
|
|
_keySpec = cipherParams.key;
|
|
_iv = cipherParams.iv;
|
|
_blockLength = ART_CBC_BLOCK_LENGTH;
|
|
|
|
if (![cipherParams ccAlgorithm:&_algorithm error:nil]) {
|
|
return nil;
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
-(size_t) keyLength {
|
|
return [self.keySpec length] *8;
|
|
}
|
|
|
|
+ (instancetype)cbcCipherWithParams:(ARTCipherParams *)cipherParams {
|
|
return [[self alloc] initWithCipherParams:cipherParams];
|
|
}
|
|
|
|
- (ARTStatus *)encrypt:(NSData *)plaintext output:(NSData *__autoreleasing *)output {
|
|
NSData *iv = self.iv != nil ? self.iv : [ARTCrypto generateSecureRandomData:self.blockLength];
|
|
NSData *ciphertext = nil;
|
|
|
|
// The maximum cipher text is plaintext length + block length. We are also prepending this with the IV so need 2 block lengths in addition to the plaintext length.
|
|
size_t outputBufLen = [plaintext length] + self.blockLength * 2;
|
|
void *buf = malloc(outputBufLen);
|
|
|
|
if (!buf) {
|
|
[self.logger error:@"ARTCrypto error encrypting"];
|
|
return [ARTStatus state:ARTStateError];
|
|
}
|
|
|
|
// Copy the iv first
|
|
memcpy(buf, [iv bytes], self.blockLength);
|
|
|
|
void *ciphertextBuf = ((char *)buf) + self.blockLength;
|
|
size_t ciphertextBufLen = outputBufLen - self.blockLength;
|
|
|
|
const void *key = [self.keySpec bytes];
|
|
size_t keyLen = [self.keySpec length];
|
|
|
|
const void *ivBytes = [iv bytes];
|
|
const void *dataIn = [plaintext bytes];
|
|
size_t dataInLen = [plaintext length];
|
|
|
|
size_t bytesWritten = 0;
|
|
CCCryptorStatus status = CCCrypt(kCCEncrypt, self.algorithm, kCCOptionPKCS7Padding, key, keyLen, ivBytes, dataIn, dataInLen, ciphertextBuf, ciphertextBufLen, &bytesWritten);
|
|
|
|
if (status) {
|
|
[self.logger error:@"ARTCrypto error encrypting. Status is %d", status];
|
|
free(ciphertextBuf);
|
|
return [ARTStatus state: ARTStateError];
|
|
}
|
|
|
|
ciphertext = [NSData dataWithBytesNoCopy:buf length:(bytesWritten + self.blockLength) freeWhenDone:YES];
|
|
if (nil == ciphertext) {
|
|
[self.logger error:@"ARTCrypto error encrypting. cipher text is nil"];
|
|
free(buf);
|
|
return [ARTStatus state:ARTStateError];
|
|
}
|
|
|
|
*output = ciphertext;
|
|
|
|
return [ARTStatus state:ARTStateOk];
|
|
}
|
|
|
|
- (ARTStatus *)decrypt:(NSData *)ciphertext output:(NSData *__autoreleasing *)output {
|
|
// The first *blockLength* bytes are the iv
|
|
if ([ciphertext length] < self.blockLength) {
|
|
return [ARTStatus state: ARTStateInvalidArgs];;
|
|
}
|
|
|
|
NSData *ivData = [ciphertext subdataWithRange:NSMakeRange(0, self.blockLength)];
|
|
NSData *actualCiphertext = [ciphertext subdataWithRange:NSMakeRange(self.blockLength, [ciphertext length] - self.blockLength)];
|
|
|
|
CCOptions options = 0;
|
|
const void *key = [self.keySpec bytes];
|
|
size_t keyLength = [self.keySpec length];
|
|
|
|
const void *iv = [ivData bytes];
|
|
const void *dataIn = [actualCiphertext bytes];
|
|
size_t dataInLength = [actualCiphertext length];
|
|
|
|
// The output will never be more than the input + block length
|
|
size_t outputLength = dataInLength + self.blockLength;
|
|
void *buf = malloc(outputLength);
|
|
size_t bytesWritten = 0;
|
|
|
|
if (!buf) {
|
|
[self.logger error:@"ARTCrypto error decrypting."];
|
|
return [ARTStatus state:ARTStateError];
|
|
}
|
|
|
|
// Decrypt without padding because CCCrypt does not return an error code
|
|
// if the decrypted value is not padded correctly
|
|
CCCryptorStatus status = CCCrypt(kCCDecrypt, self.algorithm, options, key, keyLength, iv, dataIn, dataInLength, buf, outputLength, &bytesWritten);
|
|
|
|
if (status) {
|
|
[self.logger error:@"ARTCrypto error decrypting. Status is %d", status];
|
|
free(buf);
|
|
return [ARTStatus state:ARTStateError];
|
|
}
|
|
|
|
// Check that the decrypted value is padded correctly and determine the unpadded length
|
|
const char *cbuf = (char *)buf;
|
|
int paddingLength = cbuf[bytesWritten - 1];
|
|
|
|
if (0 == paddingLength || paddingLength > bytesWritten) { free(buf);
|
|
return [ARTStatus state:ARTStateCryptoBadPadding];
|
|
}
|
|
|
|
for (size_t i=(bytesWritten - 1); i>(bytesWritten - paddingLength); --i) {
|
|
if (paddingLength != cbuf[i-1]) {
|
|
free(buf);
|
|
return [ARTStatus state:ARTStateCryptoBadPadding];
|
|
}
|
|
}
|
|
|
|
size_t unpaddedLength = bytesWritten - paddingLength;
|
|
|
|
NSData *plaintext = [NSData dataWithBytesNoCopy:buf length:unpaddedLength freeWhenDone:YES];
|
|
if (!plaintext) {
|
|
[self.logger error:@"ARTCrypto error decrypting. plain text is nil"];
|
|
free(buf);
|
|
}
|
|
|
|
*output = plaintext;
|
|
|
|
return [ARTStatus state:ARTStateOk];
|
|
}
|
|
|
|
- (NSString *)cipherName {
|
|
NSString *algo = nil;
|
|
switch (self.algorithm) {
|
|
case kCCAlgorithmAES:
|
|
algo = @"aes";
|
|
break;
|
|
case kCCAlgorithmDES:
|
|
algo = @"des";
|
|
break;
|
|
case kCCAlgorithm3DES:
|
|
algo = @"3des";
|
|
break;
|
|
case kCCAlgorithmCAST:
|
|
algo = @"cast";
|
|
break;
|
|
case kCCAlgorithmRC4:
|
|
algo = @"rc4";
|
|
break;
|
|
case kCCAlgorithmRC2:
|
|
algo = @"rc2";
|
|
break;
|
|
default:
|
|
NSAssert(NO, @"Invalid algorithm");
|
|
return nil;
|
|
}
|
|
return [NSString stringWithFormat:@"%@-cbc", algo];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ARTCrypto
|
|
|
|
+ (NSString *)defaultAlgorithm {
|
|
return @"AES";
|
|
}
|
|
|
|
+ (int)defaultKeyLength {
|
|
return 256;
|
|
}
|
|
|
|
+ (int)defaultBlockLength {
|
|
return 128;
|
|
}
|
|
|
|
+ (NSMutableData *)generateSecureRandomData:(size_t)length {
|
|
void *buf = malloc(length);
|
|
if (!buf) {
|
|
return nil;
|
|
}
|
|
int rc = SecRandomCopyBytes(kSecRandomDefault, length, buf);
|
|
if (rc != 0) {
|
|
free(buf);
|
|
return nil;
|
|
}
|
|
|
|
NSMutableData *outputData = [NSMutableData dataWithBytesNoCopy:buf length:length freeWhenDone:YES];
|
|
if (!outputData) {
|
|
free(buf);
|
|
}
|
|
return outputData;
|
|
}
|
|
|
|
+ (NSData *)generateHashSHA256:(NSData *)data {
|
|
u_int8_t digest[CC_SHA256_DIGEST_LENGTH * sizeof(u_int8_t)];
|
|
memset(digest, 0x0, CC_SHA256_DIGEST_LENGTH);
|
|
CC_SHA256([data bytes], (CC_LONG)[data length], digest);
|
|
NSData *hash = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
|
|
return hash;
|
|
}
|
|
|
|
+ (ARTCipherParams *)getDefaultParams:(NSDictionary *)values {
|
|
NSString *algorithm = values[@"algorithm"];
|
|
if (algorithm == nil) {
|
|
algorithm = [ARTCrypto defaultAlgorithm];
|
|
}
|
|
NSString *key = values[@"key"];
|
|
if (key == nil) {
|
|
[ARTException raise:NSInvalidArgumentException format:@"missing key parameter"];
|
|
}
|
|
return [[ARTCipherParams alloc] initWithAlgorithm:algorithm key:key];
|
|
}
|
|
|
|
+ (NSData *)generateRandomKey {
|
|
return [self generateRandomKey:[self defaultKeyLength]];
|
|
}
|
|
|
|
+ (NSData *)generateRandomKey:(NSUInteger)length {
|
|
return [self generateSecureRandomData:length / 8];
|
|
}
|
|
|
|
+ (id<ARTChannelCipher>)cipherWithParams:(ARTCipherParams *)params {
|
|
return [ARTCbcCipher cbcCipherWithParams:params];
|
|
}
|
|
|
|
@end
|