美文网首页
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