RC4 encryption java
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
andT
, 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 character-encoding first, e.g. usingString.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.
Ronaldinho Learn Coding
Updated on August 08, 2022Comments
-
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:
Why is the plain-text an
int
array in the above code?-
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 over 11 yearsYes 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 over 11 yearswhat 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 over 11 yearsI'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 over 11 yearsI edited my question, I test the code, but the encryption and decryption give me different result, you know why?
-
Maarten Bodewes over 11 yearsThere 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 over 11 yearsHi 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 over 11 yearsPls help me how to use your code, I tested it but give me very strange result....
-
Ronaldinho Learn Coding over 11 yearsI 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 over 11 yearsThe 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 over 11 yearsDid you get it to work? I haven't tested bigger data yet, just a few bytes.
-
Ronaldinho Learn Coding over 11 yearsYes 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 over 11 yearsTry 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 over 11 years
-
Ronaldinho Learn Coding over 11 yearsThanks very much for great support! I'll try to figure it out.
-
ChandlerQ almost 11 yearsI've copied your code and run it, but the encrypted result seems not correct. Key is
key
and plaintext ishello
, the online RC4 encryption tools show that the result be63 09 58 81 4B
, but your result is63 09 58 81 6F
. Please give it a check. -
Ramachandra A Pai over 5 yearsThanks @Pierre.Vriens. Wasn't sure if I should duplicate the answer here. I have corrected the same.
-
Ramachandra A Pai over 5 years@Pierre.Vriens can you please revert the downvote now that the correction has been made?
-
Ramachandra A Pai over 5 yearsHahaha. Sure. So that means it is possible.
-
Dan Ortega about 3 yearsThank you so much. This is the right answer.