Can't get CFB encryption/decryption to work (probably an endianness issue)

66 Views Asked by At

I have been trying to implement CFB encryption/decryption in Zig (just testing CFB8 for now) but I keep running in to the same issue:

============ expected this output: =============  len: 64 (0x40)

CA 8E B6 37 B5 35 91 85  F1 5E F6 F1 10 E5 21 98  ...7.5...^....!.
7A 9D 9C B3 FA C2 22 76  62 CF C0 94 58 AC B0 5C  z....."vb...X..\
FF B0 71 B5 34 DD FF 3F  6F 7D 0E A8 64 A5 EF 62  ..q.4..?o}..d..b
CA 1D E2 3B 66 F5 18 49  39 24 F7 AE 08 69 D0 96  ...;f..I9$...i..

============= instead found this: ==============  len: 64 (0x40)

8E CA 37 B6 35 B5 85 91  5E F1 F1 F6 E5 10 98 21  ..7.5...^......!
9D 7A B3 9C C2 FA 76 22  CF 62 94 C0 AC 58 5C B0  .z....v".b...X\.
B0 FF B5 71 DD 34 3F FF  7D 6F A8 0E A5 64 62 EF  ...q.4?.}o...db.
1D CA 3B E2 F5 66 49 18  24 39 AE F7 69 08 96 C1  ..;..fI.$9..i...

================================================

Now, I almost immediately assumed it was an endianness issue so spent several hours messing with Zig's std lib functions to see how I could make sure it was using the right endian. However, it either got worse or just came back to this. I asked on Zig's discord and they confirmed that it was probably an endian issue as well.

My code is mainly based on this go implementation and this visual representation.

Here is my code:

pub const CFB_Size = enum(u8) {
    CFB_1 = 1,
    CFB_8 = 8,
    CFB_64 = 64,
    CFB_128 = 128,
};

pub fn CFB(comptime BlockCipher: type, comptime size: CFB_Size) type {
    const block_length = BlockCipher.block_length;

    return struct {
        const Self = @This();

        block: BlockCipher,
        block_length: usize = block_length,
        size: u7 = @truncate(@intFromEnum(size)),
        iv: getBlockSizeInt() = 0,
        cipher_text: getBlockSizeInt() = 0,
        endian: std.builtin.Endian,

        pub fn initByteArray(block: BlockCipher, iv: [block_length]u8, endian: std.builtin.Endian) Self {
            var self = Self{
                .block = block,
                .endian = endian,
            };
            self.iv = mem.bytesAsValue(getBlockSizeInt(), &iv).*;
            return self;
        }

        fn getBlockSizeInt() type {
            return if (block_length == 16) u128 else if (block_length == 32) u256 else u192;
        }

        fn getCfbSizeInt() type {
            return switch (size) {
                .CFB_1 => u1,
                .CFB_8 => u8,
                .CFB_64 => u64,
                .CFB_128 => u128,
            };
        }

        pub fn enc(self: *Self, dst: []u8, src: []const u8) void {
            const src_sections = mem.bytesAsSlice(getCfbSizeInt(), src);
            var dst_sections = mem.bytesAsSlice(getCfbSizeInt(), dst);

            for (0..src_sections.len) |i| {
                self.block.encrypt(mem.asBytes(&self.cipher_text), mem.asBytes(&self.iv));
                // Get new cipher text
                const dst_value = src_sections[i] ^ @as(getCfbSizeInt(), @truncate(self.cipher_text));
                dst_sections[i] = dst_value;
                // Add to iv
                self.iv >>= self.size;
                self.iv |= @as(getBlockSizeInt(), dst_value) << @truncate(block_length * 8 - self.size);
            }
        }
    };
}
0

There are 0 best solutions below