kokke/tiny-AES-c

tiny-AES-c AES256 CTR interoperability

naquad opened this issue · 3 comments

naquad commented

I'm experiencing an issue with the latest tiny-AES-c and go1.20.6 decryption for AES256 in CTR mode. I didn't find any related information in the docs or the comments. Please clarify if the library is interoperable or am I doing something wrong?

Thank you.

Test C code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define AES256 1
#define CTR 1
#define CBC 0
#define ECB 0

#include "aes.h"  // Tiny-AES-c

#define BLOCK_SIZE AES_BLOCKLEN

// Function to print data as hex
void print_hex(FILE *f, const char *title, uint8_t* data, size_t length) {
    if (title) {
        fprintf(f, "%s: ", title);
    }

    for (size_t i = 0; i < length; i++) {
        fprintf(f, "%02x", data[i]);
    }

    fprintf(f, "\n");
}

int main() {
    // Generate a random IV
    uint8_t iv[AES_BLOCKLEN];
    for (size_t i = 0; i < BLOCK_SIZE; i++) {
        iv[i] = rand() % 256;
    }

    char* input = "MY KEY 123456789";

    // Generate sequential key
    uint8_t key[AES_KEYLEN];
    for (uint8_t i = 0; i < AES_KEYLEN; ++i) {
        key[i] = i;
    }

    struct AES_ctx ctx;
    AES_init_ctx_iv(&ctx, key, iv);

    // Encrypt the string
    uint8_t output[1024];
    size_t length = strlen(input);
    memcpy(output, input, length);
    AES_CTR_xcrypt_buffer(&ctx, output, length);

    // Print the IV, key, and encrypted data
    print_hex(stderr, "IV    ", iv, BLOCK_SIZE);
    print_hex(stderr, "Key   ", key, AES_KEYLEN);
    print_hex(stderr, "Cipher", output, strlen(input));

    // Pass to the decryptor
    print_hex(stdout, NULL, iv, BLOCK_SIZE);
    print_hex(stdout, NULL, key, AES_KEYLEN);
    print_hex(stdout, NULL, output, strlen(input));
    return 0;
}

Test Go code:

package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"encoding/hex"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	// Read the IV from stdin
	scanner.Scan()
	ivtxt := scanner.Text()

	iv, err := hex.DecodeString(ivtxt)
	if err != nil {
		panic(err)
	}

	// Read the key from stdin
	scanner.Scan()
	keytxt := scanner.Text()
	key, err := hex.DecodeString(keytxt)
	if err != nil {
		panic(err)
	}

	// Read the encrypted data from stdin
	scanner.Scan()
	data := scanner.Text()
	fmt.Printf("IV    : %s\n", ivtxt)
	fmt.Printf("Key   : %s\n", keytxt)
	fmt.Printf("Cipher: %s\n", data)
	encryptedData, err := hex.DecodeString(data)
	if err != nil {
		panic(err)
	}

	// Create a new AES cipher block
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	// Create a new CTR (Counter) mode stream
	stream := cipher.NewCTR(block, iv)

	// Decrypt the data
	stream.XORKeyStream(encryptedData, encryptedData)

	// Print the decrypted data
	fmt.Printf("Decrypted Data: %s\n", string(encryptedData))
}

The output:

$ gcc -o crypt ./crypt.c aes.c
$ ./crypt | go run decrypt.go
./crypt | go run ./decrypt.go
IV    : 67c6697351ff4aec29cdbaabf2fbe346
Key   : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Cipher: b5e844a448c8441e7d14c59b15885df1
IV    : 67c6697351ff4aec29cdbaabf2fbe346
Key   : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Cipher: b5e844a448c8441e7d14c59b15885df1
Decrypted Data: >�a��K���ђ�r�

The same issue is observed using python:

import sys
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import codecs

ivtxt = sys.stdin.readline().strip()
iv = codecs.decode(ivtxt, 'hex')
keytxt = sys.stdin.readline().strip()
key = codecs.decode(keytxt, 'hex')
data = sys.stdin.readline().strip()
encrypted_data = codecs.decode(data, 'hex')

# Create a new AES cipher
cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend())
decryptor = cipher.decryptor()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()

# Print the decrypted data
print('IV    :', ivtxt)
print('Key   :', keytxt)
print('Cipher:', data)
print("Decrypted Data:", decrypted_data)

The output:

$ ./crypt | python3 decrypt.py
IV    : 67c6697351ff4aec29cdbaabf2fbe346
Key   : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Cipher: b5e844a448c8441e7d14c59b15885df1
IV    : 67c6697351ff4aec29cdbaabf2fbe346
Key   : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Cipher: b5e844a448c8441e7d14c59b15885df1
Decrypted Data: b'>\xe1a\x98\x07\x8eK\x83\xfa\x8a\x1d\xd1\x92\xe6r\xc5'
naquad commented

To make sure it is not a Go/Python error:

import sys
from random import randint
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import codecs

iv = bytes([randint(0, 255) for _ in range(16)])
key = bytes(range(32))

# Create a new AES cipher
cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend())
decryptor = cipher.encryptor()
crypto = decryptor.update(b'MY TEST INPUT') + decryptor.finalize()

print(codecs.encode(iv, 'hex').decode('utf-8'))
print(codecs.encode(key, 'hex').decode('utf-8'))
print(codecs.encode(crypto, 'hex').decode('utf-8'))

The output:

$ python3 crypt.py | go run ./decrypt.go
IV    : 917b2bc01beaf40dc5e14ab387f3ee59
Key   : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Cipher: fc9908431c8cdf134153a6cb78
Decrypted Data: MY TEST INPUT
naquad commented

The same code with AES128 is working as expected.

naquad commented

Ok, after reading the library code skip all this. For those who hit the same issue:

aes.c includes aes.h and all defines should be either in the aes.h or on the command line (gcc -DAES256=1).
Updating the aes.h and uncommeting #define AES256 1 fixes the issue.

My apologies for the confusion.