I want to design a scheme to encrypt a variable-length message with a secret key, to provide confidentiality. The message is a short human-readable text string with byte granularity, let's say under 300 bytes. I prefer that the ciphertext is the same length as the plaintext (avoid padding).
Along the way, I also want to support integrity-checking on a best-effort basis. Preferably, I want it so that if any bit of the ciphertext is changed, then the decrypted plaintext will look garbled. (The decrypted output will actually be read by a human, and no automated checking is needed.)
It is not acceptable to append a MAC or any kind of check code due to message size restrictions; valid decryption must be inferred from the garbling of the message itself. CPU time is not a problem as long as it's under 0.1 second. An inefficient but secure scheme is okay, but the scheme should be conceptually simple to describe/audit/implement.
The complicating factor is that the message may be shorter than a block (say 16 bytes, for the AES cipher), which means ciphertext stealing can't be used. (Right?)
I'm aware of these facts already:
- Using a stream cipher satisfies the variable-length property but makes the ciphertext very malleable; this is undesirable.
- Using a block cipher gives the "decryption garble" property desired.
- Using CBC mode instead of ECB will mask repeating patterns in the input.
- Ciphertext stealing (for ECB or CBC) makes it possible to not increase the message length - but only if the message is at least one block long.
- It's possible to use a keyed hash function / MAC for 3 or 4 rounds to design a custom Feistel network cipher.
- It might be possible to use a stream cipher and a bytewise adaptation of the Infinite Garble Extension (IGE) mode to achieve garble propagation.
- Designing "home-made" crypto not reviewed by experts is frowned upon and may have subtle and fatal errors.
But I don't know what else I need to know, and how to proceed from here. I can post more details on some of the proposed algorithms (such as IGE and Feistel) if needed.
Addendum:
Feistel network idea: (using Python pseudocode)
Let H(k, m) be a MAC (such as HMAC-SHA-512) with secret key k.
Let M be the message to be encrypted.
Let i = floor(M.length / 2).
Algorithm:
M[0 : i] ^= truncate(H(k, M[i : M.length])) # left half XOR H(right half)
M[i : M.length] ^= truncate(H(k, M[0 : i])) # right half XOR H(left half)
M[0 : i] ^= truncate(H(k, M[i : M.length])) # Round 3 to achieve error propagation
M[i : M.length] ^= truncate(H(k, M[0 : i])) # Round 4 due to recommendations
(If half the message length is less than the MAC/hash length then truncating is easy. But if it's longer then some kind of stretching, i.e. CSPRNG, is needed.)
Second addendum:
I'm leaning towards this solution:
preprocessed = all-or-nothing-transform(message)
ciphertext = preprocessed XOR (stream cipher keystream)