RC4 encryption java

46,002

Solution 1

There are a few things to notice:

  • Java is not very easy to use when you require unsigned bytes (e.g. for indexing);
  • if you create a state in S and T, you should really notice that these values change, when you decrypt with the same instance you take the state used for encryption;
  • the above code is not very efficient memory wise, and you can easily rewrite it to take byte arrays;
  • to use a String, after refactoring the arguments to byte[], you first need to use first, e.g. using String.getBytes(Charset charset);

To make life easier, and to have some fun late night hacking, I improved your code and tested it against a single vector in rfc6229 using a zero'd out byte array.

UPDATE: As micahk points out below, the evil C XOR swap that was used prevented this code from encrypting the final byte of input in Java. Using regular old swaps fixes it.

Warning: the code below should be considered a coding exercise. Please use a well vetted library instead of the code snippet below to perform RC4 (or Ron's Code 4, ARC4 etc.) in your application. That means using Cipher.getInstance("RC4"); or the ARC4 classes in Bouncy Castle.

public class RC4 {
    private final byte[] S = new byte[256];
    private final byte[] T = new byte[256];
    private final int keylen;

    public RC4(final byte[] key) {
        if (key.length < 1 || key.length > 256) {
            throw new IllegalArgumentException(
                    "key must be between 1 and 256 bytes");
        } else {
            keylen = key.length;
            for (int i = 0; i < 256; i++) {
                S[i] = (byte) i;
                T[i] = key[i % keylen];
            }
            int j = 0;
            byte tmp;
            for (int i = 0; i < 256; i++) {
                j = (j + S[i] + T[i]) & 0xFF;
                tmp = S[j];
                S[j] = S[i];
                S[i] = tmp;
            }
        }
    }

    public byte[] encrypt(final byte[] plaintext) {
        final byte[] ciphertext = new byte[plaintext.length];
        int i = 0, j = 0, k, t;
        byte tmp;
        for (int counter = 0; counter < plaintext.length; counter++) {
            i = (i + 1) & 0xFF;
            j = (j + S[i]) & 0xFF;
            tmp = S[j];
            S[j] = S[i];
            S[i] = tmp;
            t = (S[i] + S[j]) & 0xFF;
            k = S[t];
            ciphertext[counter] = (byte) (plaintext[counter] ^ k);
        }
        return ciphertext;
    }

    public byte[] decrypt(final byte[] ciphertext) {
        return encrypt(ciphertext);
    }
}

Happy coding.

Solution 2

The Java code has a bug due to the use of the xor-swap technique:

        S[i] ^= S[j];
        S[j] ^= S[i];
        S[i] ^= S[j];

Instead of this, you'll want to use a temp variable as in the below. I haven't delved into why the result isn't as expected with the xor swap, but I had decryption errors with this that were resolved by simply doing a straight-forward swap. I suspect it to be a subtle-side effect of the implicit cast from byte to int that occurs in order to do the xor operation.

public class RC4 {
    private final byte[] S = new byte[256];
    private final byte[] T = new byte[256];
    private final int keylen;

    public RC4(final byte[] key) {
        if (key.length < 1 || key.length > 256) {
            throw new IllegalArgumentException(
                    "key must be between 1 and 256 bytes");
        } else {
            keylen = key.length;
            for (int i = 0; i < 256; i++) {
                S[i] = (byte) i;
                T[i] = key[i % keylen];
            }
            int j = 0;
            for (int i = 0; i < 256; i++) {
                j = (j + S[i] + T[i]) & 0xFF;
                byte temp = S[i];
                S[i] = S[j];
                S[j] = temp;
            }
        }
    }

    public byte[] encrypt(final byte[] plaintext) {
        final byte[] ciphertext = new byte[plaintext.length];
        int i = 0, j = 0, k, t;
        for (int counter = 0; counter < plaintext.length; counter++) {
            i = (i + 1) & 0xFF;
            j = (j + S[i]) & 0xFF;
            byte temp = S[i];
            S[i] = S[j];
            S[j] = temp;
            t = (S[i] + S[j]) & 0xFF;
            k = S[t];
            ciphertext[counter] = (byte) (plaintext[counter] ^ k);
        }
        return ciphertext;
    }

    public byte[] decrypt(final byte[] ciphertext) {
        return encrypt(ciphertext);
    }
}

Solution 3

Your integer arrays S andT have not been constructed. Hence you get a NullPointerException as soon as you attempt to use them.

Looking at the rest of the code, I guess they should have been 256-item arrays:

private int[] S = new int[256];
private int[] T = new int[256];

Solution 4

(I know this is a old thread, but maybe my answer can help who is reading it)

The problem is not in the RC4 code but in how you are using it. What you have to understand is every time that encript method is invoked, the S array is modified to generate a pseudo random key.

In this code your are using the decript method after encript over the same instance of RC4 class. But RC4 class have the key creation in the constructor, so when you execute decript method, the key is not recently created as it has been modified by the previous encript. Instead of this code:

int[] cipher = rc4.encrypt(text); //encryption      
System.out.print("\ncipher: ");
for (int i = 0; i < cipher.length; i++) {          
    System.out.print(cipher[i]);          
}    

int[] backtext = rc4.decrypt(cipher); //decryption
System.out.print("\nback to text: ");
for (int i = 0; i < backtext.length; i++) {          
    System.out.print(backtext[i]);            
} 

Use a rc4 new instance before decript:

int[] cipher = rc4.encrypt(text); //encryption      
System.out.print("\ncipher: ");
for (int i = 0; i < cipher.length; i++) {          
    System.out.print(cipher[i]);          
}    

rc4 = new RC4(keytest);
int[] backtext = rc4.decrypt(cipher); //decryption
System.out.print("\nback to text: ");
for (int i = 0; i < backtext.length; i++) {          
    System.out.print(backtext[i]);            
} 

So the decript method will be have a clean S array, and it will be able to obtain S sequence in the same order than the previous encript method.

Solution 5

1) int array: probably because Java doesn't support unsigned bytes.

2) Null exception: I counted line 12 being this one: S[i] = i; It looks like the S array is not being constructed before it's used.

Share:
46,002
Ronaldinho Learn Coding
Author by

Ronaldinho Learn Coding

Updated on August 08, 2022

Comments

  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding almost 2 years

    Hi there I am trying to implement the RC4 algorithm in Java. I found this code as an example that help me to understand the idea:

    public class RC4 {
      private int[] S = new int[256];
      private int[] T = new int[256];
      private int keylen;
    
      public RC4(byte[] key) throws Exception {
        if (key.length < 1 || key.length > 256) {
          throw new Exception("key must be between 1 and 256 bytes");
        } else {
          keylen = key.length;
          for (int i = 0; i < 256; i++) {
            S[i] = i;
            T[i] = key[i % keylen];
          }
          int j = 0;
          for (int i = 0; i < 256; i++) {
            j = (j + S[i] + T[i]) % 256;
            S[i] ^= S[j];
            S[j] ^= S[i];
            S[i] ^= S[j];
          }
        }
      }
    
      public int[] encrypt(int[] plaintext) {
        int[] ciphertext = new int[plaintext.length];
        int i = 0, j = 0, k, t;
        for (int counter = 0; counter < plaintext.length; counter++) {
          i = (i + 1) % 256;
          j = (j + S[i]) % 256;
          S[i] ^= S[j];
          S[j] ^= S[i];
          S[i] ^= S[j];
          t = (S[i] + S[j]) % 256;
          k = S[t];
          ciphertext[counter] = plaintext[counter] ^ k;
        }
        return ciphertext;
      }
    
      public int[] decrypt(int[] ciphertext) {
        return encrypt(ciphertext);
      }
    }
    

    I have few question:

    1. Why is the plain-text an int array in the above code?

    2. When I test this code I get strange result, can somebody explain to me? Here my code to test:

      public class RC4_Main {
      
          public static void main(String args[]) throws Exception {
              String keyword = "hello";
              byte[] keytest = keyword.getBytes(); //convert keyword to byte
      
              int[] text = {1, 2, 3, 4, 5}; // text as 12345
      
              RC4 rc4 = new RC4(keytest);
      
              System.out.print("\noriginal text: ");
              for (int i = 0; i < text.length; i++) {          
                  System.out.print(text[i]);          
              }    
      
              int[] cipher = rc4.encrypt(text); //encryption      
              System.out.print("\ncipher: ");
              for (int i = 0; i < cipher.length; i++) {          
                  System.out.print(cipher[i]);          
              }    
      
              int[] backtext = rc4.decrypt(cipher); //decryption
              System.out.print("\nback to text: ");
              for (int i = 0; i < backtext.length; i++) {          
                  System.out.print(backtext[i]);            
              } 
              System.out.println();
          }
      }
      

    Here is the result: (original and back to text are not SAME) why???

    original text: 12345
    cipher: 1483188254174
    back to text: 391501310217
    
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    Yes I fixed as your code above, it worked now but why plain text must be an integer array, how can I use string instead?
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    what do you mean by "unsigned bytes"? I mean the text should be anythings ( at least it should be string) why did the author implement it as an int array, I just don't understand that point?
  • theglauber
    theglauber over 11 years
    I'm only guessing, because i didn't write that code. In order to encrypt, you will be treating your string as a sequence of bytes, and doing mathematical operations on those bytes. For whatever historical reason, Java lacks an unsigned byte type, and often people use integers to perform math on Java bytes. The code as presented seems incomplete.
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    I edited my question, I test the code, but the encryption and decryption give me different result, you know why?
  • Maarten Bodewes
    Maarten Bodewes over 11 years
    There is really no reason to use int[] instead of a byte[] except if the author only wants to show positive numbers internally. All required operations work identically on signed bytes values as on unsigned byte values. The code gets a bit uglier though as most expressions (that do not use assignment operators) will return an integer as result, requiring a cast back to byte.
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    Hi there! Thank you so much for helping me but can you provide a simple pieces of codes that used to test your above code. I want "key" and "plain-text" that will be read from files (key file and plain-text file) in binary mode (is that mean convert contain of file to byte?). But now I just need a sample code to test this class, can you help me?
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    Pls help me how to use your code, I tested it but give me very strange result....
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    I tested code, create 2 separated RC4 objects(for encrypt and decrypt). Result after decryption is same as input plain-text => code maybe good, but I can't display the cipher-text (gave me strange characters after I convert byte to String) , I mean how can I verify if cipher-text is correct (compare to other implementation in other languages C# or C++), how to display that ciphertext?
  • Maarten Bodewes
    Maarten Bodewes over 11 years
    The output of most known ciphers is a number of bits or bytes that can have any value. Those bytes may not encode printable characters (using e.g. ASCII decoding). If you want ciphertext that can be displayed, use hexadecimal encoding. For transport use Base64. Both are available in the Apache commons libraries, Google them!
  • Maarten Bodewes
    Maarten Bodewes over 11 years
    Did you get it to work? I haven't tested bigger data yet, just a few bytes.
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    Yes it worked, I mean after you encrypted original input text you get a cipher-text, decrypt that cipher-text again you will get exactly original input but I just dont know to see the cipher - text, still have difficult to convert it to Base64 as you said
  • Maarten Bodewes
    Maarten Bodewes over 11 years
    Try hexadecimals first. A good IDE such as Eclipse can also show you the byte array in hexadecimals, but you have to configure the "Variables" view correctly using the drop down box first. Then click the plus sign in front of the array to see all elements.
  • Maarten Bodewes
    Maarten Bodewes over 11 years
  • Ronaldinho Learn Coding
    Ronaldinho Learn Coding over 11 years
    Thanks very much for great support! I'll try to figure it out.
  • ChandlerQ
    ChandlerQ almost 11 years
    I've copied your code and run it, but the encrypted result seems not correct. Key is key and plaintext is hello, the online RC4 encryption tools show that the result be 63 09 58 81 4B, but your result is 63 09 58 81 6F. Please give it a check.
  • Ramachandra A Pai
    Ramachandra A Pai over 5 years
    Thanks @Pierre.Vriens. Wasn't sure if I should duplicate the answer here. I have corrected the same.
  • Ramachandra A Pai
    Ramachandra A Pai over 5 years
    @Pierre.Vriens can you please revert the downvote now that the correction has been made?
  • Ramachandra A Pai
    Ramachandra A Pai over 5 years
    Hahaha. Sure. So that means it is possible.
  • Dan Ortega
    Dan Ortega about 3 years
    Thank you so much. This is the right answer.