Bcrypt - AES256 HMAC

Source

Source of encrypt function

C
char *aes256_hmac_encrypt(const char *input, const unsigned char *key, const unsigned char *iv, size_t *output_len) {
    // Use simple, well-tested libraries for encryption to match Python behavior
    // 1. Create buffer for output (IV + ciphertext + HMAC)
    size_t input_len = strlen(input);
    size_t padded_len = ((input_len / 16) + 1) * 16; // Round up to multiple of 16
    *output_len = 16 + padded_len + 32; // IV + padded ciphertext + HMAC
    unsigned char *output = (unsigned char *)malloc(*output_len);
    if (!output) return NULL;

    // 2. Copy IV to the beginning of output
    memcpy(output, iv, 16);

    // 3. Encrypt using CBC mode (with proper padding)
    // Initialize AES
    BCRYPT_ALG_HANDLE hAlgAES = NULL;
    BCRYPT_KEY_HANDLE hKeyAES = NULL;
    DWORD cbKeyObject = 0, cbData = 0;
    void *pbKeyObject = NULL;

    NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgAES, BCRYPT_AES_ALGORITHM, NULL, 0);
    if ((long)(status) < 0) {
        free(output);
        return NULL;
    }

    // Set CBC mode
    status = BCryptSetProperty(hAlgAES, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CBC,
                                sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
    if ((long)(status) < 0) {
        BCryptCloseAlgorithmProvider(hAlgAES, 0);
        free(output);
        return NULL;
    }

    // Get key object size
    status = BCryptGetProperty(hAlgAES, BCRYPT_OBJECT_LENGTH, (PUCHAR)&cbKeyObject,
                                sizeof(DWORD), &cbData, 0);
    if ((long)(status) < 0) {
        BCryptCloseAlgorithmProvider(hAlgAES, 0);
        free(output);
        return NULL;
    }

    // Allocate key object
    pbKeyObject = malloc(cbKeyObject);
    if (!pbKeyObject) {
        BCryptCloseAlgorithmProvider(hAlgAES, 0);
        free(output);
        return NULL;
    }

    // Generate key
    status = BCryptGenerateSymmetricKey(hAlgAES, &hKeyAES, pbKeyObject, cbKeyObject,
                                         (PUCHAR)key, 32, 0);
    if ((long)(status) < 0) {
        free(pbKeyObject);
        BCryptCloseAlgorithmProvider(hAlgAES, 0);
        free(output);
        return NULL;
    }

    // Create a copy of IV for encryption since BCryptEncrypt modifies it
    unsigned char iv_copy[16];
    memcpy(iv_copy, iv, 16);

    // Encrypt data
    DWORD cbCipherText = 0;
    status = BCryptEncrypt(hKeyAES, (PUCHAR)input, input_len, NULL, iv_copy, 16,
                            output + 16, padded_len, &cbCipherText, BCRYPT_BLOCK_PADDING);

    // Clean up AES
    BCryptDestroyKey(hKeyAES);
    free(pbKeyObject);
    BCryptCloseAlgorithmProvider(hAlgAES, 0);

    if ((long)(status) < 0) {
        free(output);
        return NULL;
    }

    // 4. Generate HMAC over (IV + ciphertext)
    BCRYPT_ALG_HANDLE hAlgHMAC = NULL;
    BCRYPT_HASH_HANDLE hHash = NULL;
    DWORD cbHashObject = 0;
    void *pbHashObject = NULL;

    status = BCryptOpenAlgorithmProvider(&hAlgHMAC, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
    if ((long)(status) < 0) {
        free(output);
        return NULL;
    }

    // Get hash object size
    status = BCryptGetProperty(hAlgHMAC, BCRYPT_OBJECT_LENGTH, (PUCHAR)&cbHashObject,
                                sizeof(DWORD), &cbData, 0);
    if ((long)(status) < 0) {
        BCryptCloseAlgorithmProvider(hAlgHMAC, 0);
        free(output);
        return NULL;
    }

    // Allocate hash object if needed
    if (cbHashObject > 0) {
        pbHashObject = malloc(cbHashObject);
        if (!pbHashObject) {
            BCryptCloseAlgorithmProvider(hAlgHMAC, 0);
            free(output);
            return NULL;
        }
    }

    // Create hash
    status = BCryptCreateHash(hAlgHMAC, &hHash, pbHashObject, cbHashObject,
                               (PUCHAR)key, 32, 0);
    if ((long)(status) < 0) {
        if (pbHashObject) free(pbHashObject);
        BCryptCloseAlgorithmProvider(hAlgHMAC, 0);
        free(output);
        return NULL;
    }

    // Hash IV
    status = BCryptHashData(hHash, output, 16, 0);
    if ((long)(status) < 0) {
        BCryptDestroyHash(hHash);
        if (pbHashObject) free(pbHashObject);
        BCryptCloseAlgorithmProvider(hAlgHMAC, 0);
        free(output);
        return NULL;
    }

    // Hash ciphertext
    status = BCryptHashData(hHash, output + 16, cbCipherText, 0);
    if ((long)(status) < 0) {
        BCryptDestroyHash(hHash);
        if (pbHashObject) free(pbHashObject);
        BCryptCloseAlgorithmProvider(hAlgHMAC, 0);
        free(output);
        return NULL;
    }

    // Finalize HMAC
    status = BCryptFinishHash(hHash, output + 16 + cbCipherText, 32, 0);

    // Clean up HMAC
    BCryptDestroyHash(hHash);
    if (pbHashObject) free(pbHashObject);
    BCryptCloseAlgorithmProvider(hAlgHMAC, 0);

    if ((long)(status) < 0) {
        free(output);
        return NULL;
    }

    // Update output length based on actual ciphertext size
    *output_len = 16 + cbCipherText + 32;

    return (char *)output;
}

Source of decrypt function

C

char *aes256_hmac_decrypt(const char *input, size_t input_len, const unsigned char *key, size_t *output_len) {
    if (input_len < 16 + 32) return NULL; // Input must at least have IV (16) + HMAC (32)

    BCRYPT_ALG_HANDLE hAlgAES = NULL, hAlgHMAC = NULL;
    BCRYPT_KEY_HANDLE hKeyAES = NULL;
    BCRYPT_HASH_HANDLE hHash = NULL;
    DWORD cbKeyObject = 0, cbData = 0, cbPlainText = 0, cbHashObject = 0;
    void *pbKeyObject = NULL, *pbPlainText = NULL, *computed_hmac = NULL, *pbHashObject = NULL;
    char *output = NULL;
    NTSTATUS status;

    // Use same key for both AES and HMAC (first 32 bytes only)
    const unsigned char *aes_key = key;
    const unsigned char *hmac_key = key;  // Changed: use same key

    // Extract components from input
    const unsigned char *iv = (const unsigned char *) input;
    size_t ciphertext_len = input_len - 16 - 32;
    const unsigned char *ciphertext = (const unsigned char *) input + 16;
    const unsigned char *provided_hmac = (const unsigned char *) input + 16 + ciphertext_len;

    // Open AES provider
    status = BCryptOpenAlgorithmProvider(&hAlgAES, BCRYPT_AES_ALGORITHM, NULL, 0);
    if ((long) (status) < 0) return NULL;

    // Set AES to CBC mode (same as encrypt function)
    status = BCryptSetProperty(hAlgAES, BCRYPT_CHAINING_MODE, (PUCHAR) BCRYPT_CHAIN_MODE_CBC,
                                sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
    if ((long) (status) < 0) goto cleanup;

    // Open HMAC-SHA256 provider
    status = BCryptOpenAlgorithmProvider(&hAlgHMAC, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
    if ((long) (status) < 0) goto cleanup;

    // Get HMAC hash object size
    status = BCryptGetProperty(hAlgHMAC, BCRYPT_OBJECT_LENGTH, (PUCHAR) &cbHashObject,
                                sizeof(DWORD), &cbData, 0);
    if ((long) (status) < 0) goto cleanup;

    // Allocate hash object if needed
    if (cbHashObject > 0) {
        pbHashObject = malloc(cbHashObject);
        if (!pbHashObject) goto cleanup;
    }

    // Create HMAC hash
    status = BCryptCreateHash(hAlgHMAC, &hHash, pbHashObject, cbHashObject,
                               (PUCHAR) hmac_key, 32, 0);
    if ((long) (status) < 0) goto cleanup;

    // Hash the IV
    status = BCryptHashData(hHash, (PUCHAR) iv, 16, 0);
    if ((long) (status) < 0) goto cleanup;

    // Hash the ciphertext
    status = BCryptHashData(hHash, (PUCHAR) ciphertext, ciphertext_len, 0);
    if ((long) (status) < 0) goto cleanup;

    // Allocate HMAC output buffer
    computed_hmac = malloc(32);
    if (!computed_hmac) goto cleanup;

    // Finalize HMAC
    status = BCryptFinishHash(hHash, computed_hmac, 32, 0);
    if ((long) (status) < 0) goto cleanup;

    // Verify HMAC
    if (memcmp(computed_hmac, provided_hmac, 32) != 0) {
        // HMAC verification failed - data may have been tampered with
        goto cleanup;
    }

    // Get AES key object size
    status = BCryptGetProperty(hAlgAES, BCRYPT_OBJECT_LENGTH, (PUCHAR) &cbKeyObject,
                                sizeof(DWORD), &cbData, 0);
    if ((long) (status) < 0) goto cleanup;

    // Allocate key object
    pbKeyObject = malloc(cbKeyObject);
    if (!pbKeyObject) goto cleanup;

    // Generate AES key
    status = BCryptGenerateSymmetricKey(hAlgAES, &hKeyAES, pbKeyObject, cbKeyObject,
                                         (PUCHAR) aes_key, 32, 0);
    if ((long) (status) < 0) goto cleanup;

    // Create a copy of IV for decryption since BCryptDecrypt modifies it
    unsigned char iv_copy[16];
    memcpy(iv_copy, iv, 16);

    // Get plaintext size
    status = BCryptDecrypt(hKeyAES, (PUCHAR) ciphertext, ciphertext_len, NULL, iv_copy, 16,
                            NULL, 0, &cbPlainText, BCRYPT_BLOCK_PADDING);
    if ((long) (status) < 0) goto cleanup;

    // Allocate plaintext buffer with space for null terminator
    pbPlainText = malloc(cbPlainText + 1);
    if (!pbPlainText) goto cleanup;

    // Reset IV copy for actual decryption
    memcpy(iv_copy, iv, 16);

    // Decrypt the data with CBC mode and padding
    status = BCryptDecrypt(hKeyAES, (PUCHAR) ciphertext, ciphertext_len, NULL, iv_copy, 16,
                            pbPlainText, cbPlainText, &cbData, BCRYPT_BLOCK_PADDING);
    if ((long) (status) < 0) goto cleanup;

    // Add null terminator
    ((unsigned char *) pbPlainText)[cbData] = 0;

    *output_len = cbData;
    output = (char *) pbPlainText;
    pbPlainText = NULL; // Prevent freeing in cleanup

    cleanup:
    if (hKeyAES) BCryptDestroyKey(hKeyAES);
    if (hHash) BCryptDestroyHash(hHash);
    if (hAlgAES) BCryptCloseAlgorithmProvider(hAlgAES, 0);
    if (hAlgHMAC) BCryptCloseAlgorithmProvider(hAlgHMAC, 0);
    if (pbKeyObject) free(pbKeyObject);
    if (pbHashObject) free(pbHashObject);
    if (pbPlainText) free(pbPlainText);
    if (computed_hmac) free(computed_hmac);

    return output;
}