289 lines
12 KiB
Objective-C
Executable File
289 lines
12 KiB
Objective-C
Executable File
//
|
|
// Cryptography.m
|
|
// TextSecureiOS
|
|
//
|
|
// Created by Christine Corbett Moran on 3/26/13.
|
|
// Copyright (c) 2013 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import <CommonCrypto/CommonHMAC.h>
|
|
#import <CommonCrypto/CommonCryptor.h>
|
|
|
|
#import "Cryptography.h"
|
|
#import "Constraints.h"
|
|
|
|
#import "NSData+Base64.h"
|
|
|
|
#define HMAC256_KEY_LENGTH 32
|
|
#define HMAC256_OUTPUT_LENGTH 32
|
|
#define AES_CBC_IV_LENGTH 16
|
|
#define AES_KEY_SIZE 32
|
|
|
|
@implementation Cryptography
|
|
|
|
|
|
#pragma mark random bytes methods
|
|
+(NSMutableData*) generateRandomBytes:(NSUInteger)numberBytes {
|
|
/* used to generate db master key, and to generate signaling key, both at install */
|
|
NSMutableData* randomBytes = [NSMutableData dataWithLength:numberBytes];
|
|
int err = 0;
|
|
err = SecRandomCopyBytes(kSecRandomDefault,numberBytes,[randomBytes mutableBytes]);
|
|
if(err != noErr) {
|
|
@throw [NSException exceptionWithName:@"random problem" reason:@"problem generating the random " userInfo:nil];
|
|
}
|
|
return randomBytes;
|
|
}
|
|
|
|
#pragma mark SHA1
|
|
|
|
+(NSString*)truncatedSHA1Base64EncodedWithoutPadding:(NSString*)string{
|
|
/* used by TSContactManager to send hashed/truncated contact list to server */
|
|
NSMutableData *hashData = [NSMutableData dataWithLength:20];
|
|
|
|
CC_SHA1([string dataUsingEncoding:NSUTF8StringEncoding].bytes,
|
|
(unsigned int)[string dataUsingEncoding:NSUTF8StringEncoding].length,
|
|
hashData.mutableBytes);
|
|
|
|
NSData *truncatedData = [hashData subdataWithRange:NSMakeRange(0, 10)];
|
|
|
|
return [[truncatedData base64EncodedString] stringByReplacingOccurrencesOfString:@"=" withString:@""];
|
|
}
|
|
|
|
+ (NSString*)computeSHA1DigestForString:(NSString*)input {
|
|
// Here we are taking in our string hash, placing that inside of a C Char Array, then parsing it through the SHA1 encryption method.
|
|
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
|
|
NSData *data = [NSData dataWithBytes:cstr length:input.length];
|
|
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
|
|
|
|
CC_SHA1(data.bytes, (unsigned int)data.length, digest);
|
|
|
|
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
|
|
|
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
|
[output appendFormat:@"%02x", digest[i]];
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
#pragma mark SHA256
|
|
+(NSData*) computeSHA256:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes {
|
|
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
|
|
CC_SHA256(data.bytes, (unsigned int)data.length, digest);
|
|
return [[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH] subdataWithRange:NSMakeRange(0, truncatedBytes)];
|
|
}
|
|
|
|
|
|
#pragma mark HMAC/SHA256
|
|
+(NSData*) computeSHA256HMAC:(NSData*)dataToHMAC withHMACKey:(NSData*)HMACKey{
|
|
uint8_t ourHmac[CC_SHA256_DIGEST_LENGTH] = {0};
|
|
CCHmac(kCCHmacAlgSHA256,
|
|
[HMACKey bytes],
|
|
[HMACKey length],
|
|
[dataToHMAC bytes],
|
|
[dataToHMAC length],
|
|
ourHmac);
|
|
return [NSData dataWithBytes:ourHmac length:CC_SHA256_DIGEST_LENGTH];
|
|
}
|
|
|
|
+(NSData*) computeSHA1HMAC:(NSData*)dataToHMAC withHMACKey:(NSData*)HMACKey{
|
|
uint8_t ourHmac[CC_SHA256_DIGEST_LENGTH] = {0};
|
|
CCHmac(kCCHmacAlgSHA1,
|
|
[HMACKey bytes],
|
|
[HMACKey length],
|
|
[dataToHMAC bytes],
|
|
[dataToHMAC length],
|
|
ourHmac);
|
|
return [NSData dataWithBytes:ourHmac length:CC_SHA256_DIGEST_LENGTH];
|
|
}
|
|
|
|
|
|
+(NSData*) truncatedSHA1HMAC:(NSData*)dataToHMAC withHMACKey:(NSData*)HMACKey truncation:(NSUInteger)bytes{
|
|
return [[Cryptography computeSHA1HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, bytes)];
|
|
}
|
|
|
|
+(NSData*) truncatedSHA256HMAC:(NSData*)dataToHMAC withHMACKey:(NSData*)HMACKey truncation:(NSUInteger)bytes{
|
|
return [[Cryptography computeSHA256HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, bytes)];
|
|
}
|
|
|
|
|
|
#pragma mark AES CBC Mode
|
|
+(NSData*)encryptCBCMode:(NSData*) dataToEncrypt withKey:(NSData*) key withIV:(NSData*) iv withVersion:(NSData*)version withHMACKey:(NSData*) hmacKey withHMACType:(TSMACType)hmacType computedHMAC:(NSData**)hmac {
|
|
/* AES256 CBC encrypt then mac
|
|
Returns nil if encryption fails
|
|
*/
|
|
size_t bufferSize = [dataToEncrypt length] + kCCBlockSizeAES128;
|
|
void* buffer = malloc(bufferSize);
|
|
|
|
size_t bytesEncrypted = 0;
|
|
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
|
|
[key bytes], [key length],
|
|
[iv bytes],
|
|
[dataToEncrypt bytes], [dataToEncrypt length],
|
|
buffer, bufferSize,
|
|
&bytesEncrypted);
|
|
|
|
if (cryptStatus == kCCSuccess){
|
|
NSData* encryptedData= [NSData dataWithBytesNoCopy:buffer length:bytesEncrypted];
|
|
//compute hmac of version||encrypted data||iv
|
|
NSMutableData *dataToHmac = [NSMutableData data];
|
|
if(version!=nil) {
|
|
[dataToHmac appendData:version];
|
|
}
|
|
[dataToHmac appendData:iv];
|
|
[dataToHmac appendData:encryptedData];
|
|
|
|
if(hmacType == TSHMACSHA1Truncated10Bytes) {
|
|
*hmac = [Cryptography truncatedSHA1HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
|
|
} else if (hmacType == TSHMACSHA256Truncated10Bytes) {
|
|
*hmac = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
|
|
} else if (hmacType == TSHMACSHA256AttachementType) {
|
|
*hmac = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
|
|
}
|
|
|
|
return encryptedData;
|
|
}
|
|
free(buffer);
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
+(NSData*)decryptCBCMode:(NSData*)dataToDecrypt
|
|
key:(NSData*)key
|
|
IV:(NSData*)iv
|
|
version:(NSData*)version
|
|
HMACKey:(NSData*) hmacKey
|
|
HMACType:(TSMACType)hmacType
|
|
matchingHMAC:(NSData *)hmac
|
|
{
|
|
|
|
/* AES256 CBC encrypt then mac
|
|
|
|
Returns nil if hmac invalid or decryption fails
|
|
*/
|
|
//verify hmac of version||encrypted data||iv
|
|
NSMutableData *dataToHmac = [NSMutableData data ];
|
|
if(version != nil) {
|
|
[dataToHmac appendData:version];
|
|
}
|
|
|
|
[dataToHmac appendData:iv];
|
|
[dataToHmac appendData:dataToDecrypt];
|
|
|
|
NSData* ourHmacData;
|
|
|
|
if(hmacType == TSHMACSHA1Truncated10Bytes) {
|
|
ourHmacData = [Cryptography truncatedSHA1HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
|
|
} else if (hmacType == TSHMACSHA256Truncated10Bytes) {
|
|
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:10];
|
|
} else if (hmacType == TSHMACSHA256AttachementType){
|
|
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToHmac withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
|
|
}
|
|
|
|
if(hmac == nil || ![ourHmacData isEqualToData:hmac] ) {
|
|
DDLogError(@"Bad HMAC on decrypting payload");
|
|
return nil;
|
|
}
|
|
|
|
// decrypt
|
|
size_t bufferSize = [dataToDecrypt length] + kCCBlockSizeAES128;
|
|
void* buffer = malloc(bufferSize);
|
|
|
|
size_t bytesDecrypted = 0;
|
|
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
|
|
[key bytes], [key length],
|
|
[iv bytes],
|
|
[dataToDecrypt bytes], [dataToDecrypt length],
|
|
buffer, bufferSize,
|
|
&bytesDecrypted);
|
|
if (cryptStatus == kCCSuccess) {
|
|
return [NSData dataWithBytesNoCopy:buffer length:bytesDecrypted];
|
|
} else{
|
|
DDLogError(@"Failed CBC decryption");
|
|
free(buffer);
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark methods which use AES CBC
|
|
+(NSData*)decryptAppleMessagePayload:(NSData*)payload withSignalingKey:(NSString*)signalingKeyString{
|
|
require(payload);
|
|
require(signalingKeyString);
|
|
|
|
unsigned char version[1];
|
|
unsigned char iv[16];
|
|
NSUInteger ciphertext_length = ([payload length]-10-17)*sizeof(char);
|
|
unsigned char *ciphertext = (unsigned char*)malloc(ciphertext_length);
|
|
unsigned char mac[10];
|
|
[payload getBytes:version range:NSMakeRange(0, 1)];
|
|
[payload getBytes:iv range:NSMakeRange(1, 16)];
|
|
[payload getBytes:ciphertext range:NSMakeRange(17, [payload length]-10-17)];
|
|
[payload getBytes:mac range:NSMakeRange([payload length]-10, 10)];
|
|
|
|
NSData* signalingKey = [NSData dataFromBase64String:signalingKeyString];
|
|
NSData* signalingKeyAESKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(0, 32)];
|
|
NSData* signalingKeyHMACKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(32, 20)];
|
|
return [Cryptography decryptCBCMode:[NSData dataWithBytesNoCopy:ciphertext
|
|
length:ciphertext_length
|
|
freeWhenDone:YES]
|
|
key:signalingKeyAESKeyMaterial
|
|
IV:[NSData dataWithBytes:iv length:16]
|
|
version:[NSData dataWithBytes:version length:1]
|
|
HMACKey:signalingKeyHMACKeyMaterial
|
|
HMACType:TSHMACSHA256Truncated10Bytes
|
|
matchingHMAC:[NSData dataWithBytes:mac length:10]];
|
|
|
|
}
|
|
|
|
+ (NSData*)decryptAttachment:(NSData*)dataToDecrypt withKey:(NSData*)key {
|
|
if (([dataToDecrypt length] < AES_CBC_IV_LENGTH + HMAC256_OUTPUT_LENGTH) || ([key length] < AES_KEY_SIZE + HMAC256_KEY_LENGTH)) {
|
|
DDLogError(@"Message shorter than crypto overhead!");
|
|
return nil;
|
|
}
|
|
|
|
// key: 32 byte AES key || 32 byte Hmac-SHA256 key.
|
|
NSData *encryptionKey = [key subdataWithRange:NSMakeRange(0, AES_KEY_SIZE)];
|
|
NSData *hmacKey = [key subdataWithRange:NSMakeRange(AES_KEY_SIZE, HMAC256_KEY_LENGTH)];
|
|
|
|
// dataToDecrypt: IV || Ciphertext || truncated MAC(IV||Ciphertext)
|
|
NSData *iv = [dataToDecrypt subdataWithRange:NSMakeRange(0, AES_CBC_IV_LENGTH)];
|
|
NSData *encryptedAttachment = [dataToDecrypt subdataWithRange:NSMakeRange(AES_CBC_IV_LENGTH, [dataToDecrypt length]-AES_CBC_IV_LENGTH-HMAC256_OUTPUT_LENGTH)];
|
|
NSData *hmac = [dataToDecrypt subdataWithRange:NSMakeRange([dataToDecrypt length]-HMAC256_OUTPUT_LENGTH, HMAC256_OUTPUT_LENGTH)];
|
|
|
|
return [Cryptography decryptCBCMode:encryptedAttachment
|
|
key:encryptionKey
|
|
IV:iv version:nil
|
|
HMACKey:hmacKey
|
|
HMACType:TSHMACSHA256AttachementType
|
|
matchingHMAC:hmac];
|
|
}
|
|
|
|
+ (TSAttachmentEncryptionResult*)encryptAttachment:(NSData*)attachment contentType:(NSString*)contentType identifier:(NSString*)identifier {
|
|
|
|
NSData* iv = [Cryptography generateRandomBytes:AES_CBC_IV_LENGTH];
|
|
NSData* encryptionKey = [Cryptography generateRandomBytes:AES_KEY_SIZE];
|
|
NSData* hmacKey = [Cryptography generateRandomBytes:HMAC256_KEY_LENGTH];
|
|
|
|
// The concatenated key for storage
|
|
NSMutableData *outKey = [NSMutableData data];
|
|
[outKey appendData:encryptionKey];
|
|
[outKey appendData:hmacKey];
|
|
|
|
NSData* computedHMAC;
|
|
NSData* ciphertext = [Cryptography encryptCBCMode:attachment withKey:encryptionKey withIV:iv withVersion:nil withHMACKey:hmacKey withHMACType:TSHMACSHA256AttachementType computedHMAC:&computedHMAC];
|
|
|
|
NSMutableData* encryptedAttachment = [NSMutableData data];
|
|
[encryptedAttachment appendData:iv];
|
|
[encryptedAttachment appendData:ciphertext];
|
|
[encryptedAttachment appendData:computedHMAC];
|
|
|
|
TSAttachmentStream *pointer = [[TSAttachmentStream alloc] initWithIdentifier:identifier data:attachment key:outKey contentType:contentType];
|
|
|
|
return [[TSAttachmentEncryptionResult alloc] initWithPointer:pointer body:encryptedAttachment];
|
|
}
|
|
|
|
@end
|