关于密码学:密码学与攻击理论引申iOS-App签名机制

74次阅读

共计 25104 个字符,预计需要花费 63 分钟才能阅读完成。

常见加密算法总结

1. 平安散列算法

Secure Hash Algorithm,常见的算法包含了 MD5SHA1HMAC 等。

将任意长度的二进制值映射为较短的固定长度的二进制值,这个短的二进制值称为哈希值,这个算法具备不可逆、碰撞低等个性。同时该类算法能够用作数字签名,用来证实某个信息的确是由某个人收回的,同时能够保障信息没有被批改。

实际上,简略来说,这种算法有两个个性:
A) 不同的输出肯定得出不同的 hash 值;
B) 无奈从 hash 值倒推出原来的输出。

2. 对称加密

symmetric-key encryption,其中常见的算法包含了 AESDES3DESRC4等。
对称加密指的是能够应用同一个密钥对内容进行加密和解密,相比非对称加密,它的特点是加 / 解密速度快,并且加密的内容长度简直没有限度。

3. 非对称加密

asymmetric/public-key encryption,常见的加密算法有 RSADSAECC 等。
非对称加密有两个密钥,别离为公钥和私钥,其中公钥公开给所有人,私钥永远只能本人晓得。

应用公钥加密的信息只能应用私钥解密,应用私钥加密只能应用公钥解密。前者用来传输须要窃密的信息,因为全世界只有晓得对应私钥的人才能够解密;后者用来作数字签名,因为公钥对所有人公开的,能够用来确认这个信息是否是从私钥的拥有者收回的。

平安散列算法

MD5 信息摘要

MD5 Message-Digest Algorithm,一种被宽泛应用的明码散列函数,能够产生出一个 128 位(16 字节)的散列值(hash value),用于确保信息传输残缺统一。
MD5 由美国明码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于 1992 年公开,用以取代 MD4 算法。
将数据(如一段文字)运算变为另一固定长度值,是散列算法的根底原理。
1996 年后被证实存在弱点,能够被加以破解,对于须要高度安全性的数据,专家个别倡议改用其余算法,如 SHA-2。2004 年,证实 MD5 算法无奈避免碰撞(collision),因而不适用于安全性认证,如 SSL 公开密钥认证或是数字签名等用处。

#include <CommonCrypto/CommonCrypto.h>

@implementation NSData (Add)

- (NSString *)md5String {unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(self.bytes, (CC_LONG)self.length, result);
    NSMutableString *hash = [NSMutableString string];
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}
@end

SHA 家族

平安散列算法 (英语:Secure Hash Algorithm,缩写为 SHA)是一个明码散列函数家族,是 FIPS 所认证的平安散列算法。
能计算出一个数字音讯所对应到的,长度固定的字符串(又称音讯摘要)的算法。且若输出的音讯不同,它们对应到不同字符串的机率很高。
SHA 家族的算法,由美国国家安全局(NSA)所设计,并由美国国家标准与技术研究院(NIST)公布,是美国的政府规范,其别离是:

SHA-0:1993 年公布,过后称做平安散列规范(Secure Hash Standard),公布之后很快就被 NSA 撤回,是 SHA- 1 的前身。
SHA-1:1995 年公布,SHA- 1 在许多平安协定中广为应用,包含 TLS 和 SSL、PGP、SSH、S/MIME 和 IPsec,曾被视为是 MD5(更早之前被广为应用的散列函数)的后继者。但 SHA- 1 的安全性在 2000 年当前曾经不被大多数的加密场景所承受。
2017 年荷兰密码学钻研小组 CWI 和 Google 正式发表攻破了 SHA-1。

SHA-2:2001 年公布,包含 SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
尽管至今尚未呈现对 SHA- 2 无效的攻打,它的算法跟 SHA- 1 基本上依然类似;因而有些人开始倒退其余代替的散列算法。

SHA-3:2015 年正式公布,SHA- 3 并不是要取代 SHA-2,因为 SHA- 2 目前并没有呈现显著的弱点。
因为对 MD5 呈现胜利的破解,以及对 SHA- 0 和 SHA- 1 呈现实践上破解的办法,NIST 感觉须要一个与之前算法不同的,可替换的加密散列算法,也就是当初的 SHA-3。

#include <CommonCrypto/CommonCrypto.h>

@implementation NSData (Add)

- (NSString *)sha1String {unsigned char result[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(self.bytes, (CC_LONG)self.length, result);
    NSMutableString *hash = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

- (NSString *)sha224String {unsigned char result[CC_SHA224_DIGEST_LENGTH];
    CC_SHA224(self.bytes, (CC_LONG)self.length, result);
    NSMutableString *hash = [NSMutableString
                             stringWithCapacity:CC_SHA224_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA224_DIGEST_LENGTH; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

- (NSString *)sha256String {unsigned char result[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(self.bytes, (CC_LONG)self.length, result);
    NSMutableString *hash = [NSMutableString
                             stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

- (NSString *)sha384String {unsigned char result[CC_SHA384_DIGEST_LENGTH];
    CC_SHA384(self.bytes, (CC_LONG)self.length, result);
    NSMutableString *hash = [NSMutableString
                             stringWithCapacity:CC_SHA384_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA384_DIGEST_LENGTH; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

- (NSString *)sha512String {unsigned char result[CC_SHA512_DIGEST_LENGTH];
    CC_SHA512(self.bytes, (CC_LONG)self.length, result);
    NSMutableString *hash = [NSMutableString
                             stringWithCapacity:CC_SHA512_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA512_DIGEST_LENGTH; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

@end

HMAC 加密算法是一种平安的基于加密 hash 函数和共享密钥的音讯认证协定. 它能够无效地避免数据在传输过程中被截获和篡改,保护了数据的完整性、可靠性和安全性. HMAC 加密算法是一种基于密钥的报文完整性的验证办法,其安全性是建设在 Hash 加密算法根底上的

HMAC 维基百科

#include <CommonCrypto/CommonCrypto.h>

@implementation NSData (Add)


- (NSString *)hmacStringUsingAlg:(CCHmacAlgorithm)alg withKey:(NSString *)key {
    size_t size;
    switch (alg) {
        case kCCHmacAlgMD5: size = CC_MD5_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA1: size = CC_SHA1_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA224: size = CC_SHA224_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA256: size = CC_SHA256_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA384: size = CC_SHA384_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA512: size = CC_SHA512_DIGEST_LENGTH; break;
        default: return nil;
    }
    unsigned char result[size];
    const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
    CCHmac(alg, cKey, strlen(cKey), self.bytes, self.length, result);
    NSMutableString *hash = [NSMutableString stringWithCapacity:size * 2];
    for (int i = 0; i < size; i++) {[hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

- (NSString *)hmacMD5StringWithKey:(NSString *)key {return [self hmacStringUsingAlg:kCCHmacAlgMD5 withKey:key];
}


- (NSString *)hmacSHA1StringWithKey:(NSString *)key {return [self hmacStringUsingAlg:kCCHmacAlgSHA1 withKey:key];
}


- (NSString *)hmacSHA224StringWithKey:(NSString *)key {return [self hmacStringUsingAlg:kCCHmacAlgSHA224 withKey:key];
}


- (NSString *)hmacSHA256StringWithKey:(NSString *)key {return [self hmacStringUsingAlg:kCCHmacAlgSHA256 withKey:key];
}


- (NSString *)hmacSHA384StringWithKey:(NSString *)key {return [self hmacStringUsingAlg:kCCHmacAlgSHA384 withKey:key];
}


- (NSString *)hmacSHA512StringWithKey:(NSString *)key {return [self hmacStringUsingAlg:kCCHmacAlgSHA512 withKey:key];
}

@end

对称加密

AES

AES256是美国 NIST 在几种加密算法比赛当选进去的 对称加密算法,是用于取代 DES 的,原名为 Rijndael 加密法,破解的报道绝对少些。

如果单纯从密码学上讲,要实现与 AES256 相当的加密强度,RSA 加密算法长度要达到 16384 位,另外 RSA1024 目前曾经不被认为是平安的加密算法了。

#include <CommonCrypto/CommonCrypto.h>

@implementation NSData (Add)

- (NSData *)AES256EncryptWithKey:(NSData *)key iv:(NSData *)iv {if (key.length != 16 && key.length != 24 && key.length != 32) return nil;
    if (iv.length != 16 && iv.length != 0) return nil;
    
    NSData *result = nil;
    size_t bufferSize = self.length + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    if (!buffer) return nil;
    
    size_t encryptedSize = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES,
                                          kCCOptionPKCS7Padding,
                                          key.bytes,
                                          key.length,
                                          iv.bytes,
                                          self.bytes,
                                          self.length,
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    if (cryptStatus == kCCSuccess) {result = [[NSData alloc] initWithBytes:buffer length:(NSUInteger)encryptedSize];
        free(buffer);
        return result;
    } else {free(buffer);
        return nil;
    }
}

- (NSData *)AES256DecryptWithKey:(NSData *)key iv:(NSData *)iv {if (key.length != 16 && key.length != 24 && key.length != 32) return nil;
    if (iv.length != 16 && iv.length != 0) return nil;
    
    NSData *result = nil;
    size_t bufferSize = self.length + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    if (!buffer) return nil;
    
    size_t encryptedSize = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES,
                                          kCCOptionPKCS7Padding,
                                          key.bytes,
                                          key.length,
                                          iv.bytes,
                                          self.bytes,
                                          self.length,
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    if (cryptStatus == kCCSuccess) {result = [[NSData alloc] initWithBytes:buffer length:(NSUInteger)encryptedSize];
        free(buffer);
        return result;
    } else {free(buffer);
        return nil;
    }
}
@end

RC4

Rivest Cipher 4是一种流加密算法,密钥长度可变。它加解密应用雷同的密钥,因而也属于对称加密算法。

#ifndef CX_SWAP // swap two value
#define CX_SWAP(_a_, _b_) do {__typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = (_tmp_); } while(0)
#endif

@implementation NSString (CX)
- (NSString *)rc4WithKey:(NSString *)key {
    int j = 0;
    unichar res[self.length];
    const unichar *buffer = res;
    unsigned char s[256];
    for (int i = 0; i < 256; i++) {s[i] = i;
    }
    for (int i = 0; i < 256; i++) {j = (j + s[i] + [key characterAtIndex:(i%key.length)])%256;
        CX_SWAP(s[i], s[j]);
    }
    int i = j = 0;
    for (int y = 0; y < self.length; y++) {i = (i + 1) % 256;
        j = (j + 1) % 256;
        CX_SWAP(s[i], s[j]);
        
        unsigned char f = [self characterAtIndex:y] ^ s[(s[i] + s[j]) % 256 ];
        res[y] = f;
    }
    return [NSString stringWithCharacters:buffer length:self.length];
}
@end

以下代码参考自 Objective-C-RSA

@interface RSA : NSObject

// return base64 encoded string
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey;
// return raw data
+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey;
// return base64 encoded string
+ (NSString *)encryptString:(NSString *)str privateKey:(NSString *)privKey;
// return raw data
+ (NSData *)encryptData:(NSData *)data privateKey:(NSString *)privKey;

// decrypt base64 encoded string, convert result to string(not base64 encoded)
+ (NSString *)decryptString:(NSString *)str publicKey:(NSString *)pubKey;
+ (NSData *)decryptData:(NSData *)data publicKey:(NSString *)pubKey;
+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey;
+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey;

@end
#import "RSA.h"
#import <Security/Security.h>

@implementation RSA

static NSString *base64_encode_data(NSData *data){data = [data base64EncodedDataWithOptions:0];
    NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return ret;
}

static NSData *base64_decode(NSString *str){NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
    return data;
}

+ (NSData *)stripPublicKeyHeader:(NSData *)d_key{
    // Skip ASN.1 public key header
    if (d_key == nil) return(nil);
    
    unsigned long len = [d_key length];
    if (!len) return(nil);
    
    unsigned char *c_key = (unsigned char *)[d_key bytes];
    unsigned int  idx    = 0;
    
    if (c_key[idx++] != 0x30) return(nil);
    
    if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
    else idx++;
    
    // PKCS #1 rsaEncryption szOID_RSA_RSA
    static unsigned char seqiod[] =
    { 0x30,   0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
        0x01, 0x05, 0x00 };
    if (memcmp(&c_key[idx], seqiod, 15)) return(nil);
    
    idx += 15;
    
    if (c_key[idx++] != 0x03) return(nil);
    
    if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
    else idx++;
    
    if (c_key[idx++] != '\0') return(nil);
    
    // Now make a new NSData from this buffer
    return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}

//credit: http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l1036
+ (NSData *)stripPrivateKeyHeader:(NSData *)d_key{
    // Skip ASN.1 private key header
    if (d_key == nil) return(nil);

    unsigned long len = [d_key length];
    if (!len) return(nil);

    unsigned char *c_key = (unsigned char *)[d_key bytes];
    unsigned int  idx    = 22; //magic byte at offset 22

    if (0x04 != c_key[idx++]) return nil;

    //calculate length of the key
    unsigned int c_len = c_key[idx++];
    int det = c_len & 0x80;
    if (!det) {c_len = c_len & 0x7f;} else {
        int byteCount = c_len & 0x7f;
        if (byteCount + idx > len) {
            //rsa length field longer than buffer
            return nil;
        }
        unsigned int accum = 0;
        unsigned char *ptr = &c_key[idx];
        idx += byteCount;
        while (byteCount) {accum = (accum << 8) + *ptr;
            ptr++;
            byteCount--;
        }
        c_len = accum;
    }

    // Now make a new NSData from this buffer
    return [d_key subdataWithRange:NSMakeRange(idx, c_len)];
}

+ (SecKeyRef)addPublicKey:(NSString *)key{NSRange spos = [key rangeOfString:@"-----BEGIN PUBLIC KEY-----"];
    NSRange epos = [key rangeOfString:@"-----END PUBLIC KEY-----"];
    if(spos.location != NSNotFound && epos.location != NSNotFound){
        NSUInteger s = spos.location + spos.length;
        NSUInteger e = epos.location;
        NSRange range = NSMakeRange(s, e-s);
        key = [key substringWithRange:range];
    }
    key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@""  withString:@""];
    
    // This will be base64 encoded, decode it.
    NSData *data = base64_decode(key);
    data = [RSA stripPublicKeyHeader:data];
    if(!data){return nil;}

    //a tag to read/write keychain storage
    NSString *tag = @"RSAUtil_PubKey";
    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
    
    // Delete any old lingering key with the same tag
    NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
    [publicKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
    [publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    [publicKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
    SecItemDelete((__bridge CFDictionaryRef)publicKey);
    
    // Add persistent version of the key to system keychain
    [publicKey setObject:data forKey:(__bridge id)kSecValueData];
    [publicKey setObject:(__bridge id) kSecAttrKeyClassPublic forKey:(__bridge id)
     kSecAttrKeyClass];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
     kSecReturnPersistentRef];
    
    CFTypeRef persistKey = nil;
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey);
    if (persistKey != nil){CFRelease(persistKey);
    }
    if ((status != noErr) && (status != errSecDuplicateItem)) {return nil;}

    [publicKey removeObjectForKey:(__bridge id)kSecValueData];
    [publicKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
    [publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    
    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKey, (CFTypeRef *)&keyRef);
    if(status != noErr){return nil;}
    return keyRef;
}

+ (SecKeyRef)addPrivateKey:(NSString *)key{
    NSRange spos;
    NSRange epos;
    spos = [key rangeOfString:@"-----BEGIN RSA PRIVATE KEY-----"];
    if(spos.length > 0){epos = [key rangeOfString:@"-----END RSA PRIVATE KEY-----"];
    }else{spos = [key rangeOfString:@"-----BEGIN PRIVATE KEY-----"];
        epos = [key rangeOfString:@"-----END PRIVATE KEY-----"];
    }
    if(spos.location != NSNotFound && epos.location != NSNotFound){
        NSUInteger s = spos.location + spos.length;
        NSUInteger e = epos.location;
        NSRange range = NSMakeRange(s, e-s);
        key = [key substringWithRange:range];
    }
    key = [key stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    key = [key stringByReplacingOccurrencesOfString:@""  withString:@""];

    // This will be base64 encoded, decode it.
    NSData *data = base64_decode(key);
    data = [RSA stripPrivateKeyHeader:data];
    if(!data){return nil;}

    //a tag to read/write keychain storage
    NSString *tag = @"RSAUtil_PrivKey";
    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

    // Delete any old lingering key with the same tag
    NSMutableDictionary *privateKey = [[NSMutableDictionary alloc] init];
    [privateKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
    [privateKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
    [privateKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
    SecItemDelete((__bridge CFDictionaryRef)privateKey);

    // Add persistent version of the key to system keychain
    [privateKey setObject:data forKey:(__bridge id)kSecValueData];
    [privateKey setObject:(__bridge id) kSecAttrKeyClassPrivate forKey:(__bridge id)
     kSecAttrKeyClass];
    [privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
     kSecReturnPersistentRef];

    CFTypeRef persistKey = nil;
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)privateKey, &persistKey);
    if (persistKey != nil){CFRelease(persistKey);
    }
    if ((status != noErr) && (status != errSecDuplicateItem)) {return nil;}

    [privateKey removeObjectForKey:(__bridge id)kSecValueData];
    [privateKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
    [privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
    [privateKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)privateKey, (CFTypeRef *)&keyRef);
    if(status != noErr){return nil;}
    return keyRef;
}

/* START: Encryption & Decryption with RSA private key */

+ (NSData *)encryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef isSign:(BOOL)isSign {const uint8_t *srcbuf = (const uint8_t *)[data bytes];
    size_t srclen = (size_t)data.length;
    
    size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
    void *outbuf = malloc(block_size);
    size_t src_block_size = block_size - 11;
    
    NSMutableData *ret = [[NSMutableData alloc] init];
    for(int idx=0; idx<srclen; idx+=src_block_size){//NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
        size_t data_len = srclen - idx;
        if(data_len > src_block_size){data_len = src_block_size;}
        
        size_t outlen = block_size;
        OSStatus status = noErr;
        
        if (isSign) {
            status = SecKeyRawSign(keyRef,
                                   kSecPaddingPKCS1,
                                   srcbuf + idx,
                                   data_len,
                                   outbuf,
                                   &outlen
                                   );
        } else {
            status = SecKeyEncrypt(keyRef,
                                   kSecPaddingPKCS1,
                                   srcbuf + idx,
                                   data_len,
                                   outbuf,
                                   &outlen
                                   );
        }
        if (status != 0) {NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
            ret = nil;
            break;
        }else{[ret appendBytes:outbuf length:outlen];
        }
    }
    
    free(outbuf);
    CFRelease(keyRef);
    return ret;
}

+ (NSString *)encryptString:(NSString *)str privateKey:(NSString *)privKey{NSData *data = [RSA encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] privateKey:privKey];
    NSString *ret = base64_encode_data(data);
    return ret;
}

+ (NSData *)encryptData:(NSData *)data privateKey:(NSString *)privKey{if(!data || !privKey){return nil;}
    SecKeyRef keyRef = [RSA addPrivateKey:privKey];
    if(!keyRef){return nil;}
    return [RSA encryptData:data withKeyRef:keyRef isSign:YES];
}

+ (NSData *)decryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{const uint8_t *srcbuf = (const uint8_t *)[data bytes];
    size_t srclen = (size_t)data.length;
    
    size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
    UInt8 *outbuf = malloc(block_size);
    size_t src_block_size = block_size;
    
    NSMutableData *ret = [[NSMutableData alloc] init];
    for(int idx=0; idx<srclen; idx+=src_block_size){//NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
        size_t data_len = srclen - idx;
        if(data_len > src_block_size){data_len = src_block_size;}
        
        size_t outlen = block_size;
        OSStatus status = noErr;
        status = SecKeyDecrypt(keyRef,
                               kSecPaddingNone,
                               srcbuf + idx,
                               data_len,
                               outbuf,
                               &outlen
                               );
        if (status != 0) {NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
            ret = nil;
            break;
        }else{
            //the actual decrypted data is in the middle, locate it!
            int idxFirstZero = -1;
            int idxNextZero = (int)outlen;
            for (int i = 0; i < outlen; i++) {if ( outbuf[i] == 0 ) {if ( idxFirstZero < 0) {idxFirstZero = i;} else {
                        idxNextZero = i;
                        break;
                    }
                }
            }
            
            [ret appendBytes:&outbuf[idxFirstZero+1] length:idxNextZero-idxFirstZero-1];
        }
    }
    
    free(outbuf);
    CFRelease(keyRef);
    return ret;
}


+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey{NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
    data = [RSA decryptData:data privateKey:privKey];
    NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return ret;
}

+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey{if(!data || !privKey){return nil;}
    SecKeyRef keyRef = [RSA addPrivateKey:privKey];
    if(!keyRef){return nil;}
    return [RSA decryptData:data withKeyRef:keyRef];
}

/* END: Encryption & Decryption with RSA private key */

/* START: Encryption & Decryption with RSA public key */

+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey{NSData *data = [RSA encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] publicKey:pubKey];
    NSString *ret = base64_encode_data(data);
    return ret;
}

+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey{if(!data || !pubKey){return nil;}
    SecKeyRef keyRef = [RSA addPublicKey:pubKey];
    if(!keyRef){return nil;}
    return [RSA encryptData:data withKeyRef:keyRef isSign:NO];
}

+ (NSString *)decryptString:(NSString *)str publicKey:(NSString *)pubKey{NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
    data = [RSA decryptData:data publicKey:pubKey];
    NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return ret;
}

+ (NSData *)decryptData:(NSData *)data publicKey:(NSString *)pubKey{if(!data || !pubKey){return nil;}
    SecKeyRef keyRef = [RSA addPublicKey:pubKey];
    if(!keyRef){return nil;}
    return [RSA decryptData:data withKeyRef:keyRef];
}

/* END: Encryption & Decryption with RSA public key */

CRC

CRC 即 循环冗余校验码(Cyclic Redundancy Check [1]):是数据通信畛域中最罕用的一种查错校验码,其特色是信息字段和校验字段的长度能够任意选定。

循环冗余查看(CRC)是一种数据传输检错性能,对数据进行多项式计算,并将失去的后果附在帧的前面,接管设施也执行相似的算法,以保障数据传输的正确性和完整性。

#import <zlib.h>

ZEXTERN uLong ZEXPORT crc32   OF((uLong crc, const Bytef *buf, uInt len));
/*
     Update a running CRC-32 with the bytes buf[0..len-1] and return the
   updated CRC-32.  If buf is Z_NULL, this function returns the required
   initial value for the crc.  Pre- and post-conditioning (one's complement) is
   performed within this function so it shouldn't be done by the application.

   Usage example:

     uLong crc = crc32(0L, Z_NULL, 0);

     while (read_buffer(buffer, length) != EOF) {crc = crc32(crc, buffer, length);
     }
     if (crc != original_crc) error();
*/

数字签名

假如,咱们有一段受权文本,须要公布,为了避免中途篡改文本内容,保障文本的完整性,以及文本是由指定的权限狗发的。首先,先将文本内容通过摘要算法,失去摘要,再用权限狗的私钥对摘要进行加密失去密文,将源文本、密文、和私钥对应的公钥一并公布即可。那么如何验证呢?

验证方首先查看公钥是否是权限狗的,而后用公钥对密文进行解密失去摘要,将文本用同样的摘要算法失去摘要,两个摘要进行比对,如果相等那么一切正常。这个过程只有有一步出问题就视为有效。

数字签名能够疾速验证文本的完整性和合法性,已广泛应用于各个领域。了解了数字签名当前,咱们进一步来看什么是数字证书。

明码攻打类型

① 惟密文攻打

(Ciphtext-only attack)

在惟密文攻打中,明码剖析者晓得明码算法,但仅能依据截获的密文进行剖析,以得出明文或密钥。因为明码剖析者所能利用的数据资源仅为密文,这是对明码剖析者最不利的状况。

②已知明文攻打

(Plaintext-known attack)

已知明文攻打是指明码剖析者除了有截获的密文外,还有一些已知的“明文—密文对”来破译明码。明码剖析者的工作指标是推出用来加密的密钥或某种 算法,这种算法能够对用该密钥加密的任何新的音讯进行解密。

③ 抉择明文攻打

(Chosen-plaintext attack)

抉择明文攻打是指明码剖析者不仅可失去一些“明文—密文对”,还能够抉择被加密的明文,并取得相应的密文。这时明码剖析者可能抉择特定的明文数据块去加密,并比拟明文和对应的密文,已剖析和发现更多的与密钥相干的信息。

明码剖析者的工作指标也是推出用来加密的密钥或某种算法,该算法能够对用该密钥加密的任何新的音讯进行解密。

④ 抉择密文攻打

(Chosen—ciphenext attack)

抉择密文攻打是指明码剖析者能够抉择一些密文,并失去相应的明文。明码剖析者的工作指标是推出密钥。这种 明码剖析多用于攻打 公钥明码体制。

iOS 证书签名原理剖析

通常咱们所说的签名就是数字签名,它是基于非对称加密算法实现的。对称加密是通过同一份密钥加密和解密数据,而非对称加密则有两份密钥,别离是公钥和私钥,用公钥加密的数据,要用私钥能力解密;用私钥加密的数据,要用公钥能力解密。这里的非对称加密就是咱们所熟知的 RSA,要理解 RSA 背地的数学原理能够参考 RSA 算法原理(一)(二)

1. 从 App Store 装置 App

这个过程的签名形式绝对简略一些。苹果官网生成一对公私钥,在苹果手机外面内置一个公钥,私钥由苹果后盾保留,咱们传 App 上 AppStore 时,苹果后盾用私钥对 App 数据值的 MD5 值进行签名,iOS 零碎下载这个 App 后,用公钥验证这个签名,若签名正确,这个 App 必定由苹果后盾认证的,并且没有被批改过,也就达到了苹果的需要:保障装置的每一个 App 都是通过苹果认证容许的。

2. 其余形式装置 APP

在理论工作当中,咱们还有一些其余的形式把 APP 装置到手机上:
开发 App 时能够间接把开发中的利用装置进手机调试;
In-House 企业外部散发,能够间接装置企业证书签名后的 App;
AD-Hoc 相当于企业散发的限度版,限度装置设施数量,较少用。

苹果对这几种形式装置的管制过程就变得复杂了,即要保障 APP 的装置时通过苹果认证的,又要管制 APP 不能被轻易装置到其余设施上,以及一些其余的权限,为了达到这样的目标,苹果采纳的流程大抵是这个样子

  • 1、在 Mac 上生成一对公私钥,这里称公钥 M,私钥 M。
  • 2、苹果本人有固定的一对公私钥,跟下面 AppStore 例子一样,私钥在苹果后盾,公钥内置在每个 iOS 设施上,这里称为公钥 A,私钥 A。
  • 3、把公钥 M 上传到苹果后盾,用苹果后盾里的私钥 A 去签名公钥 M。失去一份数据蕴含了公钥 M 以及其签名(也就是公钥的 HASH 值),把这份数据称为证书。
  • 4、在开发时,编译完一个 App 后,用本地的私钥 M 对这个 App 进行签名,同时把第三步失去的证书一起打包进 App 里,装置到手机。
  • 5、在装置时,iOS 零碎获得证书,通过零碎内置的公钥 A,去验证证书的数字签名是否正确。

验证证书确保公钥 M 是苹果认证过的,再用公钥 M 去验证 App 的签名,这里就间接验证了这个 App 的装置行为是否通过苹果官网容许。(这里只验证装置行为,不验证 App 是否被改变,因为开发阶段 App 内容总是一直变动的,苹果不须要管)。

最终流程:

上述流程只解决了下面第一个需要,也就是须要通过苹果容许才能够装置,还未解决第二个防止被滥用的问题。怎么解决呢?苹果加了两个限度,一是限度在苹果后盾注册过的设施才能够装置;二是限度签名只能针对某一个具体的 App。
那么它到底是怎么增加这两个限度的呢?在上述第三步,苹果用私钥 A 签名咱们的本地公钥 M 时,实际上除了签名本地公钥 M 外,还能够加上有限多数据,这些数据都能够保障是通过苹果官网认证的,不会有被篡改的可能。

能够把容许装置的设施 ID 列表和 App 对应的 AppID 等数据,都在第三步这里跟公钥 M 一起组成证书,再用苹果私钥 A 对这个证书签名。在最初第 5 步验证时就能够拿到设施 ID 列表,判断以后设施是否符合要求。依据数字签名的原理,只有数字签名通过验证,第 5 步这里的设施 IDs/AppID/ 公钥 M 就都是通过苹果认证的,无奈被批改,苹果就能够限度可装置的设施和 APP,防止滥用。

到这里这个证书曾经变得很简单了,有很多额定信息,实际上除了设施 ID/AppID,还有其余信息也须要在这里用苹果签名,像 App 里 iCloud、push、后盾运行 等权限苹果都想管制,苹果把这些权限开关统称为 Entitlements,它也须要通过签名去受权。
实际上一个证书原本就有规定的格局标准,下面咱们把各种额定的信息塞入证书里是不适合的,于是苹果另外搞了一个货色,叫 Provisioning Profile,一个 Provisioning Profile 里就蕴含了证书以及上述提到的所有额定信息,以及所有信息的签名。

所以,就成这样了:

在 Mac 上生成一对公私钥,这里称为公钥 M,私钥 M。
苹果本人有固定的一对公私钥,跟下面 AppStore 例子一样,私钥在苹果后盾,公钥在每个 iOS 设施上。这里称为公钥 A,私钥 A。A:Apple
把公钥 M 传到苹果后盾,用苹果后盾里的私钥 A 去签名公钥 M。失去一份数据蕴含了公钥 M 以及其签名,把这份数据称为证书。
在苹果后盾申请 AppID,配置好设施 ID 列表和 APP 可应用的权限,再加上第 3 步的证书,组成的数据用私钥 A 签名,把数据和签名一起组成一个 Provisioning Profile 文件,下载到本地 Mac 开发机。
在开发时,编译完一个 APP 后,用本地的私钥 M 对这个 APP 进行签名,同时把第 4 步失去的 Provisioning Profile 文件打包进 APP 里,文件名为 embedded.mobileprovision,把 APP 装置到手机上。
在装置时,iOS 零碎获得证书,通过零碎内置的公钥 A,去验证 embedded.mobileprovision 的数字签名是否正确,外面的证书签名也会再验一遍。
确保了 embedded.mobileprovision 里的数据都是苹果受权当前,就能够取出外面的数据,做各种验证,包含用公钥 M 验证 APP 签名,验证设施 ID 是否在 ID 列表上,AppID 是否对应得上,权限开关是否跟 APP 里的 Entitlements 对应等。

开发者证书从签名到认证最终苹果采纳的流程大抵是这样,还有一些细节像证书有效期 / 证书类型等就不细说了。

下面的步骤对应到咱们平时具体的操作和概念是这样的:
  • 第 1 步 对应的是 keychain 里的“从证书颁发机构申请证书”,这里就本地生成了一对公私钥,保留的 CertificateSigningRequest 就是公钥,私钥保留在本地电脑里。
  • 第 2 步 苹果本人解决,咱们不必管。
  • 第 3 步 对应把 CertificateSigningRequest 传到苹果后盾生成证书,并下载到本地。这时本地有两个证书,一个是第 1 步生成的,一个是这里下载回来的,keychain 会把这两个证书关联起来,因为它们的公私钥是对应的,在 Xcode 抉择下载回来的证书的时,实际上会找到 keychain 外面对应的私钥去签名。这里私钥只有生成它的这台 Mac 才有,如果别的 Mac 也要编译签名这个 App,把私钥导出给其余 Mac 应用,在 keychain 外面导出私钥,就会存成.p12 文件,其余 Mac 关上后就导入私钥。
  • 第 4 步 都是在苹果网站上操作,配置 AppID、权限、设施等,最初下载 Provisioning Profile 文件。
  • 第 5 步 Xcode 会通过第 3 步下载回来的证书(存着本地公钥),在本地找到对应的私钥(第 1 步生成的),用本地私钥去签名 App,并把 Provisioning Profile 文件命名为 embedded.mobileprovision 一起打包进去。这里对 App 的签名数据保留分为两局部,Mach- O 可执行文件会把签名间接写入这个文件里,其余资源文件则会保留在_CodeSignature 目录下。
  • 第 6、7 步 的打包和验证都是 Xcode 和 iOS 零碎主动做的事。

几个概念:

  • 证书:内容是公钥或私钥,由其余机构对其签名组成的数据包。
  • Entitlements:蕴含了 App 权限开关列表。
  • CertificateSigningRequest:本地公钥。
  • .p12:本地私钥,能够导入到其余电脑。
  • Provisioning Profile:蕴含了 证书 /Entitlements 等数据,并由苹果后盾私钥签名的数据包。
其余公布形式

后面以开发包为例子说了签名和验证的流程,另外两种形式 In-House 企业签名和 AD-Hoc 流程也是差不多的,只是企业签名不限度装置的设施数,另外须要用户在 iOS 零碎设置上手动点击信赖这个企业能力通过验证。
而 AppStore 的签名验证形式有些不一样,后面咱们说到最简略的签名形式,苹果在后盾间接用私钥签名 App 就能够了,实际上苹果的确是这样做的,如果去下载一个 AppStore 的安装包,会发现它外面是没有 embedded.mobileprovision 文件的,也就是它装置和启动的流程是不依赖这个文件,验证流程也就跟上述几种类型不一样了。

因为上传到 AppStore 的包苹果会从新对内容加密,原来的本地私钥签名就没有用了,须要从新签名,从 AppStore 下载的包苹果也并不打算管制它的有效期,不须要内置一个 embedded.mobileprovision 去做校验,间接在苹果用后盾的私钥从新签名,iOS 装置时用本地公钥验证 App 签名就能够了。

那为什么公布 AppStore 的包还是要跟开发版一样搞各种证书和 Provisioning Profile,因为苹果想做对立治理,Provisioning Profile 里蕴含一些权限管制,AppID 的测验等,苹果不想在上传 AppStore 包时从新用另一种协定做一遍这些验证,就不如对立把这部分放在 Provisioning Profile 里,上传 AppStore 时只有用同样的流程验证这个 Provisioning Profile 是否非法就能够了。

所以 App 上传到 AppStore 后,就跟你的 证书 / Provisioning Profile 都没有关系了,无论他们是否过期或被破除,都不会影响 AppStore 上的安装包。

ipa 的组成

iOS 程序最终都会以.ipa 文件导出,先来理解一下 ipa 文件的构造:


事实上,ipa 文件只是一个 zip 包,能够应用如下命令解压:

/usr/bin/unzip -q xxx.ipa -d <destination>

解压后,失去上图的 Payload 目录,上面是个子目录,其中的内容如下:

  1. 资源文件,例如图片、html、等等。
  2. _CodeSignature/CodeResources。这是一个 plist 文件,可用文本查看,其中的内容就是是程序包中(不包含 Frameworks)所有文件的签名。留神这里是 所有文件。意味着你的程序一旦签名,就不能更改其中任何的货色,包含资源文件和可执行文件自身。iOS 零碎会查看这些签名。
  3. 可执行文件。此文件跟资源文件一样须要签名。
  4. 一个 mobileprovision 文件. 打包的时候应用的,从 MC 上生成的。
  5. Frameworks。程序援用的非零碎自带的 Frameworks,每个 Frameworks 其实就是一个 app,其中的构造应该和 app 差不多,也蕴含签名信息 CodeResources 文件

相干的程序和命令

个别咱们会用 Xcode 自带的 archive 性能来打包 ipa 和签名,实际上 xcode 只不过是调用了一些内部程序实现了工作,如果咱们有朝一日须要本人实现自动化的签名流程,就须要理解到底相干的程序和命令有哪些。

用上面命令,列出零碎中可用于签名的无效证书:

/usr/bin/security find-identity -v -p codesigning 1) E056929276F94152F3FDF0EA84BD2B06396F2DDD "iPhone Developer: Liang Ding (2U967A2YJ6)" 2) 7C608F653A989E95E1A4D303EC4E6625D95EEB42 "iPhone Distribution: Liang Ding (7XPNRZE9TC)" 2 valid identities found

能够看到这个命令列出了一个字符串标示的证书名称,如:iPhone Developer: Liang Ding (2U967A2YJ6)。这个名称前面会用到的。

应用如下命令对 xxx.app 目录签名,codesign 程序会主动将其中的文件都签名,(Frameworks 不会主动签):

/user/bin/codesign -fs "iPhone Developer: Liang Ding (2U967A2YJ6)" --no-strict Payload/xxx.app

对于每个 Framework,也须要应用这个命令签名,下面说了 Framework 的构造跟 app 其实差不多,所以签名命令相似。这个命令会主动找到证书相干的私钥。- f 示意对于曾经签名的 app 强制重签。

最初用上面命令校验签名是否非法:

/usr/bin/codesign -v xxx.app

如果没有任何输入阐明没有问题。

应用 zip 命令从新打包成 ipa 包

/usr/bin/zip -qry destination source

对 app 从新签名的流程

如果要设计一个自动化的重签程序,大抵须要这么个流程:

  1. 首先解压 ipa
  2. 如果 mobileprovision 须要替换,替换
  3. 如果存在 Frameworks 子目录,则对.app 文件夹下的所有 Frameworks 进行签名,在 Frameworks 文件夹下的 .dylib.framework
  4. 对 xxx.app 签名
  5. 从新打包

iOS 设施如何验证 app 是否非法

要害的几个点:

  1. 解压 ipa
  2. 取出embedded.mobileprovision,通过签名校验是否被篡改过 a. 其中有几个证书的公钥,其中开发证书和公布证书用于校验签名 b. BundleId c. 受权列表
  3. 校验所有文件的签名,包含 Frameworks
  4. 比对 Info.plist 外面的 BundleId 是否合乎 embedded.mobileprovision 文件中的

正文完
 0