概要: ECDH 加密的学习和使用总结
1、ECDHTool 工具 单利初始化
+ (ECDHTool *)sharedInstance {
static ECDHTool *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ECDHTool alloc] init];
});
return sharedInstance;
}
2、创建 EC_KEY 通过 NID_secp256k1
// curve 参数传 NID_secp256k1
- (EC_KEY *)createNewKeyWithCurve:(int)curve {
// 生成EC_KEY对象
int asn1Flag = OPENSSL_EC_NAMED_CURVE;
int form = POINT_CONVERSION_UNCOMPRESSED;
EC_KEY *eckey = NULL;
EC_GROUP *group = NULL;
eckey = EC_KEY_new();
group = EC_GROUP_new_by_curve_name(curve);
EC_GROUP_set_asn1_flag(group, asn1Flag);
EC_GROUP_set_point_conversion_form(group, form);
EC_KEY_set_group(eckey, group);
int resultFromKeyGen = EC_KEY_generate_key(eckey);
if (resultFromKeyGen != 1){
raise(-1);
}
return eckey;
}
3、创建 privateKeyBase64 和 publicKeyBase64
/**
* 生成ECC(椭圆曲线加密算法)的私钥和公钥
*/
- (void)generatekeyPairs{
EC_KEY *eckey;
eckey = [self createNewKeyWithCurve:KCurveName];
NSString *privateKeyBase64 = [self getPem:eckey voidType:ECDHKeyPairTypePrivate];
NSString *publicKeyBase64 = [self getPem:eckey voidType:ECDHKeyPairTypePublic];
self.privateKeyBase64 = privateKeyBase64;
self.publicKeyBase64 = publicKeyBase64;
EC_KEY_free(eckey);
}
/**
@param ecKey EC_KEY
@param keyPairType ECDHKeyPairType (公钥/私钥)类型
@return 公钥/私钥
*/
- (NSString *)getPem:(EC_KEY *)ecKey
voidType:(ECDHKeyPairType)keyPairType{
BIO *out = NULL;
BUF_MEM *buf;
buf = BUF_MEM_new();
out = BIO_new(BIO_s_mem());
switch (keyPairType) {
case ECDHKeyPairTypePrivate:
PEM_write_bio_ECPrivateKey(out, ecKey, NULL, NULL, 0, NULL, NULL);
break;
case ECDHKeyPairTypePublic:
PEM_write_bio_EC_PUBKEY(out, ecKey);
break;
default:
break;
}
BIO_get_mem_ptr(out, &buf);
NSString *pem = [[NSString alloc] initWithBytes:buf->data
length:(NSUInteger)buf->length
encoding:NSASCIIStringEncoding];
if (keyPairType == ECDHKeyPairTypePublic) {
pem = [pem stringByReplacingOccurrencesOfString:@"-----BEGIN PUBLIC KEY-----" withString:@""];
pem = [pem stringByReplacingOccurrencesOfString:@"\r" withString:@""];
pem = [pem stringByReplacingOccurrencesOfString:@"\n" withString:@""];
pem = [pem stringByReplacingOccurrencesOfString:@"-----END PUBLIC KEY-----" withString:@""];
}
BIO_free(out);
return pem;
}
4、APP 与 Server 交换公钥接口
- (void)serverPublicKeyExchange:(void (^)(void))success
failure:(void (^)(void))failure {
// 每次与server交换公钥接口 先初始化 APP 本地公私钥
[self generatekeyPairs];
// 重置之前获取的 Server 公钥
self.serverPublicKeyBase64 = nil;
// 服务器密钥标识
self.skey = nil;
// App.priKey + Server.pubKey、ECDH协商出来的AES.key
self.aesKey = nil;
// 交换公钥参数生成
// 通过 app 生成的 ECDH 公钥 publicKeyBase64 作为参数,可以再加上自己的其他参数例如:(时间戳,签名)
NSDictionary *postInfo = [NetWorkAPIInstance spayServerPublicKeyExchange:self.publicKeyBase64];
__weak typeof(self) weakSelf = self;
// 网络接口请求后台的 Server PublicKey
// URL 交换公钥 接口地址
[APIHttp post:URL paramter:postInfo success:^(id responseObject) {
NSString *serPubKey = responseObject[@"serPubKey"];
NSString *skey = responseObject[@"skey"];
if (serPubKey.length>0 && skey.length>0) {
// 保存 Server PublicKey
weakSelf.serverPublicKeyBase64 = serPubKey;
// 服务器密钥标识
weakSelf.skey = skey;
// App.priKey + Server.pubKey、ECDH协商出来的AES.key
// 所以需要 本地的私钥和服务返回的公钥进行协商
weakSelf.aesKey = [ECDHTool getShareKeyFromPeerPublicKeyBase64:serPubKey privateKeyBase64:weakSelf.privateKeyBase64 length:32];
success();
} else {
failure();
}
} failure:^(NSError *error) {
failure();
}];
}
根据获取到的 server publicKey 和 APP 本地的 私钥进行协商,得到 aesKey
/**
根据三方公钥和自持有的私钥经过DH(Diffie-Hellman)算法生成的协商密钥
@param peerPublicKeyBase64 三方公钥
@param privateKeyBase64 自持有私钥
@param length 协商密钥长度
@return 协商密钥
*/
+ (NSString *)getShareKeyFromPeerPublicKeyBase64:(NSString *)peerPublicKeyBase64
privateKeyBase64:(NSString *)privateKeyBase64
length:(int)length {
// 根据私钥PEM字符串,生成私钥
EC_KEY *clientEcKey = [ECDHTool privateKeyFromPEM:privateKeyBase64];
if (!length) {
// 获取私钥长度
const EC_GROUP *group = EC_KEY_get0_group(clientEcKey);
length = (EC_GROUP_get_degree(group) + 7)/8;
}
// 根据peerPubPem生成新的公钥EC_KEY
EC_KEY *serverEcKey = [ECDHTool publicKeyFromPEM:peerPublicKeyBase64];
const EC_POINT *serverEcKeyPoint = EC_KEY_get0_public_key(serverEcKey);
char shareKey[length];
ECDH_compute_key(shareKey, length, serverEcKeyPoint, clientEcKey, NULL);
// 释放公钥,释放私钥
EC_KEY_free(clientEcKey);
EC_KEY_free(serverEcKey);
NSData *shareKeyData = [NSData dataWithBytes:shareKey length:length];
NSString *shareKeyStr = [shareKeyData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
return shareKeyStr;
}
交换公钥 需要通过接口来进行实现
/**
交换公钥接口
@param appPublicKey iOS生成秘钥对的 公钥
*/
- (NSDictionary *)spayServerPublicKeyExchange:(NSString *)appPublicKey {
NSDictionary *postInfo = @{@"publicKey":appPublicKey};
return [self noLoginPostPackageSign:postInfo];
}
/**
* 新接口,对请求参数封装。同时生成签名(未登陆的,未做公钥交换生成ECDH.skey的)
*/
- (NSDictionary*)noLoginPostPackageSign:(NSDictionary*)info{
// 本地已经有ECDH 交换skey 则使用 ECDH.skey 做salt
// 兼容登录成功后,已经有ECDH.skey,后主动登出,再调用忘记密码重置流程接口
if ([ECDHTool sharedInstance].skey.length > 0) {
return [self newPostPackageSign:info];
}
//将时间戳放入签名
NSMutableDictionary *infoDic = [info mutableCopy];
NSString * timestamp = nil;
if ([infoDic objectForKey:@"timestampCache"] == nil) {
//生成戳随机值
timestamp = [[SPRequstTimestampManager sharedInstance] getTimestamp];
[infoDic setObject:timestamp forKey:@"timestampCache"];
}else {
timestamp = [infoDic objectForKey:@"timestampCache"];
}
//签名
NSString *signString = [self requestPackageSignString:infoDic signSaltString:timestamp.md5Hash];
//保存时间戳随机值
[[NSUserDefaults standardUserDefaults] setObject:signString forKey:timestamp];
[[NSUserDefaults standardUserDefaults] synchronize];
[infoDic removeObjectForKey:@"timestampCache"];
NSMutableDictionary *signInfo = [[NSMutableDictionary alloc] initWithDictionary:infoDic];
if (signString) {
[signInfo safeSetObject:signString forKey:@"signS"];
}
NSString *infoMapString = [signInfo parseResponseJSONToString];
NSMutableDictionary *postInfo = [[NSMutableDictionary alloc] init];
[postInfo setObject:infoMapString forKey:@"data"];
return postInfo;
}
/**
* 新接口,对请求参数封装。同时生成签名(做公钥交换生成ECDH.skey的)
*/
- (NSDictionary*)newPostPackageSign:(NSDictionary*)info{
NSMutableDictionary *infoDic = [info mutableCopy];
NSString *signString = [self requestPackageSignString:infoDic signSaltString:[ECDHTool sharedInstance].aesKey.md5Hash.saltKeyMd5Hash16];
NSMutableDictionary *signInfo = [[NSMutableDictionary alloc] initWithDictionary:infoDic];
if (nssString) {
[signInfo safeSetObject:signString forKey:@"signS"];
}
NSString *infoMapString = [signInfo parseResponseJSONToString];
NSMutableDictionary *postInfo = [[NSMutableDictionary alloc] init];
[postInfo setObject:infoMapString forKey:@"data"];
return postInfo;
}
一般情况下接口会有一个签名
签名的用处和思路:在接口中基本上都会用到签名机制,其实都是为了防止发送的信息被串改,发送方通过将一些字段要素按一定的规则排序后,在转化成json字符串,通过MD5加密机制发送,当接收方接受到请求后需要验证该信息是否被篡改过,也需要将对应的字段按照同样的规则生成验签sign,然后在于接收到的进行比对,可以发现信息是否被串改过。
/**
* 获取请求包签名
*
* @param requestPackageInfo 请求体
* @param signSaltString 加密盐值
*
* @return <#return value description#>
*/
- (NSString*)requestPackageSignString:(NSDictionary*)requestPackageInfo
signSaltString:(NSString*)signSaltString{
if (requestPackageInfo &&
[requestPackageInfo isKindOfClass:[NSDictionary class]] &&
signSaltString) {
//将表单内容的字段按照ASCII排序,排序规则为ASCII从小到大排序
NSArray *orderArray = [requestPackageInfo orderForKeyAscii];
__block NSString *soureString = @"";
[orderArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
soureString = [NSString stringWithFormat:@"%@%@=%@&",soureString,obj,requestPackageInfo[obj]];
}];
soureString = [NSString stringWithFormat:@"%@%@=%@",soureString,@"key",signSaltString];
return [soureString sha256Hash].uppercaseString;
}else{
return nil;
}
}
NSDictionary 分类工具 NSDictionary (UtilsExtras)
/**
* 通过字典的key,以ASCII排序
*
* @return NSArray NSString*
*/
- (NSArray*)orderForKeyAscii{
NSArray *array = self.allKeys;
//将表单内容的字段按照ASCII排序,排序规则为ASCII从小到大排序
NSArray *orderArray = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return ([obj2 compare:obj1] == NSOrderedAscending);
}];
return orderArray;
}
/**
* NSDictionary to NSString
*
* @return JSONString
*/
- (NSString*)parseResponseJSONToString{
NSError *error;
NSData *jsonData;
@try {
BOOL prettyPrint = NO;
jsonData = [NSJSONSerialization dataWithJSONObject:self
options:(NSJSONWritingOptions) (prettyPrint ? NSJSONWritingPrettyPrinted : 0)
error:&error];
}
@catch (NSException *exception) {
}
@finally {
if (!jsonData || ![jsonData isKindOfClass:[NSData class]]) {
return nil;
} else {
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
}
}
NSString 使用 SHA256 Hash 算法
#pragma mark - 16位 小写
- (NSString *)saltKeyMd5Hash16 {
NSString *string = [self substringWithRange:NSMakeRange(0, 16)];
return string;
}
/**
* SHA256 Hash
*/
- (NSString *)sha256Hash {
const char *str = self.UTF8String;
unsigned char *digest;
digest = malloc(CC_SHA256_DIGEST_LENGTH);
CC_SHA256(str, (CC_LONG)strlen(str), digest);
NSMutableString *strM = [NSMutableString string];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[strM appendFormat:@"%02X", digest[i]];
}
free(digest);
return [strM copy];
}
5、得到加解密的 aeskey 后,加解密用法
例如参数密码加密
// 加密 passWord 字符串
NSString *passWord = [AESUntil AES128Encrypt:password gkey:[ECDHTool sharedInstance].aesKey];
AESUntil Tool 代码方法
加密算法
//加密
+(NSString *)AES128Encrypt:(NSString *)plainText gkey:(NSString*)gkey
{
char keyPtr[kCCKeySizeAES128+1];
memset(keyPtr, 0, sizeof(keyPtr));
[gkey.md5Hash.md5Hash16.uppercaseString getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
char ivPtr[kCCBlockSizeAES128+1];
memset(ivPtr, 0, sizeof(ivPtr));
[gIv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
NSData* data = [plainText dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger dataLength = [data length];
int diff = kCCKeySizeAES128 - (dataLength % kCCKeySizeAES128);
int newSize = 0;
if(diff > 0)
{
newSize = (int)dataLength + diff;
}
char dataPtr[newSize];
memcpy(dataPtr, [data bytes], [data length]);
for(int i = 0; i < diff; i++)
{
dataPtr[i + dataLength] = 0x00;
}
size_t bufferSize = newSize + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
memset(buffer, 0, bufferSize);
size_t numBytesCrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
0x0000, //No padding
keyPtr,
kCCKeySizeAES128,
ivPtr,
dataPtr,
sizeof(dataPtr),
buffer,
bufferSize,
&numBytesCrypted);
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
NSMutableString *result = resultData.hexValueString.mutableCopy;
NSRange range = {0,result.length};
[result replaceOccurrencesOfString:@" " withString:@"" options:NSCaseInsensitiveSearch range:range];
NSRange range2 = {0,result.length};
[result replaceOccurrencesOfString:@">" withString:@"" options:NSCaseInsensitiveSearch range:range2];
NSRange range1 = {0,result.length};
[result replaceOccurrencesOfString:@"<" withString:@"" options:NSCaseInsensitiveSearch range:range1];
return result;
}
free(buffer);
return nil;
}
解密算法
//解密
+(NSString *)AES128Decrypt:(NSString *)encryptText gkey:(NSString*)gkey
{
char keyPtr[kCCKeySizeAES128 + 1];
memset(keyPtr, 0, sizeof(keyPtr));
[gkey.md5Hash.md5Hash16.uppercaseString getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
char ivPtr[kCCBlockSizeAES128 + 1];
memset(ivPtr, 0, sizeof(ivPtr));
[gIv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
NSData *data = encryptText.dataFromHexString;
NSUInteger dataLength = [data length];
size_t bufferSize = dataLength ;
void *buffer = malloc(bufferSize);
size_t numBytesCrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES,
0x0000, //No padding
keyPtr,
kCCBlockSizeAES128,
ivPtr,
[data bytes],
dataLength,
buffer,
bufferSize,
&numBytesCrypted);
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
NSString *result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
result = [result stringByReplacingOccurrencesOfString:@"\0" withString:@""];
return result;
}
free(buffer);
return nil;
}
使用的要点
1、需要先本地初始化 ECDHTool ,并生成对应的 公钥和私钥
2、使用自己本地公钥请求 server 的公钥
3、使用 本地私钥 和 server 公钥 进行协商,得到加解密的 aesKey
网友评论