【问题标题】:RC4 encryption javaRC4加密java
【发布时间】:2012-09-05 21:01:29
【问题描述】:

您好,我正在尝试用 Java 实现 RC4 算法。我发现这个code 是一个帮助我理解这个想法的例子:

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);
  }
}

我有几个问题:

  1. 为什么上面代码中的纯文本是int数组?

  2. 当我测试这段代码时,我得到了奇怪的结果,有人能给我解释一下吗?这是我要测试的代码:

    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();
        }
    }
    

结果如下:(原文和回文不一样)为什么???

original text: 12345
cipher: 1483188254174
back to text: 391501310217

【问题讨论】:

    标签: java encryption rc4-cipher


    【解决方案1】:

    有几点需要注意:

    • 当您需要无符号字节(例如用于索引)时,Java 不是很容易使用;
    • 如果您在ST 中创建一个状态,您应该真正注意到这些值发生了变化,当您使用相同的实例解密时您采用了用于加密的状态;李>
    • 上述代码在内存方面效率不高,您可以轻松地将其重写为字节数组;
    • 要使用String,在将参数重构为byte[]后,首先需要先使用,例如使用String.getBytes(Charset charset);

    为了让生活更轻松,并在深夜黑客攻击中获得乐趣,我改进了您的代码,并使用零字节数组针对 rfc6229 中的单个向量对其进行了测试。

    更新:正如 micahk 在下面指出的那样,使用的邪恶 C XOR 交换阻止了此代码加密 Java 中输入的最后一个字节。使用常规的旧交换可以修复它。

    警告:下面的代码应视为编码练习。请使用经过严格审查的库而不是下面的代码 sn-p 在您的应用程序中执行 RC4(或 Ron 的代码 4、ARC4 等)。这意味着使用 Cipher.getInstance("RC4"); 或 Bouncy Castle 中的 ARC4 类。

    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);
        }
    }
    

    编码愉快。

    【讨论】:

    • 您好!非常感谢您对我的帮助,但是您能否提供一些用于测试上述代码的简单代码。我想要以二进制模式从文件(密钥文件和纯文本文件)中读取的“密钥”和“纯文本”(这意味着将文件的包含转换为字节吗?)。但是现在我只需要一个示例代码来测试这个类,你能帮帮我吗?
    • 请帮助我如何使用你的代码,我测试了它但给了我很奇怪的结果......
    • 大多数已知密码的输出是一些可以具有任何值的位或字节。这些字节可能无法对可打印字符进行编码(例如使用 ASCII 解码)。如果您想要可以显示的密文,请使用十六进制的encoding。对于传输使用 Base64。两者都可以在 Apache 公共库中找到,谷歌它们!
    • 先尝试十六进制。 Eclipse 等好的 IDE 也可以以十六进制显示字节数组,但您必须首先使用下拉框正确配置“变量”视图。然后点击数组前面的加号查看所有元素。
    【解决方案2】:

    您的整数数组ST 尚未构造。因此,一旦您尝试使用它们,您就会得到一个NullPointerException

    查看其余代码,我猜它们应该是 256 项数组:

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

    【讨论】:

    • 是的,我按照上面的代码进行了修复,它现在可以工作了,但是为什么纯文本必须是整数数组,我怎样才能使用字符串呢?
    【解决方案3】:

    由于使用了异或交换技术,Java 代码有一个错误:

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

    您需要使用一个临时变量,如下所示。我还没有深入研究为什么 xor 交换的结果不像预期的那样,但是我有解密错误,通过简单地进行直接交换就可以解决。我怀疑这是为了执行 xor 操作而发生的从 byte 到 int 的隐式转换的微妙副作用。

    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);
        }
    }
    

    【讨论】:

      【解决方案4】:

      1) int 数组:可能是因为 Java 不支持无符号字节。

      2) 空异常:我数第 12 行是这一行:S[i] = i; 看起来 S 数组在使用之前没有被构造。

      【讨论】:

      • “无符号字节”是什么意思?我的意思是文本应该是任何东西(至少应该是字符串)为什么作者将它实现为一个int数组,我就是不明白这一点?
      • 我只是猜测,因为我没有编写该代码。为了加密,您将把字符串视为字节序列,并对这些字节进行数学运算。无论出于何种历史原因,Java 都缺少无符号字节类型,人们经常使用整数对 Java 字节进行数学运算。提供的代码似乎不完整。
      • 我编辑了我的问题,我测试了代码,但是加密和解密给了我不同的结果,你知道为什么吗?
      • 真的没有理由使用 int[] 代替 byte[] 除非作者只想在内部显示正数。所有必需的操作对有符号字节值的工作与对无符号字节值的工作相同。尽管大多数表达式(不使用assignment operators)将返回一个整数作为结果,但代码变得有点难看,需要强制转换回byte
      【解决方案5】:

      (我知道这是一个旧线程,但也许我的回答可以帮助谁正在阅读它)

      问题不在于 RC4 代码,而在于您如何使用它。 您必须了解的是,每次调用 encript 方法时,都会修改 S 数组以生成伪随机密钥。

      在此代码中,您在对同一 RC4 类实例进行加密后使用 decript 方法。但是 RC4 类在构造函数中创建了密钥,因此当您执行 decript 方法时,密钥不是最近创建的,因为它已被先前的 encript 修改。而不是这段代码:

      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]);            
      } 
      

      在 decript 之前使用一个 rc4 新实例:

      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]);            
      } 
      

      所以 decript 方法会有一个干净的 S 数组,并且它能够以与之前的 encript 方法相同的顺序获得 S 序列。

      【讨论】:

      • 非常感谢。这是正确的答案。
      【解决方案6】:

      RC4 是一种损坏的算法,如果要保持数据高度安全,建议不要再使用相同的算法。

      如果您仍需要有效的实现,则无需在代码中重新创建算法。 Java API javax.crypto 可以为您完成。只需生成一个密钥并调用模式设置为加密/解密的 init 方法。

      static String decryptRC4() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{
      
          byte[] testDataBytes = "testString".getBytes();
      
          KeyGenerator rc4KeyGenerator = KeyGenerator.getInstance("RC4");
          SecretKey key = rc4KeyGenerator.generateKey();
      
          // Create Cipher instance and initialize it to encrytion mode
          Cipher cipher = Cipher.getInstance("RC4");  // Transformation of the algorithm
          cipher.init(Cipher.ENCRYPT_MODE, key);
          byte[] cipherBytes = cipher.doFinal(testDataBytes);
      
          // Reinitialize the Cipher to decryption mode
          cipher.init(Cipher.DECRYPT_MODE,key, cipher.getParameters());
          byte[] testDataBytesDecrypted = cipher.doFinal(cipherBytes);
      
          System.out.println("Decrypted Data : "+new String(testDataBytesDecrypted));
          return new String(testDataBytesDecrypted);
      }
      

      输出:

      如果您需要将加密数据作为 url 的一部分发送,请使用 Base64Encoding 然后发送。

      例如

          static String decryptRC4() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{
      
          byte[] plainBytes = "testString".getBytes();
      
          KeyGenerator rc4KeyGenerator = KeyGenerator.getInstance("RC4");
          SecretKey key = rc4KeyGenerator.generateKey();
      
          // Create Cipher instance and initialize it to encrytion mode
          Cipher cipher = Cipher.getInstance("RC4");  // Transformation of the algorithm
          cipher.init(Cipher.ENCRYPT_MODE, key);
          byte[] cipherBytes = cipher.doFinal(plainBytes);
      
          String encoded = encodeBase64(cipherBytes);
      
          String decoded = decodeBase64(encoded);
      
          // Reinitialize the Cipher to decryption mode
          cipher.init(Cipher.DECRYPT_MODE,key, cipher.getParameters());
          byte[] plainBytesDecrypted = cipher.doFinal(Hex.decode(decoded));
      
          System.out.println("Decrypted Data : "+new String(plainBytesDecrypted));
          return new String(plainBytesDecrypted);
      }
      
      static String decodeBase64(String encodedData){
          byte[] b = Base64.getDecoder().decode(encodedData);
          String decodedData = DatatypeConverter.printHexBinary(b);
          return decodedData;
      }
      
      static String encodeBase64(byte[] data){
          byte[] b = Base64.getEncoder().encode(data);
          String encodedData = new String(b);
          /*String encodedData = DatatypeConverter.printHexBinary(b);*/
          return encodedData;
      }
      

      提示:如上所示使用 Hex.decode 从 base64 解码字符串中获取字节,否则您将遇到编码问题。尽可能使用 Hex 进行转换,并使用 bouncycastle 方法转换为字节数组。

      需要进口:

      import java.io.IOException;
      import java.security.InvalidAlgorithmParameterException;
      import java.security.InvalidKeyException;
      import java.security.MessageDigest;
      import java.security.NoSuchAlgorithmException;
      import java.util.Base64;
      
      import javax.crypto.BadPaddingException;
      import javax.crypto.Cipher;
      import javax.crypto.IllegalBlockSizeException;
      import javax.crypto.KeyGenerator;
      import javax.crypto.NoSuchPaddingException;
      import javax.crypto.SecretKey;
      import javax.xml.bind.DatatypeConverter;
      
      import org.apache.commons.codec.DecoderException;
      import org.bouncycastle.util.encoders.Hex;
      

      此外,如果您从自己的字符串生成密钥,则可以使用 MD5Hashing 来实现。

      有关如何使用自定义字符串创建密钥的帮助,请参阅此内容: https://stackoverflow.com/a/52463858/5912424

      【讨论】:

      • 谢谢@Pierre.Vriens。不确定我是否应该在这里复制答案。我已经更正了。
      • @Pierre.Vriens 既然已经进行了更正,请您恢复否决票吗?
      • 哈哈哈。当然。所以这意味着它是可能的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-09
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      相关资源
      最近更新 更多