-module(kryptos@block). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/kryptos/block.gleam"). -export([key_size/1, block_size/1, aes_128/1, aes_192/1, aes_256/1, ecb/1, cbc/2, ctr/2, encrypt/2, decrypt/2, cipher_name/1, cipher_key/1, cipher_iv/1, aes_key/1, is_ctr/1, wrap/2, unwrap/2]). -export_type([block_cipher/0, cipher_context/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Block cipher implementations and modes of operation.\n" "\n" " This module provides AES block ciphers and modes of operation (ECB, CBC, CTR).\n" "\n" " **IMPORTANT SECURITY WARNING:**\n" " ECB, CBC, and CTR modes do NOT provide authentication. An attacker can modify\n" " ciphertext without detection. For most applications, you should use\n" " authenticated encryption modes like AES-GCM or ChaCha20-Poly1305\n" " from the `kryptos/aead` module instead.\n" "\n" " Use these modes only when:\n" " - Interoperating with legacy systems that require them\n" " - Implementing higher-level protocols that provide their own authentication\n" " - You fully understand the security implications\n" "\n" " ## Modes Overview\n" "\n" " - **ECB (Electronic Codebook):** Encrypts each block independently.\n" " INSECURE for most uses - reveals patterns in data. Only use for\n" " single-block encryption or specific legacy requirements.\n" " - **CBC (Cipher Block Chaining):** Each block XORed with previous ciphertext.\n" " Requires random IV per encryption. Uses PKCS7 padding automatically.\n" " - **CTR (Counter):** Converts block cipher to stream cipher.\n" " Nonce reuse is catastrophic - NEVER reuse a nonce with the same key.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import kryptos/block\n" " import kryptos/crypto\n" "\n" " // CBC encryption with random IV\n" " let assert Ok(cipher) = block.aes_256(crypto.random_bytes(32))\n" " let assert Ok(ctx) = block.cbc(cipher, iv: crypto.random_bytes(16))\n" " let assert Ok(ciphertext) = block.encrypt(ctx, <<\"secret\":utf8>>)\n" " let assert Ok(decrypted) = block.decrypt(ctx, ciphertext)\n" " // decrypted == <<\"secret\":utf8>>\n" " ```\n" ). -opaque block_cipher() :: {aes, integer(), bitstring()}. -opaque cipher_context() :: {ecb, block_cipher()} | {cbc, block_cipher(), bitstring()} | {ctr, block_cipher(), bitstring()}. -file("src/kryptos/block.gleam", 80). ?DOC( " Returns the key size in bytes for a block cipher.\n" "\n" " ## Parameters\n" " - `cipher`: The block cipher to get the key size for\n" "\n" " ## Returns\n" " The key size in bytes (16, 24, or 32 for AES).\n" ). -spec key_size(block_cipher()) -> integer(). key_size(Cipher) -> case Cipher of {aes, Key_size, _} -> Key_size end. -file("src/kryptos/block.gleam", 93). ?DOC( " Returns the block size in bytes for a block cipher.\n" "\n" " ## Parameters\n" " - `cipher`: The block cipher to get the block size for\n" "\n" " ## Returns\n" " The block size in bytes (16 for AES).\n" ). -spec block_size(block_cipher()) -> integer(). block_size(Cipher) -> case Cipher of {aes, _, _} -> 16 end. -file("src/kryptos/block.gleam", 107). ?DOC( " Creates a new AES-128 block cipher with the given key.\n" "\n" " ## Parameters\n" " - `key`: A 16-byte key for AES-128\n" "\n" " ## Returns\n" " - `Ok(BlockCipher)` if the key is exactly 16 bytes\n" " - `Error(Nil)` if the key size is incorrect\n" ). -spec aes_128(bitstring()) -> {ok, block_cipher()} | {error, nil}. aes_128(Key) -> case erlang:byte_size(Key) =:= 16 of true -> {ok, {aes, 128, Key}}; false -> {error, nil} end. -file("src/kryptos/block.gleam", 122). ?DOC( " Creates a new AES-192 block cipher with the given key.\n" "\n" " ## Parameters\n" " - `key`: A 24-byte key for AES-192\n" "\n" " ## Returns\n" " - `Ok(BlockCipher)` if the key is exactly 24 bytes\n" " - `Error(Nil)` if the key size is incorrect\n" ). -spec aes_192(bitstring()) -> {ok, block_cipher()} | {error, nil}. aes_192(Key) -> case erlang:byte_size(Key) =:= 24 of true -> {ok, {aes, 192, Key}}; false -> {error, nil} end. -file("src/kryptos/block.gleam", 137). ?DOC( " Creates a new AES-256 block cipher with the given key.\n" "\n" " ## Parameters\n" " - `key`: A 32-byte key for AES-256\n" "\n" " ## Returns\n" " - `Ok(BlockCipher)` if the key is exactly 32 bytes\n" " - `Error(Nil)` if the key size is incorrect\n" ). -spec aes_256(bitstring()) -> {ok, block_cipher()} | {error, nil}. aes_256(Key) -> case erlang:byte_size(Key) =:= 32 of true -> {ok, {aes, 256, Key}}; false -> {error, nil} end. -file("src/kryptos/block.gleam", 155). ?DOC( " Creates an ECB mode context for the given cipher.\n" "\n" " **SECURITY WARNING:** ECB mode is insecure for most use cases.\n" " Identical plaintext blocks produce identical ciphertext blocks,\n" " revealing patterns in the data.\n" "\n" " ## Parameters\n" " - `cipher`: The block cipher to use\n" "\n" " ## Returns\n" " An ECB cipher context.\n" ). -spec ecb(block_cipher()) -> cipher_context(). ecb(Cipher) -> {ecb, Cipher}. -file("src/kryptos/block.gleam", 168). ?DOC( " Creates a CBC mode context with the given cipher and IV.\n" "\n" " ## Parameters\n" " - `cipher`: The block cipher to use\n" " - `iv`: A 16-byte initialization vector (must be random and unique per encryption)\n" "\n" " ## Returns\n" " - `Ok(CipherContext)` if the IV is exactly 16 bytes\n" " - `Error(Nil)` if the IV size is incorrect\n" ). -spec cbc(block_cipher(), bitstring()) -> {ok, cipher_context()} | {error, nil}. cbc(Cipher, Iv) -> case erlang:byte_size(Iv) =:= 16 of true -> {ok, {cbc, Cipher, Iv}}; false -> {error, nil} end. -file("src/kryptos/block.gleam", 187). ?DOC( " Creates a CTR mode context with the given cipher and nonce.\n" "\n" " **SECURITY WARNING:** Nonce reuse is catastrophic in CTR mode.\n" " NEVER reuse a nonce with the same key.\n" "\n" " ## Parameters\n" " - `cipher`: The block cipher to use\n" " - `nonce`: A 16-byte nonce (must be unique per encryption with the same key)\n" "\n" " ## Returns\n" " - `Ok(CipherContext)` if the nonce is exactly 16 bytes\n" " - `Error(Nil)` if the nonce size is incorrect\n" ). -spec ctr(block_cipher(), bitstring()) -> {ok, cipher_context()} | {error, nil}. ctr(Cipher, Nonce) -> case erlang:byte_size(Nonce) =:= 16 of true -> {ok, {ctr, Cipher, Nonce}}; false -> {error, nil} end. -file("src/kryptos/block.gleam", 218). -spec validate_iv(cipher_context()) -> boolean(). validate_iv(Ctx) -> case Ctx of {ecb, _} -> true; {cbc, _, Iv} -> erlang:byte_size(Iv) =:= 16; {ctr, _, Nonce} -> erlang:byte_size(Nonce) =:= 16 end. -file("src/kryptos/block.gleam", 211). ?DOC( " Encrypts plaintext using the cipher mode.\n" "\n" " ## Parameters\n" " - `ctx`: The cipher context (includes IV/nonce for CBC/CTR)\n" " - `plaintext`: The data to encrypt\n" "\n" " ## Returns\n" " - `Ok(ciphertext)` on success\n" " - `Error(Nil)` if IV/nonce size is incorrect\n" "\n" " ## Notes\n" " - ECB: No IV required\n" " - CBC: Automatically applies PKCS7 padding; ciphertext may be larger than plaintext\n" " - CTR: No padding needed; ciphertext is same size as plaintext\n" ). -spec encrypt(cipher_context(), bitstring()) -> {ok, bitstring()} | {error, nil}. encrypt(Ctx, Plaintext) -> case validate_iv(Ctx) of true -> kryptos_ffi:block_cipher_encrypt(Ctx, Plaintext); false -> {error, nil} end. -file("src/kryptos/block.gleam", 244). ?DOC( " Decrypts ciphertext using the cipher mode.\n" "\n" " ## Parameters\n" " - `ctx`: The cipher context (includes IV/nonce for CBC/CTR)\n" " - `ciphertext`: The encrypted data\n" "\n" " ## Returns\n" " - `Ok(plaintext)` on success\n" " - `Error(Nil)` if IV/nonce size is incorrect, ciphertext size is invalid, or padding is invalid\n" "\n" " ## Notes\n" " - ECB: No IV required\n" " - CBC: Automatically removes PKCS7 padding; returns error if padding is invalid\n" " - CTR: No padding; ciphertext size equals plaintext size\n" ). -spec decrypt(cipher_context(), bitstring()) -> {ok, bitstring()} | {error, nil}. decrypt(Ctx, Ciphertext) -> case validate_iv(Ctx) of true -> kryptos_ffi:block_cipher_decrypt(Ctx, Ciphertext); false -> {error, nil} end. -file("src/kryptos/block.gleam", 259). ?DOC(false). -spec cipher_name(cipher_context()) -> binary(). cipher_name(Ctx) -> case Ctx of {ecb, Cipher} -> case Cipher of {aes, Key_size, _} -> <<<<"aes-"/utf8, (erlang:integer_to_binary(Key_size))/binary>>/binary, "-ecb"/utf8>> end; {cbc, Cipher@1, _} -> case Cipher@1 of {aes, Key_size@1, _} -> <<<<"aes-"/utf8, (erlang:integer_to_binary(Key_size@1))/binary>>/binary, "-cbc"/utf8>> end; {ctr, Cipher@2, _} -> case Cipher@2 of {aes, Key_size@2, _} -> <<<<"aes-"/utf8, (erlang:integer_to_binary(Key_size@2))/binary>>/binary, "-ctr"/utf8>> end end. -file("src/kryptos/block.gleam", 277). ?DOC(false). -spec cipher_key(cipher_context()) -> bitstring(). cipher_key(Ctx) -> case Ctx of {ecb, Cipher} -> case Cipher of {aes, _, Key} -> Key end; {cbc, Cipher, _} -> case Cipher of {aes, _, Key} -> Key end; {ctr, Cipher, _} -> case Cipher of {aes, _, Key} -> Key end end. -file("src/kryptos/block.gleam", 287). ?DOC(false). -spec cipher_iv(cipher_context()) -> bitstring(). cipher_iv(Ctx) -> case Ctx of {ecb, _} -> <<>>; {cbc, _, Iv} -> Iv; {ctr, _, Nonce} -> Nonce end. -file("src/kryptos/block.gleam", 468). -spec split_into_blocks(bitstring(), list(bitstring())) -> list(bitstring()). split_into_blocks(Data, Acc) -> case Data of <> -> split_into_blocks(Rest, [Block | Acc]); _ -> lists:reverse(Acc) end. -file("src/kryptos/block.gleam", 485). -spec xor_with_counter(bitstring(), integer()) -> bitstring(). xor_with_counter(A, T) -> A_int@1 = case A of <> -> A_int; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/block"/utf8>>, function => <<"xor_with_counter"/utf8>>, line => 486, value => _assert_fail, start => 14001, 'end' => 14043, pattern_start => 14012, pattern_end => 14039}) end, Result = erlang:'bxor'(A_int@1, T), <>. -file("src/kryptos/block.gleam", 360). -spec wrap_inner( block_cipher(), bitstring(), list(bitstring()), integer(), integer(), integer(), list(bitstring()) ) -> {bitstring(), list(bitstring())}. wrap_inner(Cipher, A, R, N, J, I, Acc) -> case R of [] -> {A, lists:reverse(Acc)}; [Ri | Rest] -> B = kryptos_ffi:aes_encrypt_block( Cipher, <> ), {A_new@1, Ri_new@1} = case B of <> -> {A_new, Ri_new}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/block"/utf8>>, function => <<"wrap_inner"/utf8>>, line => 373, value => _assert_fail, start => 10889, 'end' => 10949, pattern_start => 10900, pattern_end => 10945}) end, T = (N * J) + I, A_xored = xor_with_counter(A_new@1, T), wrap_inner(Cipher, A_xored, Rest, N, J, I + 1, [Ri_new@1 | Acc]) end. -file("src/kryptos/block.gleam", 343). -spec wrap_rounds( block_cipher(), bitstring(), list(bitstring()), integer(), integer() ) -> {bitstring(), list(bitstring())}. wrap_rounds(Cipher, A, R, N, J) -> case J < 6 of false -> {A, R}; true -> {A_new, R_new} = wrap_inner(Cipher, A, R, N, J, 1, []), wrap_rounds(Cipher, A_new, R_new, N, J + 1) end. -file("src/kryptos/block.gleam", 446). -spec unwrap_inner( block_cipher(), bitstring(), list(bitstring()), integer(), integer(), integer(), list(bitstring()) ) -> {bitstring(), list(bitstring())}. unwrap_inner(Cipher, A, R, N, J, I, Acc) -> case R of [] -> {A, Acc}; [Ri | Rest] -> T = (N * J) + I, A_xored = xor_with_counter(A, T), B = kryptos_ffi:aes_decrypt_block( Cipher, <> ), {A_new@1, Ri_new@1} = case B of <> -> {A_new, Ri_new}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/block"/utf8>>, function => <<"unwrap_inner"/utf8>>, line => 461, value => _assert_fail, start => 13269, 'end' => 13329, pattern_start => 13280, pattern_end => 13325}) end, unwrap_inner(Cipher, A_new@1, Rest, N, J, I - 1, [Ri_new@1 | Acc]) end. -file("src/kryptos/block.gleam", 428). -spec unwrap_rounds( block_cipher(), bitstring(), list(bitstring()), integer(), integer() ) -> {bitstring(), list(bitstring())}. unwrap_rounds(Cipher, A, R, N, J) -> case J >= 0 of false -> {A, R}; true -> {A_new, R_new} = unwrap_inner( Cipher, A, lists:reverse(R), N, J, N, [] ), unwrap_rounds(Cipher, A_new, R_new, N, J - 1) end. -file("src/kryptos/block.gleam", 492). ?DOC(false). -spec aes_key(block_cipher()) -> bitstring(). aes_key(Cipher) -> case Cipher of {aes, _, Key} -> Key end. -file("src/kryptos/block.gleam", 499). ?DOC(false). -spec is_ctr(cipher_context()) -> boolean(). is_ctr(Ctx) -> case Ctx of {ctr, _, _} -> true; _ -> false end. -file("src/kryptos/block.gleam", 334). -spec do_wrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. do_wrap(Cipher, Plaintext) -> N = erlang:byte_size(Plaintext) div 8, R = split_into_blocks(Plaintext, []), {A, R_final} = wrap_rounds( Cipher, <<16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6>>, R, N, 0 ), {ok, gleam_stdlib:bit_array_concat([A | R_final])}. -file("src/kryptos/block.gleam", 321). ?DOC( " Wraps key material using AES Key Wrap (RFC 3394).\n" "\n" " Key wrapping is used to protect cryptographic keys when they need to be\n" " transported or stored. Unlike general encryption, key wrapping:\n" " - Does not require an IV (uses a default IV internally)\n" " - Provides integrity protection\n" " - Output is always 8 bytes larger than input\n" "\n" " ## Parameters\n" " - `cipher`: The AES block cipher used as the key-encryption key (KEK)\n" " - `plaintext`: The key material to wrap (must be a multiple of 8 bytes, minimum 16 bytes)\n" "\n" " ## Returns\n" " - `Ok(ciphertext)` where ciphertext is plaintext.size + 8 bytes\n" " - `Error(Nil)` if plaintext size is invalid\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import kryptos/block\n" " import kryptos/crypto\n" "\n" " let assert Ok(kek) = block.aes_256(crypto.random_bytes(32))\n" " let key_to_wrap = crypto.random_bytes(32)\n" " let assert Ok(wrapped) = block.wrap(kek, key_to_wrap)\n" " ```\n" ). -spec wrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. wrap(Cipher, Plaintext) -> Size = erlang:byte_size(Plaintext), case (Size >= 16) andalso ((Size rem 8) =:= 0) of true -> do_wrap(Cipher, Plaintext); false -> {error, nil} end. -file("src/kryptos/block.gleam", 415). -spec do_unwrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. do_unwrap(Cipher, Ciphertext) -> {A@1, Rest@1} = case Ciphertext of <> -> {A, Rest}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"kryptos/block"/utf8>>, function => <<"do_unwrap"/utf8>>, line => 416, value => _assert_fail, start => 12210, 'end' => 12265, pattern_start => 12221, pattern_end => 12252}) end, N = erlang:byte_size(Rest@1) div 8, R = split_into_blocks(Rest@1, []), {A_final, R_final} = unwrap_rounds(Cipher, A@1, R, N, 5), case fun kryptos_ffi:constant_time_equal/2( A_final, <<16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6, 16#a6>> ) of true -> {ok, gleam_stdlib:bit_array_concat(R_final)}; false -> {error, nil} end. -file("src/kryptos/block.gleam", 399). ?DOC( " Unwraps key material using AES Key Wrap (RFC 3394).\n" "\n" " ## Parameters\n" " - `cipher`: The AES block cipher used as the key-encryption key (KEK)\n" " - `ciphertext`: The wrapped key material (must be a multiple of 8 bytes, minimum 24 bytes)\n" "\n" " ## Returns\n" " - `Ok(plaintext)` where plaintext is ciphertext.size - 8 bytes\n" " - `Error(Nil)` if ciphertext size is invalid or integrity check fails\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import kryptos/block\n" "\n" " let assert Ok(kek) = block.aes_256(kek_bytes)\n" " let assert Ok(unwrapped) = block.unwrap(kek, wrapped_key)\n" " ```\n" ). -spec unwrap(block_cipher(), bitstring()) -> {ok, bitstring()} | {error, nil}. unwrap(Cipher, Ciphertext) -> Size = erlang:byte_size(Ciphertext), case (Size >= 24) andalso ((Size rem 8) =:= 0) of true -> do_unwrap(Cipher, Ciphertext); false -> {error, nil} end.