搞定通信加密,力防数据泄露
移动互联网主流的网络通信方式面临诸多风险:
算法破解、协议破解、中间人攻击……
攻击者利用多种攻击方式,不断对移动应用发起攻击。
在移动应用未做有效保护措施的情况下,如果加密 Key、通信协议、核心算法等被破解,会就会导致核心业务逻辑和重要接口暴露,*轻则影响正常使用体验,重则发生数据泄漏或财产损失*。
因此, 通信加密 是一套保障移动应用通信数据机密性的专业解决方案。
------
一、 通信加密流程
通信加密是对网络传输的消息体整体进行加密,移动客户端将要发送至服务端的消息整体进行加密,服务端接收到消息后进行解密。
同理,服务端发送至移动客户端的消息也需整体进行加密,移动客户端对接收到消息进行解密。通信加密示例流程如图 1 所示。
图 1 通信加密示例流程
通过整体加密客户端和服务端通信消息,可防止客户端与服务端的通信内容不被篡改和伪造,也在一定程度上防御了中间人攻击。
二、密钥协商算法
实施通信加密,最核心的是选用哪种类型的加密算法。目前常用的加密算法分类可以分为 对称加密 和 非对称加密 。
不过,对称加密算法相比于非对称加密算法来说,加解密的效率要高得多。而客户端与服务端通信对效率要求比较高,需要选用对称加密作为加密算法。
为了保证加密算法的安全,使用 ECDH 密钥协商算法生成密钥,防范将密钥预埋到客户端上或通过网络传输时的潜在安全风险。
ECDH 密钥协商算法虽然解决了密钥存储和传输的安全问题,但是该算法本身是无法抵抗中间人攻击的。如果密钥协商过程中被中间人攻击,就会导致生成的密钥不可信。
为了解决该问题,便有了升级版的密钥协商算法 ECDHE。它可在密钥协商时使用非对称ECDSA 签名算法对服务端的公钥进行签名。客户端进行验签操作可有效防御中间人攻击。
HTTPS 的握手过程就是使用的 ECDHE 算法,客户端与服务端的密钥协商流程如图 2 所示。
图 2 客户端与服务端的密钥协商流程
ECDHE 是基于椭圆曲线的密钥交换算法,可以利用 OpenSSL 库中封装的椭圆曲线算法实现我们自己的密钥协商。
三、密钥生成
密钥协商中最重要的是生成用于协商的公私钥对,生成公私钥的参考示例代码如下:
int generate_key(unsigned char **prikey, unsigned char **pubkey){
EC_KEY *ec_key;
int ret;
int prikey_len;
int pubkey_len;
ret = init_key(&ec_key);
prikey_len = i2d_ECPrivateKey(ec_key, NULL);
if(0 != prikey_len){
*prikey = calloc(prikey_len + 1, sizeof(char));
int ret = i2d_ECPrivateKey(ec_key, prikey);
if(0 == ret){
EC_KEY_free(ec_key);
return ret;
}
}
pubkey_len = i2o_ECPublicKey(ec_key, NULL);
if(0 != pubkey_len){
*pubkey = calloc(pubkey_len + 1, sizeof(char));
ret = i2o_ECPublicKey(ec_key, pubkey);
if(0 == ret){
EC_KEY_free(ec_key);
return ret;
}
}
}
四、 密钥交换
生成公私钥后就要进行另一个环节——密钥交换。只要我们确保 交换过程中的任何一方公钥不被劫持替换 就能保证不被中间人攻击。
对服务端下发到客户端的公钥使用非对称的 ECDSA 算法进行签名,客户端使用预埋的公钥进行验签,具体校验流程如图 3 所示。
图 3 客户端校验流程
密钥协商过程中客户端校验签名的示例代码如下:
int ecdsa_verify(unsigned char *server_pubkey, unsigned char *server_pubkey_sign, EC_KEY *ecdsa_pubkey) {
ECDSA_SIG* ecdsa_sign = d2i_ECDSA_SIG(NULL, (const unsigned char **)&server_pubkey_sign, strlen(server_pubkey_sign));
if(ecdsa_sign == NULL){
return -1;
}
ret = ECDSA_do_verify(server_pubkey, strlen((const char *)server_pubkey), ecdsa_sign, ecdsa_pubkey);
if(1 != ret) {
ECDSA_SIG_free(ecdsa_sign);
return -1;
}
ECDSA_SIG_free(ecdsa_sign);
return 1;
}
密钥协商中最重要的一步就是 客户端和服务端各自用自己的私钥及接收的对方公钥计算出通信加密时所用的密钥**。核心示例代码如下:
unsigned char* generate_secret_key(unsigned char *server_pubkey, unsigned char *client_prikey){
EC_KEY* ec_prikey = d2i_ECPrivateKey(NULL, (const unsigned char **)&client_prikey, strlen(client_prikey));
if(ec_key_pri == NULL){
return NULL;
}
EC_KEY* ec_pubkey = o2i_ECPublicKey(ec_pubkey, (const unsigned char **)&server_pubkey, strlen(server_pubkey));
if(ec_pubkey == NULL){
return NULL;
}
EC_POINT* ec_point = (EC_POINT *)EC_KEY_get0_public_key(ec_pubkey);
if(ec_point == NULL){
return NULL;
}
unsigned char *secret_key = OPENSSL_malloc(48);
int ret = ECDH_compute_key(secret_key, 48, ec_point, ec_prikey, NULL);
if(ret < 0) {
free(secret_key);
EC_KEY_free(ec_pubkey);
EC_KEY_free(ec_prikey);
return NULL;
}
EC_KEY_free(ec_pubkey);
EC_KEY_free(ec_prikey);
return secret_key;
}
五、密钥存储
使用的密钥协商算法生成密钥后,就涉及另一个问题:**生成的密钥要怎么存储**。
在其生成后对密钥进行简单的移位变形处理存储在应用的数据目录中即可。因通过密钥协商方式生成出密钥各用户是独立的。每个用户都拥有自己唯一的密钥,即使泄漏也不会影响其他用户,相对于所有用户使用同一密钥的安全性要高。
通信加密使用的加密算法为 AES,加密模式为 CBC。选用该模式是其破解难度高,同时符合SSL/TLS、IPSec 标准。
协商得到的密钥可以通过一系列的字符串变换,得到 AES 加密算法可直接使用的 key 和iv,此处就不做过多介绍。AES 算法就使用 OpenSSL 库封装即可,示例代码如下:
}
char* aes_decrypt(unsigned char *ciphertext, unsigned char *key, unsigned char *iv) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
char* plaintext = calloc(strlen(ciphertext) + 1, sizeof(char));
int len;
if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) {
return NULL;
}
if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, strlen(ciphertext))) {
return NULL;
}
if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
return NULL;
}
EVP_CIPHER_CTX_free(ctx);
return plaintext;
}
密钥协商成功后,客户端与服务端通信数据全为密文数据,加密后的通信数据示例如图 4 所示。
图 4 加密后的通信数据示例
通信数据全部为密文,即使攻击者成功绕过了客户端的防抓包策略,抓取到客户端与服务端的通信数据,也无法对其正常进行分析。
这样,攻击者就无法通过通信数据中的关键字段快速定位到客户端中相应的功能代码,从而在一定程度上提高客户端安全防护水平。
- 点赞
- 收藏
- 关注作者
评论(0)