开发者

OneTimePad implementation fails. Maybe a stream problem?

开发者 https://www.devze.com 2023-01-16 13:14 出处:网络
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 ad

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:

  1. 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
  2. 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消