-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Classification: Symmetric Stream Cipher Β· Custom Research Design State Width: 256-bit (four 64-bit words) Intended Use: Academic research, demonstration, security experimentation Authors: RlxChap2 License: MIT
MASC-256 (Memory Accumulator Stack Cipher, 256-bit) is an experimental symmetric stream cipher whose central design thesis is that dynamic runtime memory behavior can be used as an entropy amplifier to increase diffusion without incurring the fixed-round overhead of block cipher constructions.
| Goal | Mechanism |
|---|---|
| High diffusion per byte | Accumulator round with aggressive rotations |
| Memory-dependent keystream | Stack and heap state folded into every output byte |
| Symmetric operation | Identical encrypt / decrypt logic; state synchronizes on ciphertext bytes |
| Side-channel disruption | Non-deterministic heap allocation addresses at runtime |
| Simple, auditable core | ~120 lines of portable C99 |
MASC-256 belongs to the additive stream cipher family, where a keystream is generated from a secret key and XOR'd with plaintext byte-by-byte:
Unlike conventional stream ciphers (RC4, Salsa20, ChaCha20) that evolve state purely through arithmetic, MASC-256 deliberately involves the OS memory allocator and stack frame layout in state evolution, making the keystream partially dependent on process-level memory entropy.
β οΈ Research Note: MASC-256 is a novel design that has not undergone formal peer cryptanalysis. It should not be used to protect sensitive production data without independent security audit. See Section 12.
| Symbol | Meaning |
|---|---|
| Bitwise XOR | |
| Addition modulo |
|
| Left rotation by |
|
| Right rotation by |
|
| Bitwise AND | |
| The four 64-bit words of accumulator state ACC | |
| Key byte array of length |
|
| Plaintext byte at position |
|
| Ciphertext byte at position |
|
| Keystream byte at position |
|
| Accumulator state at time-step |
All arithmetic on 64-bit words is implicitly
Constants are 64-bit fractional-part-derived values (similar to SHA-2 IV strategy).
The entire cipher state is a single 256-bit structure:
State is seeded with irrational-constant-derived magic numbers (fractional parts of
| Word | Hex Value | Source Approximation |
|---|---|---|
0x9E3779B97F4A7C15 |
|
|
0x6A09E667F3BCC909 |
||
0xBB67AE8584CAA73B |
||
0x3C6EF372FE94F82B |
These are the same constants used in SHA-512 and BLAKE2, providing a well-distributed, bias-free starting point.
Each processing step (key byte or plaintext byte) drives a full state transition:
where
ROTL(x, r): (x << r) | (x >> (64 - r))
ROTR(x, r): (x >> r) | (x << (64 - r))
Mathematical definitions:
Diffusion effect: A single-bit change in
In
accumulator_round, four consecutive rotations with prime-like offsets (17, 23, 31, 47) are used to ensure no short cycles exist within a 64-bit word.
Purpose: Fold transient stack memory values into all four accumulator words, introducing data-dependent entropy from the processor stack frame.
C Implementation (annotated):
void stack_mix(ACC *acc, uint64_t v) {
uint64_t stack_buf[8]; // 8 Γ 64-bit values on the stack
for(int i = 0; i < 8; i++) {
stack_buf[i] = rotl(v ^ (acc->a + i), i + 1);
acc->a ^= stack_buf[i];
acc->b += rotl(stack_buf[i], (i + 3) % 64);
acc->c ^= acc->b + stack_buf[i];
acc->d = rotl(acc->d ^ acc->c, 7);
}
}Full per-iteration formulas (for iteration
Dependency chain per iteration:
v, a βββΊ S_i βββΊ a'
ββββΊ b' βββΊ c' βββΊ d'
Every word is a function of all preceding words after iteration
Stack entropy source: The array stack_buf resides at a runtime-determined stack address. While the values are deterministic given
Purpose: Allocate a 512-byte heap buffer whose address is non-deterministic (OS/allocator-dependent), fold it into the state across 64 iterations.
C Implementation (annotated):
void heap_mix(ACC *acc, uint64_t seed) {
uint64_t *heap = malloc(64 * sizeof(uint64_t)); // 512 bytes on heap
for(int i = 0; i < 64; i++) {
heap[i] = rotl(seed ^ (acc->a + i), (i % 31) + 1);
acc->a += heap[i];
acc->b ^= rotl(heap[i], (i % 17) + 3);
acc->c += acc->b ^ heap[i];
acc->d ^= rotl(acc->c, (i % 13) + 5);
}
free(heap);
}Per-iteration formulas (for
Rotation schedule analysis:
| Word | Rotation modulus | Period | Values used |
|---|---|---|---|
|
|
31 | ||
|
|
17 | ||
|
|
13 |
The three moduli (31, 17, 13) are mutually coprime, so the joint rotation pattern has period heap_mix call.
Memory-dependent entropy: The heap allocation address heap varies each call due to ASLR and allocator state. While the values stored in the buffer are deterministically computed, the cache-line layout, page mapping, and hardware prefetcher behavior introduce timing and microarchitectural variations that are invisible to a pure algorithmic model.
Purpose: Apply a final non-linear, ARX (Add-Rotate-XOR) compression across all four state words. This is analogous to the permutation step in sponge constructions.
C Implementation:
void accumulator_round(ACC *acc) {
acc->a = rotl(acc->a ^ acc->d, 17);
acc->b = rotl(acc->b + acc->a, 23);
acc->c = rotl(acc->c ^ acc->b, 31);
acc->d = rotl(acc->d + acc->c, 47);
}Formulas:
Rotation constants (17, 23, 31, 47) are all prime numbers, chosen to avoid short cycles and maximize the period of the internal permutation. The alternating XOR/ADD pattern creates algebraic incompatibility between GF(2) and
Single-pass diffusion: A 1-bit difference in
-
$a \oplus d$ spreads the bit to$a'$ -
$b + a'$ carries it into$b'$ (addition carry chain) -
$c \oplus b'$ carries it into$c'$ -
$d + c'$ carries it into$d'$
Key initialization transforms a variable-length byte key
void init_acc(ACC *acc, const uint8_t *key, size_t len) {
// Load magic constants
acc->a = 0x9E3779B97F4A7C15ULL;
acc->b = 0x6A09E667F3BCC909ULL;
acc->c = 0xBB67AE8584CAA73BULL;
acc->d = 0x3C6EF372FE94F82BULL;
for(size_t i = 0; i < len; i++) {
acc->a ^= key[i] + rotl(acc->d, i % 63); // Key absorption
stack_mix(acc, key[i]); // Stack entropy
heap_mix(acc, key[i] ^ acc->a); // Heap entropy
accumulator_round(acc); // Non-linear mix
}
}For key byte index
Step 1 β Key Absorption:
Step 2 β Stack Mixing:
Step 3 β Heap Mixing (seed =
Step 4 β Accumulator Round:
Initial Constants
(aβ, bβ, cβ, dβ)
β
βΌ for each key byte Kα΅’
βββββββββββββββββββββββββ
β a β= (Kα΅’ + ROTL(d, i%63)) β β Key Absorption
βββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β stack_mix(ACC, Kα΅’) β β Stack Entropy (8 iters)
βββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β heap_mix(ACC, Kα΅’ β a) β β Heap Entropy (64 iters)
βββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β accumulator_round(ACC) β β Non-linear ARX
βββββββββββββββββββββββββ
β
βΌ (next key byte)
Final ACC state
For a key of
- 1 key absorption operation
- 8 iterations of
stack_mix - 64 iterations of
heap_mix - 1
accumulator_round
Total operations per key byte: 74 inner loop iterations + 1 ARX round.
For a 16-byte key: 1,184 inner iterations.
For a 32-byte key: 2,368 inner iterations.
The variable-length key schedule means there is no key length upper bound; longer keys increase initialization cost linearly but improve the entropy saturation of the initial state.
void encrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen) {
ACC acc;
init_acc(&acc, key, klen);
for(size_t i = 0; i < len; i++) {
uint64_t mix = acc.a ^ acc.b ^ acc.c ^ acc.d; // Keystream word
data[i] ^= (uint8_t)(mix & 0xFF); // Encrypt byte
stack_mix(&acc, data[i]); // Update: ciphertext byte
heap_mix(&acc, mix ^ data[i]); // Update: mix β ciphertext
accumulator_round(&acc); // Final round
}
}Keystream generation:
Encryption:
State update (with
where
Key βββΊ init_acc βββΊ ACCβ
β
ββββββββββββββ€
β β for each plaintext byte Mα΅’
βΌ β
ks = (aβbβcβd) & 0xFF
β β
Cα΅’ = Mα΅’ β ks β
β β
stack_mix(ACC, Cα΅’) β
β β
heap_mix(ACC, ks'βCα΅’)
β β
accumulator_round β
β β
ββββββββββββββ
β
Output: Cβ Cβ Cβ ... Cβ (as hex)
All ciphertext bytes are output as uppercase hexadecimal pairs to handle the full 256-byte space without control character issues:
printf("%02X", data[i]); // e.g., byte 0xAB β "AB"void decrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen) {
ACC acc;
init_acc(&acc, key, klen);
for(size_t i = 0; i < len; i++) {
uint64_t mix = acc.a ^ acc.b ^ acc.c ^ acc.d;
uint8_t cipher = data[i]; // Save raw ciphertext byte
data[i] ^= (uint8_t)(mix & 0xFF); // Recover plaintext
stack_mix(&acc, cipher); // Update with ciphertext (same as encrypt)
heap_mix(&acc, mix ^ cipher);
accumulator_round(&acc);
}
}Keystream generation (identical to encryption):
Decryption:
State update (using saved
The cipher is symmetric because:
- The same
init_accproduces identical$\text{ACC}_0$ for the same key. - The keystream byte
$\text{ks}_i$ depends only on$\text{ACC}_i$ , not on$M_i$ or$C_i$ . - The state update after byte
$i$ uses the ciphertext byte$C_i$ in both directions, so$\text{ACC}_{i+1}$ is identical in both encrypt and decrypt paths.
Formally: since
Important Disclaimer: The following analysis is a design-intent assessment based on the cipher's structure. It has not been validated by formal cryptanalysis, third-party audit, or peer review. Claims below are theoretical properties, not proven guarantees.
The keystream byte at position
If the four 64-bit words are treated as independently uniform (after key initialization), the XOR of four uniform values is itself uniform. The expected entropy per keystream byte:
In practice, the words are not independent β they evolve through shared state β but the nonlinear mixing through both addition and XOR (which are algebraically incompatible) makes statistical correlation between consecutive keystream bytes computationally hard to exploit.
The avalanche criterion requires that a 1-bit change in input produces approximately a 50% change in output bits (Hamming distance β 128 bits for 256-bit state, β 4 bits for the 8-bit output).
Single-byte input change propagation:
| Step | Words affected | Mechanism |
|---|---|---|
| Key absorption | ||
stack_mix (8 iters) |
Cross-word XOR and addition | |
heap_mix (64 iters) |
64 rounds of ARX diffusion | |
accumulator_round |
Final ARX mixing |
After the full state update, an expected Hamming distance of
Linear cryptanalysis looks for affine approximations
Mitigating factors in MASC-256:
-
Mixed algebraic groups: Addition in
$\mathbb{Z}_{2^{64}}$ and XOR in$\mathbb{F}_2^{64}$ are algebraically incompatible. Linear approximations of$a + b$ over$\mathbb{F}_2$ have maximum bias$\epsilon \leq 2^{-1}$ per bit, decaying rapidly with word width. -
64 iterations of
heap_mix: Each iteration compounds the approximation error, producing exponential bias reduction:$\epsilon_{64} \leq (1/2)^{64}$ for simple linear trails. -
State-feedback: Because the state after byte
$i$ feeds into the keystream for byte$i+1$ , a linear distinguisher must span the entire message, amplifying the required data complexity exponentially.
Differential cryptanalysis tracks how input differences
Key property: The state update mixes in the ciphertext byte
This self-synchronizing differential divergence means that a differential characteristic valid at position
A timing side-channel on heap_mix is theoretically possible if the adversary can:
- Precisely measure
mallocexecution time. - Correlate that timing to the heap address (which depends on allocator state).
- Infer
seedoracc->afrom that address.
In practice:
- ASLR randomizes heap base addresses per-process run.
-
malloctiming depends on the allocator's free-list state, which changes unpredictably. - Cache-timing attacks on the heap buffer access pattern would require cache-line-level resolution of a 512-byte buffer at a non-constant address β a significantly harder target than S-box cache attacks on AES.
However, no formal cache-timing analysis has been performed. This is a claimed property, not a proven one.
For an
For a 32-byte (256-bit) key:
An adversary attempting to recover the full 256-bit state from keystream bytes must:
- Collect keystream bytes
$\text{ks}_0, \text{ks}_1, \ldots, \text{ks}_n$ . - Invert
$n$ coupled ARX state transitions. - Match to the initial state space of
$2^{256}$ possibilities.
Given the non-invertibility of heap_mix (malloc'd buffer is freed; address is lost), direct algebraic inversion is infeasible. Brute force remains at
Key (ASCII): UltraEncryptedKey
Key (Hex): 556C7472614 56E637279707465644B6579
Key Length: 17 bytes
Plaintext (ASCII): Advanced Memory Encryption System
Plaintext (Hex): 416476616E636564204D656D6F727920456E6372797074696F 6E2053797374656D
Plaintext Length: 33 bytes
Expected Output (compile and run main.c to verify):
Encrypted: [Run ./build/Debug/acc_crypto to generate]
Decrypted: Advanced Memory Encryption System
Since the cipher's heap allocation addresses may vary across platforms and OS versions (due to ASLR), the encrypted hex output will differ between executions on different platforms if memory-address values contribute indirectly. However, the ciphertext values must be deterministic on a single clean execution because
heap_mixstores computed values in the heap buffer β the values are deterministic; only the addresses vary. On any single run, encrypt β decrypt must recover the original plaintext.
To confirm determinism on your platform:
./build/Debug/acc_crypto
# Run twice; encrypted output must be IDENTICAL both times
# Decrypted output must always be: "Advanced Memory Encryption System"Changing one bit of the key must produce a completely different ciphertext (avalanche at key level):
| Key | Expected behavior |
|---|---|
UltraEncryptedKey |
Produces ciphertext |
UltraEncryptedKex |
Produces completely different ciphertext |
ultraEncryptedKey |
Produces completely different ciphertext |
Quantitatively,
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ACCUMULATOR (256-bit) β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββ β
β β a (64) β β b (64) β β c (64) β β d (64) β β
β ββββββββ¬βββββββ ββββββββ¬βββββββ ββββββββ¬βββββββ βββββββ¬ββββββ β
βββββββββββΌββββββββββββββββΌββββββββββββββββΌββββββββββββββββΌβββββββββββ
β β β β
βββββββββββββββββ΄ββββββββββββββββ΄ββββββββββββββββ
β
aβbβcβd
β
& 0xFF = ks_i
β
Input byte ββββββΊ Output byte
β
ββββββββββββββββββββΌβββββββββββββββββββββββ
β stack_mix β
β (8 iters: S=ROTL(vβ(a+i), i+1)) β
β aβ=S, b+=ROTL(S), cβ=(b+S), d=ROTL(dβc, 7) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
ββββββββββββββββββββΌβββββββββββββββββββββββ
β heap_mix β
β (64 iters: H=ROTL(seedβ(a+i), var)) β
β a+=H, bβ=ROTL(H), c+=(bβH), dβ=ROTL(c) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
ββββββββββββββββββββΌβββββββββββββββββββββββ
β accumulator_round β
β a=ROTL(aβd,17), b=ROTL(b+a,23) β
β c=ROTL(cβb,31), d=ROTL(d+c,47) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
Next ACC state
ENCRYPT DECRYPT
βββββββββββββββββββββ βββββββββββββββββββββ
ACC_i ACC_i (identical, same key)
β β
βΌ βΌ
ks = (aβbβcβd) & 0xFF ks = (aβbβcβd) & 0xFF
β β
βΌ βΌ
C_i = M_i β ks save C_i; M_i = C_i β ks
β β
βΌ βΌ
stack_mix(ACC, C_i) βββsameββββΊ stack_mix(ACC, C_i)
heap_mix(ACC, ks'βC_i) heap_mix(ACC, ks'βC_i)
accumulator_round accumulator_round
β β
βΌ βΌ
ACC_{i+1} β‘ ACC_{i+1} β guaranteed identical
malloc(64 Γ 8 bytes)
ββββββββββββββββββββββββββββββββββββββββββββββββ
β heap[0] β heap[1] β ... β heap[31] β...βheap[63] β
β H(0) β H(1) β β H(31) β β H(63) β
ββββββββββββββββββββββββββββββββββββββββββββββββ
β β
βΌ feeds into βΌ
a += H(i)
b ^= ROTL(H(i), (i%17)+3)
c += b ^ H(i)
d ^= ROTL(c, (i%13)+5)
free(heap)
| Property | AES-256 | MASC-256 |
|---|---|---|
| Cipher type | Block (128-bit block) | Stream (1-byte output per step) |
| Key length | Fixed 256-bit | Variable (any length) |
| Internal state | 128-bit (4Γ4 byte matrix) | 256-bit (4Γ64-bit words) |
| Rounds per block/byte | 14 fixed rounds | 1 stack + 1 heap + 1 ARX round |
| Inner loop iterations | ~14 Γ AES ops | 73 per byte (8 stack + 64 heap + 1 ARX) |
| Parallelizability | CTR/GCM modes only | Inherently sequential |
| Formal security proof | Yes (proofs under standard assumptions) | None (research cipher) |
| NIST standardization | Yes | No |
| Side-channel hardening | Requires AES-NI | Memory-address variation (partial) |
| Hardware acceleration | AES-NI instruction set | None |
| Peer review | Extensive, 25+ years | None |
| Memory allocations | Zero | 1 Γ 512-byte per plaintext byte |
AES-256 benefits from:
- 25+ years of public cryptanalysis with no successful key-recovery attacks
- Hardware acceleration making it extremely fast in practice
- Formal security reductions and proofs
- NIST standardization with strict implementation requirements
MASC-256's theoretical strengths β memory-dependent entropy, variable-length key absorption, 256-bit state β are design properties, not proven security properties. A cipher is not more secure than AES-256 until it survives equivalent public scrutiny.
Recommended path to validation:
- Publish the design openly (this document is a start).
- Submit to IACR ePrint Archive for community review.
- Implement a full statistical test suite (NIST SP 800-22 / TestU01).
- Commission a formal cryptanalysis engagement.
- Pursue standardization track if results are favorable.
| Limitation | Description | Impact |
|---|---|---|
| No IV/Nonce | Encrypting two messages with the same key produces identical keystreams | Nonce-reuse attack: XOR two ciphertexts to cancel the keystream |
| Sequential only | State depends on all prior bytes; no random access | Cannot parallelize encryption |
malloc per byte |
512-byte allocation every plaintext byte | Significant memory allocator overhead vs. AES |
| No authentication | Pure encryption; no MAC or AEAD | Ciphertext tampering is undetected |
| No formal analysis | Claimed properties are unverified | Unknown actual security margin |
| Heap values deterministic | Despite non-deterministic addresses, buffer values are computable | Side-channel resistance weaker than claimed |
-
Add a nonce/IV parameter to
init_accβ mix a random 128-bit nonce into the initial state before key absorption. - Implement AEAD mode β append a 128-bit Poly1305 or GHASH authentication tag.
- Batch the heap allocation β allocate the heap buffer once per message rather than per byte to reduce allocator overhead.
-
Extract more keystream bits β use all 64 bits of
mix, not just 8 bits, to encrypt 8 bytes per state update (increasing throughput 8Γ). - Formal statistical testing β run NIST SP 800-22 and Diehard battery on the raw keystream.
- Side-channel hardening β consider constant-time implementations of rotation and XOR.
- C99-compatible compiler: GCC β₯ 4.8, Clang β₯ 3.5, or MSVC 2019+
- CMake β₯ 3.10
-
Standard C library (
stdlib.h,stdint.h,stddef.h)
# Configure
cmake -B build -S .
# Build (Debug by default)
cmake --build build
# Run
./build/Debug/acc_cryptomake all # Equivalent to cmake configure + build
make run # Build and executeEncrypted: <HEX STRING OF 33 BYTES = 66 HEX CHARS>
Decrypted: Advanced Memory Encryption System
To use MASC-256 in your own project, include acc_crypto.h and link acc_crypto.c + memory_mix.c:
#include "acc_crypto.h"
// Encrypt a buffer in-place
uint8_t my_key[] = "MySecretKey";
uint8_t my_data[] = "Hello, World!";
size_t data_len = sizeof(my_data) - 1;
encrypt(my_data, data_len, my_key, sizeof(my_key) - 1);
// my_data now contains ciphertext
decrypt(my_data, data_len, my_key, sizeof(my_key) - 1);
// my_data restored to "Hello, World!"void init_acc(ACC *acc, const uint8_t *key, size_t len);Initializes accumulator acc from key key of byte length len. Must be called before encrypt or decrypt.
void encrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen);Encrypts len bytes in data in-place using key. Internally calls init_acc.
void decrypt(uint8_t *data, size_t len, const uint8_t *key, size_t klen);Decrypts len bytes in data in-place using key. Internally calls init_acc.
void stack_mix(ACC *acc, uint64_t v);Applies 8 rounds of stack-entropy mixing to acc using seed value v.
void heap_mix(ACC *acc, uint64_t seed);Allocates a 512-byte heap buffer, applies 64 rounds of ARX mixing to acc, then frees the buffer.
void accumulator_round(ACC *acc);Applies one round of the 4-word ARX permutation to acc.
uint64_t rotl(uint64_t x, int r);
uint64_t rotr(uint64_t x, int r);Circular bit-rotation of x by r positions.
- FIPS 197 β Advanced Encryption Standard (AES), NIST, 2001.
- Bernstein, D.J. β "The Salsa20 Family of Stream Ciphers," ECRYPT eSTREAM, 2008.
- Bernstein, D.J. β "ChaCha, a variant of Salsa20," SKEW 2008.
- Aumasson, J.P., et al. β "BLAKE2: simpler, smaller, fast as MD5," ACNS 2013.
- Kelsey, J., et al. β "Key-Schedule Cryptanalysis of IDEA, G-DES, GOST, SAFER, and Triple-DES," CRYPTO 1996.
- Menezes, A., van Oorschot, P., Vanstone, S. β Handbook of Applied Cryptography, CRC Press, 1996. (Free PDF: cacr.uwaterloo.ca)
- NIST SP 800-22 β A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications, 2010.
- Ferguson, N., Schneier, B., Kohno, T. β Cryptography Engineering, Wiley, 2010.
This document was produced for the MASC-256 research cipher. For questions, contributions, or cryptanalysis results, open an issue in the project repository.
- Repository Mapping
- Parameter Summary
- Notation
- Threat Model
- Security Goals and Non-Goals
- Construction Overview
- Public Message Format
- Key Schedule and Domain Separation
- Accumulator State
- Primitive Operations
- State Absorption
- Accumulator Initialization
- Keystream Generation
- Encryption Core
- Authentication Core
- Public API
- Error Semantics
- Reference Test Vector
- Implementation Security Notes
- Validation and Analysis Program
- Known Limitations
- Public Positioning and Evaluation Scope
- References
- Release Conformance
- Appendix A: Complete Seal Algorithm
- Appendix B: Complete Open Algorithm
- Appendix C: Core Stream Algorithm