In the case of the one-time pad (OTP), the simplest deniable encryption scheme could employ the use of a Decoy Message and Decoy Key that create a Universal Ciphertext that will be the same Univeral Ciphertext that will be XORed with the Real Message, resulting in deterministic Real Key (provided that the length of the real message is at least as long as the decoy message and resulting keys and ciphertext to avoid the need of padding).
- The drawback will be treating plaintext bits as a private key, in the sense of
generating a secure key (Real Key), since it is entirely deterministically derived from a
"plain text" referred to here as Real
Message.
Thus the security of the real key in bits will be contingent on the
randomness and length (and any resulting or inherit security) of the real message
in bits (so the message needs to be treated literally and figuratively as a private key in terms of securing the whole system for the real message to be secure, since it is not being "secured" with the private key (rather the private key is being created from the message). Below is an example:
pl= decoy key
k1= decoy message
c1 = universal ciphertext
p2= real key
k2= real message
p1⊕k1 =c1
c1⊕k2 = k1
*Decoy Message (length n) ⊕ Decoy Key (length n) = Universal Ciphertext (length n)
*Universal Ciphertext (length n) ⊕ Real Message (length n) = Real Key (length n)
Opinion: Such an encryption method for plausible deniability could work (perfect secrecy and quantum secure) if the message itself was sufficiently random and provided that its length in bits was unfeasible to brute-force guess or collide with (i.e. the real message could be a private key itself that was generated by a CSPRNG) and 128-bits long, for example. Else, a short or predictable real message, would produce a real key that is not secure.
Important security assumptions: for a sufficiently secure real message (plaintext), such as a CSPRNG-generated 256-bit integer, the resulting real key that is computed after XOR'ing against the Universal ciphertext, where the square root of [message space * key space] = ciphertext space (including duplicates is 2^512 because of XOR's commutativity), of which there will at least that many times (2^256) other messages and keys, will produce the same universal ciphertext. Thus there is no way to know which message or key is the one in question as they will all seem valid, without having prior knowledge of the real key, if the message (treated as a key generator) is sufficiently secure. This limit/range is governed by the number of distinct XOR equations that exist (without permutation) for any arbitrary range of real integers (i.e. n=256) following sequence A028401.
Example Python code to compute distinct XOR:
Initial_bits= int(input("enter number of bits"))
Initial_number_range= 2**Initial_bits
Unique_XOR_triplets=((Initial_number_range+1)*(Initial_number_range+2))//6
Total_triplet_input_terms =((Initial_number_range//2)+1)*(Initial_number_range)+(Initial_number_range//2)+1
Repeat_Groups=(Total_triplet_input_terms)-((Initial_number_range**2)//2)-Initial_number_range
Checksum_repeat_group=(Initial_number_range//2)+1
print('1: Initial_bits, this will become the exponent for 2 raised to this power:',Initial_bits)
print('2: Initial_number_range, two raised to the number of the initial bits equals this:',Initial_number_range)
print('3: Unique_XOR_triplets, three input terms counts as one:',Unique_XOR_triplets)
print('4: Total_triplet_input_terms (i.e. A XOR B = C would be three input terms):',Total_triplet_input_terms)
print('5: Repeat_Groups, each number in group repeats this many times, and an extra this many times of zero:',Repeat_Groups)
Python code with plausible deniability encryption scheme using insecure 56-bit strings as example:
p1= 0b01101000011001010110110001101100011011110000000000000000 #plaintext1 DUMMY MESSAGE 56-bit example ASCII for "hello" padded to length of real message: 0b01101000011001010110110001101100011011110000000000000000
p1=bin(p1)
k1= 0b10011001100110011001100110011001100110011001100110011000 #key1 DUMMY KEY 56-bit example: 0b10011001100110011001100110011001100110011001100110011000
k1=bin(k1)
c1= int(p1,2)^int(k1,2) #ciphertext (computed DETERMINISTIC 56-bit derived from XOR'ing dummy message with dummy key): "0b11110001111111001111010111110101111101101001100110011000"
c1=bin(c1)
p2= 0b01101100011001010110000101110110011010010110111001100111 #plaintext2 REAL MESSAGE 8-bit example ASCII for "leaving" : 0b01101100011001010110000101110110011010010110111001100111
p2=bin(p2)
k2= int(c1,2)^int(p2,2) #key2 REAL KEY ((computed DETERMINISTIC) 56-bit derived from XOR'ing Ciphertext with Real message: 0b10011101100110011001010010000011100111111111011111111111
k2=bin(k2)
print('p1 DUMMY MESSAGE is:',p1)
print('k1 DUMMY KEY is:',k1)
print('c1 UNIVERSAL CIPHERTEXT is:',c1)
print('p2 REAL MESSAGE is:',p2)
print('k2 REAL KEY is:',k2) ## this "Key" is deterministic thus only as strong as the randomness of the message
print('real message p2: ', (p2),
'is truely',(int(p2,2))== int(k2,2)^int(c1,2))
print('as c1 ',c1,'xor p2', p2, 'equals',bin(int(k2,2)), 'is truely')
print((int(k2,2)==(int(c1,2)^(int(p2,2)))))
print('and equals = ',bin(int(c1,2)^(int(p2,2))))