使用 C openssl AES-GCM 加密创建 ESP 数据包会抛出错误的 ICV

Creating ESP packet using C openssl AES-GCM encryption throws wrong ICV

我正在尝试使用 AES128-CCM16 加密我的 ICMP 数据包。我使用 c openssl 库进行加密。但是加密后的结果是错误的!

我使用了两个 Linux 18.04 虚拟机来模拟带有 strongswan IPsec 的 ESP 数据包。我捕获了 ESP 数据包并在我的解密函数中打印了我的整个变量。

static bool _aes_ccm_encrypt(uint8_t* payload, uint16_t payload_len, uint8_t* key, uint16_t key_len, uint8_t* iv, uint8_t iv_len, uint8_t* aad, uint8_t aad_len, uint8_t* icv, uint8_t icv_len) {
    EVP_CIPHER_CTX *ctx = NULL;
    int len;
    uint8_t salt_len = 3;
    uint8_t new_iv[iv_len + salt_len];

    EVP_CIPHER *cipher = NULL;
    switch(key_len) {
        case 19: cipher = EVP_aes_128_ccm(); break;
        case 27: cipher = EVP_aes_192_ccm(); break;
        case 35: cipher = EVP_aes_256_ccm(); break;
        default: break;
    }   

    // Add salt to IV
    memcpy(new_iv, key + (key_len - salt_len), salt_len);
    memcpy(new_iv + salt_len, iv, iv_len);
    iv = new_iv;
    iv_len += salt_len;

    ctx = EVP_CIPHER_CTX_new();
    if(1 != EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL)
            || 1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL)
            || 1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) 
            || 1 != EVP_EncryptUpdate(ctx, NULL, &len, NULL, payload_len)
            || 1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)
            || 1 != EVP_EncryptUpdate(ctx, payload, &len, payload, payload_len)
            || 1 != EVP_EncryptFinal_ex(ctx, payload+len, &len))
        goto failure;

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, icv_len, icv);
    print_hex("icv", icv, icv_len);
    EVP_CIPHER_CTX_free(ctx);
    return true;

failure:
    ZF_LOGE("AES CCM Encryption failed");
    if(ctx)
        EVP_CIPHER_CTX_free(ctx);
    return false;
}

这是我捕获的数据包

tcpdump: listening on enp0s8, link-type EN10MB (Ethernet), capture size 262144 bytes
08:48:00.686858 IP (tos 0x0, ttl 64, id 3134, offset 0, flags [none], proto ESP (50), length 140)
    10.10.10.100 > pc2: ESP(spi=0xc8ee6879,seq=0x0), length 120
        0x0000:  ffff ffff ffff ffff ffff ffff 0800 4500
        0x0010:  008c 0c3e 0000 4032 4526 0a0a 0a64 0a0a
        0x0020:  0a65 c8ee 6879 0000 0000 0a0a 0a64 0a0a
        0x0030:  0a65 c8df 30c2 3f71 282b 1ee8 73de 411f
        0x0040:  a937 fbe2 dd3a ee2d 77fd 8b5f 83d6 2523
        0x0050:  8b7c 22d0 6af3 2fb7 1af3 6182 e263 caa4
        0x0060:  6a43 12b5 fc84 52d7 1daa 2437 86a7 9dce
        0x0070:  028e ee0c febe 543d 49f8 e6ca 96c1 e83b
        0x0080:  6514 f23a b916 ba23 58fb 3f52 a522 bd6a
        0x0090:  33e5 0739 0462 0000 0000

这是我打印的变量

原始负载

payload_o(88):
0x0x105790806:  4500 0054 0c3e 0000 4001 44a4 0a00 0a64
0x0x105790816:  0a00 0b64 0000 45be 1910 0001 408e 105d
0x0x105790826:  0000 0000 8772 0a00 0000 0000 1011 1213
0x0x105790836:  1415 1617 1819 1a1b 1c1d 1e1f 2021 2223
0x0x105790846:  2425 2627 2829 2a2b 2c2d 2e2f 3031 3233
0x0x105790856:  3435 3637 0000 0204

密钥

key(19):
0x0x7ff8a400215c:  f2e2 66ad ebca 5110 6110 3d29 4900 263d
0x0x7ff8a400216c:  650a 19

iv(11):
0x0x7ffda9dbcc90:  650a 190a 0a0a 640a 0a0a 65

AAD

aad(8):
0x0x1057907f6:  c8ee 6879 0000 0000

ICV

icv(16):
0x0x10579085e:  3f52 a522 bd6a 33e5 0739 0462 0000 0000

加密有效负载

payload_e(88):
0x0x105790806:  c8df 30c2 3f71 282b 1ee8 73de 411f a937
0x0x105790816:  fbe2 dd3a ee2d 77fd 8b5f 83d6 2523 8b7c
0x0x105790826:  22d0 6af3 2fb7 1af3 6182 e263 caa4 6a43
0x0x105790836:  12b5 fc84 52d7 1daa 2437 86a7 9dce 028e
0x0x105790846:  ee0c febe 543d 49f8 e6ca 96c1 e83b 6514
0x0x105790856:  f23a b916 ba23 58fb

我用零填充的 ICV 发送了这个数据包,但是 Linux IPsec 丢弃了这个数据包。 我错过了任何 openssl 选项吗?

我用过

EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, icv_len, icv);

使用参数 icv_len=16,但它只创建 12 字节的 ICV。

我解决了问题。所以自己回答

EVP_EncryptInit_ex(...) 之前,应插入以下代码。

EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, icv_len, NULL)

最终代码应该是这样的

static bool _aes_ccm_encrypt(uint8_t* payload, uint16_t payload_len, uint8_t* key, uint16_t key_len, uint8_t* iv, uint8_t iv_len, uint8_t* aad, uint8_t aad_len, uint8_t* icv, uint8_t icv_len) {
    EVP_CIPHER_CTX *ctx = NULL;
    int len;
    uint8_t salt_len = 3;
    uint8_t new_iv[iv_len + salt_len];

    EVP_CIPHER *cipher = NULL;
    switch(key_len) {
        case 19: cipher = EVP_aes_128_ccm(); break;
        case 27: cipher = EVP_aes_192_ccm(); break;
        case 35: cipher = EVP_aes_256_ccm(); break;
        default: break;
    }   

    // Add salt to IV
    memcpy(new_iv, key + (key_len - salt_len), salt_len);
    memcpy(new_iv + salt_len, iv, iv_len);
    iv = new_iv;
    iv_len += salt_len;

    print_hex("payload_o", payload, payload_len);
    print_hex("key", key, key_len);
    print_hex("iv", iv, iv_len);
    print_hex("aad", aad, aad_len);

    ctx = EVP_CIPHER_CTX_new();
    if(1 != EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL)
            || 1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, iv_len, NULL)
            || 1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, icv_len, NULL)
            || 1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) 
            || 1 != EVP_EncryptUpdate(ctx, NULL, &len, NULL, payload_len)
            || 1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)
            || 1 != EVP_EncryptUpdate(ctx, payload, &len, payload, payload_len)
            || 1 != EVP_EncryptFinal_ex(ctx, payload+len, &len))
        goto failure;

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, icv_len, icv);

    print_hex("icv", icv, icv_len);
    print_hex("payload_e", payload, payload_len);

    EVP_CIPHER_CTX_free(ctx);
    return true;

failure:
    ZF_LOGE("AES CCM Encryption failed");
    if(ctx)
        EVP_CIPHER_CTX_free(ctx);
    return false;
}