CTR-AES256 Encrypt does not match OpenSSL -aes-256-ctr

时光怂恿深爱的人放手 提交于 2019-12-11 05:03:55

问题


My problem is that I cannot get the AES 256 CTR output from the C code below to match the output from the OpenSSL command below.

The C code produces this:

5f b7 18 d1 28 62 7f 50 35 ba e9 67 a7 17 ab 22
f9 e4 09 ce 23 26 7b 93 82 02 d3 87 eb 01 26 ac
96 2c 01 8c c8 af f3 de a4 18 7f 29 46 00 2e 00

The OpenSSL command line produces this:

5f b7 18 d1 28 62 7f 50 35 ba e9 67 a7 17 ab 22
3c 01 11 bd 39 14 74 76 31 57 a6 53 f9 00 09 b4
6f a9 49 bc 6d 00 77 24 2d ef b9 c4

Notice the first 16 bytes are the same because the nonceIV was the same, however, when the nonceIV is updated on the next iteration, then XOR'd with the plaintext, the next 16 bytes differ and so on...?

I cannot understand why that happens? Anyone know why the hex codes are different after the first 16 byte chunk?

Disclaimer: I'm no C expert.

Thanks!!

Fox.txt

The quick brown fox jumped over the lazy dog

Then run the following OpenSSL command to create foxy.exe

openssl enc -aes-256-ctr -in fox.txt -out foxy.exe -K 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4 -iv f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff -nosalt -nopad -p

Here's what foxy.exe contains:

5f b7 18 d1 28 62 7f 50 35 ba e9 67 a7 17 ab 22
3c 01 11 bd 39 14 74 76 31 57 a6 53 f9 00 09 b4
6f a9 49 bc 6d 00 77 24 2d ef b9 c4

Here's the code.

    #include <Windows.h>

    // What is AES CTR
    //
    // AES - CTR (counter) mode is another popular symmetric encryption algorithm.
    //
    // It is advantageous because of a few features :
    // 1. The data size does not have to be multiple of 16 bytes.
    // 2. The encryption or decryption for all blocks of the data can happen in parallel, allowing faster implementation.
    // 3. Encryption and decryption use identical implementation.
    //
    // Very important note : choice of initial counter is critical to the security of CTR mode.
    // The requirement is that the same counter and AES key combination can never to used to encrypt more than more one 16 - byte block.

    // Notes
    // -----
    // * CTR mode does not require padding to block boundaries.
    //
    // * The IV size of AES is 16 bytes.
    //
    // * CTR mode doesn't need separate encrypt and decrypt method. Encryption key can be set once. 
    //
    // * AES is a block cipher : it takes as input a 16 byte plaintext block,
    //   a secret key (16, 24 or 32 bytes) and outputs another 16 byte ciphertext block.
    //
    // References
    // ----------
    // https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
    // https://www.cryptopp.com/wiki/CTR_Mode#Counter_Increment
    // https://modexp.wordpress.com/2016/03/10/windows-ctr-mode-with-crypto-api/
    // https://msdn.microsoft.com/en-us/library/windows/desktop/jj650836(v=vs.85).aspx
    // http://www.cryptogrium.com/aes-ctr.html
    // http://www.bierkandt.org/encryption/symmetric_encryption.php


    #define IV_SIZE 16
    #define AES_BLOCK_SIZE 16

    typedef struct _key_hdr_t {
        PUBLICKEYSTRUC hdr;            // Indicates the type of BLOB and the algorithm that the key uses.
        DWORD          len;            // The size, in bytes, of the key material.
        char           key[32];        // The key material.
    } key_hdr;


    // NIST specifies two types of counters.
    //
    // First is a counter which is made up of a nonce and counter.
    // The nonce is random, and the remaining bytes are counter bytes (which are incremented).
    // For example, a 16 byte block cipher might use the high 8 bytes as a nonce, and the low 8 bytes as a counter.
    //
    // Second is a counter block, where all bytes are counter bytes and can be incremented as carries are generated.
    // For example, in a 16 byte block cipher, all 16 bytes are counter bytes.
    //
    // This uses the second method, which means the entire byte block is treated as counter bytes.

    void IncrementCounterByOne(char *inout)
    {
        int i;

        for (i = 16 - 1; i >= 0; i--) {
            inout[i]++;
            if (inout[i]) {
                break;
            }
        }
    }


    void XOR(char *plaintext, char *ciphertext, int plaintext_len)
    {
        int i;

        for (i = 0; i < plaintext_len; i++)
        {
            plaintext[i] ^= ciphertext[i];
        }
    }


    unsigned int GetAlgorithmIdentifier(unsigned int aeskeylenbits)
    {
        switch (aeskeylenbits)
        {
        case 128:
            return CALG_AES_128;
        case 192:
            return CALG_AES_192;
        case 256:
            return CALG_AES_256;
        default:
            return 0;
        }
    }


    unsigned int GetKeyLengthBytes(unsigned int aeskeylenbits)
    {
        return aeskeylenbits / 8;
    }


    void SetKeyData(key_hdr *key, unsigned int aeskeylenbits, char *pKey)
    {
        key->hdr.bType = PLAINTEXTKEYBLOB;
        key->hdr.bVersion = CUR_BLOB_VERSION;
        key->hdr.reserved = 0;
        key->hdr.aiKeyAlg = GetAlgorithmIdentifier(aeskeylenbits);
        key->len = GetKeyLengthBytes(aeskeylenbits);
        memmove(key->key, pKey, key->len);
    }

    // point = pointer to the start of the plaintext, extent is the size (44 bytes)
    void __stdcall AESCTR(char *point, int extent, char *pKey, char *pIV, unsigned int aeskeylenbits, char *bufOut)
    {
        HCRYPTPROV hProv;
        HCRYPTKEY  hSession;
        key_hdr    key;
        DWORD      IV_len;
        div_t      aesblocks;
        char       nonceIV[64];
        char       tIV[64];
        char       *bufIn;

        bufIn = point;

        memmove(nonceIV, pIV, IV_SIZE);

        SetKeyData(&key, aeskeylenbits, pKey);

        CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT | CRYPT_SILENT);

        CryptImportKey(hProv, (PBYTE)&key, sizeof(key), 0, CRYPT_NO_SALT, &hSession);

        aesblocks = div(extent, AES_BLOCK_SIZE);

        while (aesblocks.quot != 0)
        {
            IV_len = IV_SIZE;
            memmove(tIV, nonceIV, IV_SIZE);
            CryptEncrypt(hSession, 0, FALSE, 0, (BYTE *)tIV, &IV_len, sizeof(tIV));
            XOR(bufIn, tIV, AES_BLOCK_SIZE);
            IncrementCounterByOne(nonceIV);
            bufIn += AES_BLOCK_SIZE;
            aesblocks.quot--;
        }

        if (aesblocks.rem != 0)
        {
            memmove(tIV, nonceIV, IV_SIZE);
            CryptEncrypt(hSession, 0, TRUE, 0, (BYTE *)tIV, &IV_len, sizeof(tIV));
            XOR(bufIn, tIV, aesblocks.rem);
        }

        memmove(bufOut, point, extent);

        CryptDestroyKey(hSession);
        CryptReleaseContext(hProv, 0);
    }

I was able to get this working by the suggested pseudocode on the M$ CryptEncrypt() remarks section https://msdn.microsoft.com/en-us/library/windows/desktop/aa379924(v=vs.85).aspx:

// Set the IV for the original key. Do not use the original key for 
// encryption or decryption after doing this because the key's 
// feedback register will get modified and you cannot change it.
CryptSetKeyParam(hOriginalKey, KP_IV, newIV)

while(block = NextBlock())
{
    // Create a duplicate of the original key. This causes the 
    // original key's IV to be copied into the duplicate key's 
    // feedback register.
    hDuplicateKey = CryptDuplicateKey(hOriginalKey)

    // Encrypt the block with the duplicate key.
    CryptEncrypt(hDuplicateKey, block)

    // Destroy the duplicate key. Its feedback register has been 
    // modified by the CryptEncrypt function, so it cannot be used
    // again. It will be re-duplicated in the next iteration of the 
    // loop.
    CryptDestroyKey(hDuplicateKey)
}

Here's the updated code with the two new lines added:

HCRYPTKEY  hDuplicateKey;
boolean    final;

while (aesblocks.quot != 0)
{
    CryptDuplicateKey(hOriginalKey, NULL, 0, &hDuplicateKey);
    IV_len = IV_SIZE;
    memmove(tIV, nonceIV, IV_len);
    final = (aesblocks.quot == 1 && aesblocks.rem == 0) ? TRUE : FALSE;
    CryptEncrypt(hDuplicateKey, 0, final, 0, (BYTE *)tIV, &IV_len, sizeof(tIV));
    XOR(bufIn, tIV, AES_BLOCK_SIZE);
    IncrementCounterByOne(nonceIV);
    bufIn += AES_BLOCK_SIZE;
    aesblocks.quot--;
    CryptDestroyKey(hDuplicateKey);
}

if (aesblocks.rem != 0)
{
    CryptDuplicateKey(hOriginalKey, NULL, 0, &hDuplicateKey);
    final = TRUE;
    memmove(tIV, nonceIV, IV_SIZE);
    CryptEncrypt(hDuplicateKey, 0, final, 0, (BYTE *)tIV, &IV_len, sizeof(tIV));
    XOR(bufIn, tIV, aesblocks.rem);
    CryptDestroyKey(hDuplicateKey);
}

回答1:


I'm not familiar with the Microsoft APIs, but I believe that CryptEncrypt() uses CBC mode by default - so the output from the first block of encryption is automatically being fed into the input for the second block. You are building CTR mode yourself form scratch (which incidentally is generally not an advisable thing to do - you should use the capabilities of crypto libraries rather than "roll your own" crypto). To get the expected output you probably need to get CryptEncrypt to use AES in ECB mode - which I believe can be done using CryptptSetKeyParam (https://msdn.microsoft.com/en-us/library/aa380272.aspx) and setting KP_MODE to CRYPT_MODE_ECB.




回答2:


Make sure your input file doesn't contain any extra characters like new line etc. Openssl will include those extra characters while encrypting.



来源:https://stackoverflow.com/questions/42847568/ctr-aes256-encrypt-does-not-match-openssl-aes-256-ctr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!