lib/reticulum/src/crypto/cbc.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const block_length: u8 = 16;
4 pub const max_input_length: u16 = 65_520;
5
6 pub const CbcError = error{
7 InputTooLong,
8 InvalidLength,
9 OutputTooSmall,
10 OverlappingBuffers,
11 };
12
13 /// AES-128 in cipher block chaining mode for the sealed message format this
14 /// package encrypts into (*token*) under a 32-byte key, following
15 /// Reticulum@1.5.0 RNS/Cryptography/AES.py:43-76.
16 pub const Aes128Cbc = Cbc(std.crypto.core.aes.Aes128);
17 /// AES-256 in cipher block chaining mode for the sealed message format this
18 /// package encrypts into (*token*) under a 64-byte key, following
19 /// Reticulum@1.5.0 RNS/Cryptography/AES.py:78-111.
20 pub const Aes256Cbc = Cbc(std.crypto.core.aes.Aes256);
21
22 fn Cbc(comptime Cipher: type) type {
23 return struct {
24 pub const Key = [Cipher.key_bits / 8]u8;
25
26 pub fn encrypt(
27 key: Key,
28 iv: [block_length]u8,
29 plaintext: []const u8,
30 out: []u8,
31 ) CbcError![]u8 {
32 const result = try checkedOutput(plaintext, out);
33 const cipher = Cipher.initEnc(key);
34 var chain = iv;
35 var offset: usize = 0;
36 while (offset < plaintext.len) : (offset += block_length) {
37 var block = plaintext[offset..][0..block_length].*;
38 xor(&block, chain);
39 cipher.encrypt(result[offset..][0..block_length], &block);
40 chain = result[offset..][0..block_length].*;
41 }
42 return result;
43 }
44
45 pub fn decrypt(
46 key: Key,
47 iv: [block_length]u8,
48 ciphertext: []const u8,
49 out: []u8,
50 ) CbcError![]u8 {
51 const result = try checkedOutput(ciphertext, out);
52 const cipher = Cipher.initDec(key);
53 var chain = iv;
54 var offset: usize = 0;
55 while (offset < ciphertext.len) : (offset += block_length) {
56 const encrypted = ciphertext[offset..][0..block_length].*;
57 var block: [block_length]u8 = undefined;
58 cipher.decrypt(&block, &encrypted);
59 xor(&block, chain);
60 result[offset..][0..block_length].* = block;
61 chain = encrypted;
62 }
63 return result;
64 }
65 };
66 }
67
68 fn checkedOutput(input: []const u8, out: []u8) CbcError![]u8 {
69 if (input.len > max_input_length) return error.InputTooLong;
70 if (input.len % block_length != 0) return error.InvalidLength;
71 if (out.len < input.len) return error.OutputTooSmall;
72 const result = out[0..input.len];
73 if (partiallyOverlaps(input, result)) return error.OverlappingBuffers;
74 return result;
75 }
76
77 fn partiallyOverlaps(input: []const u8, output: []u8) bool {
78 if (input.len == 0) return false;
79 const input_start = @intFromPtr(input.ptr);
80 const output_start = @intFromPtr(output.ptr);
81 if (input_start == output_start) return false;
82 const input_end = input_start + input.len;
83 const output_end = output_start + output.len;
84 return input_start < output_end and output_start < input_end;
85 }
86
87 fn xor(block: *[block_length]u8, chain: [block_length]u8) void {
88 for (block, chain) |*byte, previous| byte.* ^= previous;
89 }