OneTimePad implementation fails. Maybe a stream problem?
I had some time and decided to implement a one time pad just for fun and self education. Now I end up with a weird behavior of the data. Its driving me crazy ^^. Would you please help me? Thanks in advance.
There is an encrypt method which takes as arguements:
- an
InputStream
for the plain text - an
OutputStreams
for the cipher text - and an
OutputStreams
for the key.
There is an decrypt method which takes as arguements:
- an
InputStream
for the cipher text - an
InputStream
for the key - an
OutputStreams
for the plain text.
There is a main method to test and debug the code. Here is the class:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
public class MyPad {
public static void encrypt(InputStream plainTextInputStream, OutputStream cipherTextOutputStream, OutputStream keyOutputStream) {
int plainTextByte;
SecureRandom random = new SecureRandom();
System.out.println("plain\tkey\tcipher");
try {
while((plainTextByte = plainTextInputStream.read()) != -1){
int keyByte = random.nextInt(256);
int cipherTextByte = (plainTextByte + keyByte) % 256;
System.out.println(plainTextByte + "\t" + keyByte + "\t" + cipherTextByte);
cipherTextOutputStrea开发者_开发百科m.write(cipherTextByte);
keyOutputStream.write(keyByte);
}
plainTextInputStream.close();
cipherTextOutputStream.close();
keyOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void decrypt(InputStream cipherTextInputStream, InputStream keyInputStream, OutputStream plainTextOutputStream) {
int cipherTextByte;
System.out.println("plain\tkey\tcipher");
try {
while((cipherTextByte = cipherTextInputStream.read()) != -1){
int keyByte = keyInputStream.read();
int plainTextByte = Math.abs(cipherTextByte - keyByte) % 256;
System.out.println(plainTextByte + "\t" + keyByte + "\t" + cipherTextByte);
plainTextOutputStream.write(plainTextByte);
}
cipherTextInputStream.close();
keyInputStream.close();
plainTextOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String plainText = "This is my plain text.";
InputStream plainTextInputStream = new ByteArrayInputStream(plainText.getBytes());
ByteArrayOutputStream cipherTextOutputStream = new ByteArrayOutputStream();
ByteArrayOutputStream keyOutputStream = new ByteArrayOutputStream();
System.out.println("------------------------------------ encrypting");
encrypt(plainTextInputStream, cipherTextOutputStream, keyOutputStream);
String cipherText = cipherTextOutputStream.toString();
String key = keyOutputStream.toString();
System.out.println("plaintext:\t" + plainText);
System.out.println("ciphertext:\t" + cipherText);
System.out.println("key:\t" + key);
InputStream cipherTextInputStream = new ByteArrayInputStream(cipherText.getBytes());
InputStream keyInputStream = new ByteArrayInputStream(key.getBytes());
ByteArrayOutputStream plainTextOutputStream = new ByteArrayOutputStream();
System.out.println("------------------------------------ decrypting");
decrypt(cipherTextInputStream, keyInputStream, plainTextOutputStream);
plainText = plainTextOutputStream.toString();
System.out.println("plaintext:\t" + plainText);
}
}
Now here is the problem I have. I encrypt a plain text and decrypt it immediatley, but the encrypted plain text is not the same as the original. I made some ouput for debugging and it seems like, the data I wrote during encryption is not the same data I read during decryption. Look at the output yourself:
------------------------------------ encrypting plain key cipher 84 25 109 104 239 87 105 86 191 115 74 189 32 100 132 105 17 122 115 211 70 32 147 179 109 104 213 121 118 239 32 139 171 112 244 100 108 196 48 97 181 22 105 226 75 110 94 204 32 156 188 116 92 208 101 91 192 120 165 29 116 177 37 46 49 95 plaintext: This is my plain text. ciphertext: mW���zF���d0K̼��%_ key: �VJdӓhv��ĵ�^�\[��1 ------------------------------------ decrypting plain key cipher 84 25 109 152 239 87 48 191 239 2 189 191 103 86 189 165 74 239 91 100 191 172 17 189 28 211 239 44 147 191 85 104 189 4 118 122 169 239 70 48 191 239 2 189 191 50 239 189 48 191 239 2 189 191 7 196 189 58 181 239 48 239 191 2 191 189 89 189 100 46 94 48 217 239 22 116 191 75 15 189 204 96 92 188 148 91 239 48 239 191 2 191 189 50 189 239 48 239 191 2 191 189 160 189 29 12 49 37 96 -1 95 plaintext: T�0g�[�,U�020:0Y.�t`�020�`
This looks really weird to me. Any suggestions where that difference is coming from?
Thanks in advance.
There are two problems:
- You use String#getBytes to get the bytes to feed back in. This means that they went through a round of string decoding and encoding. Notice that the sequence of key bytes is not the same on encryption and decryption. You should instead use ByteArrayOutputStream#toByteArray
Math.abs(cipherTextByte - keyByte) % 256
is wrong. (0 - 1) (mod 256) = 255 not 1. You should instead use(256 + cipherTextByte - keyByte) % 256
Instead of adding and subtracting and then %256'ing, you could use an xor. That'd accomplish the same task with less mathematical oddity. a ^ b
doesn't care about signs or remainders or any of that junk, never falls outside the bounds of its arguments (byte1 ^ byte2
will always be byte-sized), and it's easily reversible.
As for why you're having problems, though: When you use abs
to wrap your numbers, you end up breaking certain assumptions that go along with two's-complement numbers. (abs(-x)
and 256-x
are not equal in most cases.) That's making your math wonky, as what should be the sign bit ends up lost in the shuffle -- and other bits are wrongly flipped because of it, and can't be flipped back because you tossed out the sign with abs
.
For example, assume your cleartext byte is 65 ('A', if you care), and your RNG comes up with 192 for the pad byte. abs(65+192) % 256
will give you 1 as your "ciphertext". When you decrypt it, though, 1-192 == -191. In two's complement in 8 bits, that corresponds to 65, but abs(1-192)
gives you 191.
You'd do better to use (cipherTextByte + (256 - keyByte)) % 256
. In the example, 1 + (256-192) == 1 + 64 == 65.
Also, as has been mentioned, once you have encrypted bytes, they should be treated as just that -- bytes. They no longer form a string; they are and should always (or rather, til decrypted) be a sequence of bytes. Encodings and such could subtly modify the data, adding a byte here or converting something there, and you may end up with garbage when you try to decrypt it. But your biggest problem in your example is the math, rather than encoding issues.
May be it's simpler, from coding point of view, to use a one-time pad derivative, where single Unicode characters are used in stead of single bits?
The specification resides at http://longterm.softf1.com/specifications/txor/index.html
The security and proofs are pretty much the same, but there is no need to handle any of the character encoding, UTF-8, etc. part.
精彩评论