常见加密算法总结
1. 安全散列算法
Secure Hash Algorithm,常见的算法包括了 MD5、SHA1、HMAC 等。
将任意长度的二进制值映射为较短的固定长度的二进制值,这个短的二进制值称为哈希值,这个算法具有不可逆、碰撞低等特性。同时该类算法可以用作数字签名,用来证实某个信息确实是由某个人发出的,同时可以保证信息没有被修改。实际上,简单来说,这种算法有两个特性:
A) 不同的输入一定得出不同的 hash 值;
B) 无法从 hash 值倒推出原来的输入。
2. 对称加密
symmetric-key encryption,其中常见的算法包括了 AES、DES、3DES 、RC4等。
对称加密指的是可以使用同一个密钥对内容进行加密和解密,相比非对称加密,它的特点是加/解密速度快,并且加密的内容长度几乎没有限制。
3. 非对称加密
asymmetric/public-key encryption,常见的加密算法有 RSA、DSA、ECC 等。
非对称加密有两个密钥,分别为公钥和私钥,其中公钥公开给所有人,私钥永远只能自己知道。使用公钥加密的信息只能使用私钥解密,使用私钥加密只能使用公钥解密。前者用来传输需要保密的信息,因为全世界只有知道对应私钥的人才可以解密;后者用来作数字签名,因为公钥对所有人公开的,可以用来确认这个信息是否是从私钥的拥有者发出的。
安全散列算法
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加密算法基础上的
#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目录,下面是个子目录,其中的内容如下:
- 资源文件,例如图片、html、等等。
- _CodeSignature/CodeResources。这是一个plist文件,可用文本查看,其中的内容就是是程序包中(不包括Frameworks)所有文件的签名。注意这里是
所有文件
。意味着你的程序一旦签名,就不能更改其中任何的东西,包括资源文件和可执行文件本身。iOS系统会检查这些签名。 - 可执行文件。此文件跟资源文件一样需要签名。
- 一个mobileprovision文件.打包的时候使用的,从MC上生成的。
- 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重新签名的流程
如果要设计一个自动化的重签程序,大致需要这么个流程:
- 首先解压ipa
- 如果mobileprovision需要替换,替换
- 如果存在
Frameworks
子目录,则对.app文件夹下的所有Frameworks进行签名,在Frameworks文件夹下的.dylib
或.framework
- 对xxx.app签名
- 重新打包
iOS设备如何验证app是否合法
关键的几个点:
- 解压ipa
- 取出
embedded.mobileprovision
,通过签名校验是否被篡改过 a. 其中有几个证书的公钥,其中开发证书和发布证书用于校验签名 b. BundleId c. 授权列表- 校验所有文件的签名,包括Frameworks
- 比对Info.plist里面的BundleId是否符合
embedded.mobileprovision
文件中的
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。