intel / intel-ipsec-mb

Intel(R) Multi-Buffer Crypto for IPSec
BSD 3-Clause "New" or "Revised" License
292 stars 88 forks source link

EEA3(ZUC) 1 Buffer implementation LFSR update can result in invalid LFSR state, causing incorrect keystream generation #144

Closed simscott85 closed 7 months ago

simscott85 commented 8 months ago

The modulus operation here:

https://github.com/intel/intel-ipsec-mb/blob/12cfc770bd6600d1cf48bf4edfb70d4d64521bdc/lib/x86_64/zuc_common.asm#L240

can result in s16 being calculated as 0. It should be set to 2^31 - 1 (0x7fffffff) if 0 as per section 3.2 of the specification (https://www.gsma.com/aboutus/wp-content/uploads/2014/12/eea3eia3zucv16.pdf). This is not taken into account in the current implementation of the LFSR_UPDT macro.

An example key and IV combination which causes incorrect keystream generation:

Key={0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}

IV:={0xcf, 0x50, 0x72, 0x4b, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x50, 0x72, 0x4b, 0x00, 0x00, 0x00, 0x00} (Bearer=0, Direction=0, COUNT=3478155851)

tkanteck commented 8 months ago

Thanks for the contact. Yes, we have seen it before and we also noticed that all implementations, including reference implementation from the above spec, do not do perform this operation. It feels that either the reference implementation is missing this operation or section of the spec was not updated (operation no longer needed). We decided to stay in alignment with the reference implementation from the spec.

simscott85 commented 8 months ago

Thanks for the prompt reply. I agree that there is no explicit comparison with 0 in the reference implementation, however I think it's a different circumstance that may result in s16 = 0. In any case, the 1 buffer implementation here does not produce the same keystream as the reference implementation. With the above parameters (key, iv) the 4 buffer AVX2, 8 buffer AVX2, and reference implementations all produce the same keystream and calculate s16 in the 10th iteration of LFSRUpdateWithWorkMode to be 0x7fffffff. It is only the 1 buffer implementation that calculates s16 to be 0.

This can be demonstrated by encrypting 5 or 13 buffers using IMB_ZUC_EEA3_N_BUFFER, all with the same key/IV as above. The 5th or 13th output buffer will contain different ciphertext to the other buffers (which all match the ciphertext produced by the reference implementation).

EDIT:

Actually, after looking again at the reference implementation I don't think it is possible for s16 to ever be calculated as 0 in LFSRWithWorkMode(). I think the only way this would be possible is if s0 already contained the value 0, which I don't believe should be possible, though I'm happy to be proven incorrect.

tkanteck commented 8 months ago

Thanks. We'll have a look into the 1 buffer implementation.

tkanteck commented 7 months ago

Could you me help re-produce the problem, please?

I have changed content of zuc_eea3_128.json.c file with two vectors with same IV but provided in different formats (16-byte long and 6-byte short)the last one). I think this is the vector you mentioned was causing the problem:

const struct cipher_test zuc_eea3_128_test_json[] = {
        { 48, 128, 201,
          "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
          "\x4b\x72\x50\xcf\x00\x00", /* 48-bit iv - expanded in the test code to 128-bit IV with zuc_eea3_iv_gen() */
          "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
          "\xF5\x55\x33\x65\x01\x31\x2E\xD7\x72\x08\xC8\xFC\x30\xB5\xA4\x4A",
          1, 128 },
        { 128, 128, 202,
          "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
          "\xcf\x50\x72\x4b\x00\x00\x00\x00\xcf\x50\x72\x4b\x00\x00\x00\x00", /* 128-bit iv */
          "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
          "\xF5\x55\x33\x65\x01\x31\x2E\xD7\x72\x08\xC8\xFC\x30\xB5\xA4\x4A",
          1, 128 },
       { 0, 0, 0, NULL, NULL, NULL, NULL, 0, 0 }
};

It looks that all implementations produce the same result. Could you share failing vector to help me reproduce the problem?

simscott85 commented 7 months ago

I'm not familiar with these unit tests, but here is some code that reproduces the issue:

void ReproduceEea3Issue()
{
#define NUM_SDUS 5
#define SDU_LENGTH 1500
#define COUNT 3478155851

    IMB_MGR *mgr = alloc_mb_mgr(0);
    init_mb_mgr_avx2(mgr);

    const uint8_t bearer = 0;
    const uint8_t direction = 0;
    const uint8_t cipherKey[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

    uint8_t *key[NUM_SDUS];
    uint32_t length[NUM_SDUS] = {};
    uint8_t *IV[NUM_SDUS] = {};
    uint8_t *plainText[NUM_SDUS] = {};
    uint8_t *cipherText[NUM_SDUS] = {};

    uint8_t iv[16];
    iv[0] = (COUNT >> 24) & 0xFF;
    iv[1] = (COUNT >> 16) & 0xFF;
    iv[2] = (COUNT >> 8) & 0xFF;
    iv[3] = COUNT & 0xFF;

    iv[4] = ((bearer << 3) | ((direction & 1) << 2)) & 0xFC;
    iv[5] = 0;
    iv[6] = 0;
    iv[7] = 0;

    iv[8] = iv[0];
    iv[9] = iv[1];
    iv[10] = iv[2];
    iv[11] = iv[3];

    iv[12] = iv[4];
    iv[13] = iv[5];
    iv[14] = iv[6];
    iv[15] = iv[7];

    printf("Key:");
    for (uint8_t i = 0; i < 16; i++) printf(" 0x%02x", cipherKey[i]);
    printf("\nIV: ");
    for (uint8_t i = 0; i < 16; i++) printf(" 0x%02x", iv[i]);
    printf("\n");

    for (uint8_t i = 0; i < NUM_SDUS; i++)
    {
        key[i] = new uint8_t[16];
        IV[i] = new uint8_t[16];
        plainText[i] = new uint8_t[SDU_LENGTH];
        cipherText[i] = new uint8_t[SDU_LENGTH];

        length[i] = SDU_LENGTH;

        memset(plainText[i], 0, SDU_LENGTH);
        memset(cipherText[i], 0, SDU_LENGTH);

        memcpy(IV[i], iv, 16);
        memcpy(key[i], cipherKey, 16);
    }

    IMB_ZUC_EEA3_N_BUFFER(mgr, key, IV, &plainText[0], &cipherText[0], length, NUM_SDUS);

    for (uint8_t i = 1; i < NUM_SDUS; i++)
    {
        if (memcmp(cipherText[0], cipherText[i], SDU_LENGTH) != 0)
        {
            printf("Cipher Text %u does not match 0!\n", i);
        }
        else
        {
            printf("Cipher Text %u OK!\n", i);
        }
    }

    for (uint8_t i = 0; i < NUM_SDUS; i++)
    {
        delete[] key[i];
        delete[] IV[i];
        delete[] plainText[i];
        delete[] cipherText[i];
    }
}

And this is the output:

Key: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
IV:  0xcf 0x50 0x72 0x4b 0x00 0x00 0x00 0x00 0xcf 0x50 0x72 0x4b 0x00 0x00 0x00 0x00
Cipher Text 1 OK!
Cipher Text 2 OK!
Cipher Text 3 OK!
Cipher Text 4 does not match 0!

This is using commit 15ae7608cbc14b698278c2afdd6400bf929e9b20, and running on Linux.

tkanteck commented 7 months ago

Thanks. This is helpful