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 }