美文网首页
IOS 加密 ECDH 学习使用(OpenSSL)

IOS 加密 ECDH 学习使用(OpenSSL)

作者: 阳光下的灰尘 | 来源:发表于2022-11-10 10:24 被阅读0次

概要: 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

相关文章

网友评论

      本文标题:IOS 加密 ECDH 学习使用(OpenSSL)

      本文链接:https://www.haomeiwen.com/subject/vkhatdtx.html