Disclaimer: the algorithms I present here (and in other messages) are used as a hands-on way to learn about developing crypto algorithms, (and might also be of interest for other curious people), not for practical use (at least not after dozens of iterations and peer-review). I understand some people vote down not just because it seems unproved and speculative, and possibly flawed, but also perhaps because I'm not necessarily presenting it this way. This is just a manner of speech. Most of this *is* probably flawed, but that's the whole point here!
Say I’m using a 128 bit block cipher and would like to encrypt a 256 bit input that would be divided to two blocks, however I wish that every bit changed in the ciphertext would completely corrupt both plaintext blocks when decrypted.
Consider the following simple solution:
Version 1 - two blocks only (very flawed - kept for historical reference)
Encryption:
- XOR the first plaintext block with the second one and store the result in place of the first ciphertext block.
- Encrypt the two blocks with PCBC mode, meaning both the first block’s plaintext and ciphertext are XORed with the second block’s plaintext before encrypting.
Decryption:
- Decrypt the two ciphertext blocks with PCBC mode.
- XOR the first plaintext block with the second and store the result in place of the first.
Will this method deliver true non-malleability?
What about an extended version with more than two blocks? - in which during encryption, the additional processing simply goes from the last block backwards, XORs with the subsequent block and stores the result in place (the processing during decryption is also simple, i think). I read though that with PCBC "if two adjacent ciphertext blocks are exchanged, this does not affect the decryption of subsequent blocks." so I'm not sure it would deliver its promise in this case.
Version 2 - two blocks only (reformulated to overcome issues pointed out by @poncho but still flawed - kept for historical reference).
Encryption:
- XOR the first plaintext block with the IV and the second plaintext block and store the result in place of the first plaintext block.
- Encrypt the first plaintext block twice, with two different keys (where the second key may be derived from the first).
- Output the first encryption of the first block as the first ciphertext block.
- XOR the second plaintext block with the second encryption of the first block (note the second encryption is completely secret and will never be known).
- Encrypt the XORed second block and output it.
Decryption:
- Decrypt the first ciphertext block.
- Re-encrypt the resulting plaintext of the first block with the secondary key (note the resulting plaintext is still XORed with the IV and original second plaintext block).
- Decrypt the second ciphertext block.
- XOR the resulting plaintext with the re-encryption of the first block and store it in place.
- XOR the first block with the IV and decrypted second block and store it in place.
[TODO: consider if this could be extended to more than 2 blocks - the "pre-processing stage" is explained in the remarks of the first version of the algorithm]
NOTE: this still needs some work, it has a serious problem!
Assume an IV of 0, and both plaintext blocks of 0: corrupting on bit of the second ciphertext block would give out a completely psuedorandom second plaintext block, XOR it with the second encryption of the first decrypted block to get the final value of the second, now XORing with the first would yield the exact same value for the two blocks! So right now it does propagate errors backwards, but doesn't deliver true non-malleability. I'll try to see how to fix that tomorrow..
A possible sketch of a solution is to try to a create a dependency for the first block on a psuedorandom permutation of the second one rather than on its plain value. Hopefully that can be done without introducing an additional encryption step. We'll see about that..
Version 3 - two blocks only - (corrects the flaw in the second one, modified in-place to overcome flaws pointed out by @Ricky Demer - might be correct)
Encryption:
Derive intermediate blocks $I_1,I_2$:
$I_1 = P_1 \oplus E_{key_2}(IV \oplus P_2)$
$I_2 = P_2 \oplus E_{key_2}(IV \oplus I_1)$
Derive ciphertext blocks $C_1,C_2$:
$C_1 = E_{key_1}(I_1)$
$C_2 = E_{key_1}(I_2)$
Decryption:
Derive intermediate blocks $I_1,I_2$:
$I_1 = D_{key_1}(C_1)$
$I_2 = D_{key_1}(C_2)$
Derive plaintext blocks $P_1,P_2$:
$P_2 = I_2 \oplus E_{key_2}(IV \oplus I_1)$
$P_1 = I_1 \oplus E_{key_2}(IV \oplus P_2)$
An initial attempt for handling the general case for $n$ blocks (based on the same technique as the previous one - but flawed for $n>2$)
Encryption:
For every plaintext block $P_k, k \leq n$
Derive an intermediate block:
$I_k = P_k \oplus E_{key_2}(IV \oplus I_1 \oplus I_2 \oplus ... \oplus I_{k-1} \oplus P_{k+1})$
(where the last block would be $I_n = P_n \oplus E_{key_2}(IV \oplus I_1 \oplus I_2 \oplus ... \oplus I_{n-1})$)
Encrypt the intermediate block:
$C_k = E_{key_1}(I_k)$
Decryption:
Go forward and decrypt all the intermediate blocks with the first key:
For every ciphertext block $C_k, k \leq n$
$I_k = D_{key_1}(C_k)$
Now start at $n$ and go backwards to derive the plaintext blocks:
Derive the last plaintext block:
$P_n = I_n \oplus E_{key_2}(IV \oplus I_1 \oplus I_2 \oplus ... \oplus I_{n-1})$
For every intermediate block $I_k, k < n$:
$P_k = I_k \oplus E_{key_2}(IV \oplus I_1 \oplus I_2 \oplus ... \oplus I_{k-1} \oplus P_{k+1})$
Note: This is vulnerable to block-exchanging attacks, since during decryption the intermediate blocks are XORed before encryption, and the XOR operation is commutative. I'm considering different solutions:
A simple but impractical solution would be to use a different key for each block (i.e. one key per index).
A faster one would be to XOR the current intermediate block with a hash of all the previous ones before encrypting it to the ciphertext, i.e. $C_k = E_{key_1}(I_k \oplus H(I_1..I_{k-1}))$ and reverse the operation during decryption. Since the intermediate blocks are in essence secret, encrypted, psuedo-random data, the hash used probably doesn't need to be cryptographic (e.g. perhaps CRC32/64 could suffice) and could be calculated incrementally, giving a relatively small impact on performance.
An even more interesting (but speculative and possibly flawed) solution is not to use decryption at all but to use the block cipher in something akin to counter mode with a different nonce for each block for the outward encryption, where the nonce would be the XOR of all the previously decrypted blocks. The decryption of the ciphertext would be something like $I_k = C_k \oplus E_{key_1}(IV \oplus I_1 \oplus I_2 \oplus ... \oplus I_{k-1})$. This may actually imply a total reformulation and simplification of the whole algorithm.
Version 4: (A new approach, completely reformulated to prevent block exchange attacks. In development - might be flawed at its current state)
Note: for convenience, the IV is considered to be the 0th intermediate block, i.e. $I_0 = IV$
Encryption:
For all non-final plaintext blocks $P_k, 1 \le k < n$
Derive a non-final intermediate block $I_k$:
$I_k = P_k \oplus E(I_{k-1} \oplus P_{k+1} \oplus E(I_0))$
Derive a non-final ciphertext block $C_k$:
$C_k = I_k \oplus E(I_{k-1})$
For the final plaintext block $P_n$:
Derive a final intermediate block $I_n$:
$I_n = P_n \oplus E(I_{n-1})$
Derive a final ciphertext block $C_n$:
$C_n = E(I_n)$
Decryption:
Iterate forward to derive intermediate blocks:
Derive all non-final intermediate blocks $I_k, 1 \le k < n$:
$I_k = C_k \oplus E(I_{k-1})$
Derive the final intermediate block $I_n$:
$I_n = D(C_n)$
Iterate backwards to derive plaintext blocks:
Derive the final plaintext block $P_n$:
$P_n = I_n \oplus E(I_{n-1})$
Derive all non-final plaintext blocks $P_k, 1 \le k < n$:
$P_k = I_k \oplus E(I_{k-1} \oplus P_{k+1} \oplus E(I_0))$
The approach here is a bit more subtle. The outer encryption XORs the current intermediate block with the encryption of the previous one. This means that during the forward iterating part of the decryption process, the first corrupted ciphertext block would actually render the underlying intermediate block malleable, since changing one bit of its corresponding ciphertext block would only change one bit of it. The next ones would be randomly corrupted though, since they depend on an encryption of the previous ones. This is not a real issue though, since during the backwards stage it would be corrupted again, this time randomly since the next plaintext block would not be the expected one.
(There are currently some minor issues with the similarity of the inner and outer encryption, especially for the cases where the plaintext is 0. I'm looking for ways to get around that [without using two different keys]. The current solution is to XOR the inner encryption arguments with the constant value $E(I_0)$ as a secret random constant (per IV) to avoid situations where both the inner and outer will yield the same result and cancel each other - this doesn't require an additional encryption operation since it is needed anyway by the outer encryption. I'm also considering adding a counter if that would prove necessary)
The following diagram describes the encryption process (For $n=3$. The two layers have been separated for clarity, but it should be understood that the whole process occurs in one forward pass):
This one describes the two-stage decryption process (also for $n=3$).
NOTE: this is a work in progress, and might contain severe or careless mistakes.. It is very preliminary and there is still much to analyze - the scrutiny of the community is needed and strongly appreciated.
In case this algorithm does, at some point, evolve to a correct algorithm (though in the special case of $n \le 2$ it might already be at its current state), it would be published under CC-BY.
$I_2 = P_2 \oplus E_{k_2}(I_1)$ - The second block includes the propagation of the IV through the intermediate block. What I am considering though is whether the IV and the next plaintext block could be simply XORed and encrypted together instead of encrypting them separately. – Anon2000 May 21 '15 at 10:31