copensslaesevp-cipher

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


I'm trying to encrypt my ICMP packet with AES128-CCM16. I used c openssl library for encryption. But encrypted result is wrong!

I used two Linux 18.04 VM for simulating ESP packet with strongswan IPsec. I captured ESP packet and printed my whole variables in my decrypt function.

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;
}

This is my captured packet

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

And this is my printed variables

Original payload

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

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

IV

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

Encrypted payload

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

I sent this packets with zero-filled ICV but the Linux IPsec dropped this packets. Is there any openssl options I missed?

I used

EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, icv_len, icv);

with parameters icv_len=16, but it creates only 12 byte ICV.


Solution

  • I solved problem. So answer myself

    Before EVP_EncryptInit_ex(...), below code should be inserted.

    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, icv_len, NULL)
    

    Final code should be like this

    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;
    }