Java 相当于 C# AES 加密

Java equivalent of C# AES encryption

我在 C# 中有以下代码。它使用 AES 对称算法对字节数组进行编码。我需要编写 Java 等效于此代码。

class Program
{
    static void Main(string[] args)
    {
        string a = "ABCDEFGHIJKLMNOP";
        byte[] bytes = Encoding.ASCII.GetBytes(a);
        byte[] cipher = encode(bytes, "1111111122222222111111112222222211111111222222221111111122222222", "66666666555555556666666655555555");
    }

    private static byte[] encode(byte[] toEncrypt, string sKey, string sIV)
    {
        byte[] IV = new byte[16];
        byte[] key = new byte[32];
        byte[] array = new byte[toEncrypt.Length];
        string s;

        for (int i = 0; i < IV.Length; ++i)
        {
            s = sIV.Substring(i * 2, 2);
            IV[i] = Convert.ToByte(s, 16);
        }

        for (int i = 0; i < key.Length; ++i)
        {
            s = sKey.Substring(i * 2, 2);
            key[i] = Convert.ToByte(s, 16);
        }

        MemoryStream filecrypt = new MemoryStream(array);

        AesManaged encrypt = new AesManaged();
        encrypt.Mode = CipherMode.CBC;
        encrypt.Padding = PaddingMode.None;
        encrypt.BlockSize = 128;
        encrypt.KeySize = 256;

        CryptoStream cs = new CryptoStream(filecrypt, encrypt.CreateEncryptor(key, IV), CryptoStreamMode.Write);
        cs.Write(toEncrypt, 0, toEncrypt.Length);
        cs.Close();

        return array;
    }
}

这是我在Java中写的尝试。代码看起来没问题,但是输出不一样,一定是哪里出错了。

public class Main {

    public static void main(String [] args) {
        byte [] code = encode("ABCDEFGHIJKLMNOP".getBytes(), "1111111122222222111111112222222211111111222222221111111122222222", "66666666555555556666666655555555");
    }

    private static byte[] toByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        int a;
        int b;
        for (int i = 0; i < len; i += 2) {
            a = (Character.digit(s.charAt(i), 16) << 4);
            b = Character.digit(s.charAt(i+1), 16);
            int n = (Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16);
                data[i / 2] = (byte) (n);
        }
        return data;
    }

    private static byte[] encode(byte[] toEncrypt, String skey, String siv)
    {
        byte[] key = toByteArray(skey);
        byte[] iv = toByteArray(siv);

        byte[] array = new byte[toEncrypt.length];

        Cipher cipher;

        try {
            cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE,  new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
            array = cipher.doFinal(array);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return array;
    }
}

任何线索和想法将不胜感激。

就个人而言,如果您的 objective 只是使用 Java 进行 AES 加密,您不应该将您的代码基于 C# class。当然它们可能很相似,但是 Java 已经有强大的库。

除此之外,我希望我有我的加密书在这里向您解释它,但不幸的是我现在能做的最好的只是为您提供一个别人尝试过的很好的例子:

希望这些链接能帮助您实现 objective。

此外,关于您的特定 C# 代码,我看不到您在 Java 中指定以下代码的位置:

encrypt.BlockSize = 128;
encrypt.KeySize = 256;

在第二个教程中,我建议您举一个示例,其中指定密钥大小。希望我帮到了你!

我不太了解 C#,但通常您希望多个连续的加密结果不同。这就是为 AES 算法指定初始 IV 的原因。加密代码可能如下所示:

  public String encrypt( String stringToEncrypt, IvParameterSpec ivSpec ) {
    if ( stringToEncrypt == null ) {
      return null;
    }
    try {
      Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding");
      SecretKeySpec keySpec = new SecretKeySpec( key, "AES" );
      cipher.init( Cipher.ENCRYPT_MODE, keySpec, ivSpec );
      byte[] data = cipher.doFinal( stringToEncrypt.getBytes( "UTF-8" ) );
      return String.format( "%s:%s", Base64.encode( ivSpec.getIV() ), Base64.encode( data ) );
    } catch ( Exception e ) {
      throw new RuntimeException( "Unable to encrypt the string", e );
    }
  }

您的密钥和 IV 应该使用 SecureRandom 生成,因为这提供了 java 中的最佳熵:

byte[] iv = new byte[32];
random.nextBytes( iv );
byte[] key = new byte[32];
random.nextBytes( key );

此外,您可能希望事后计算 HMAC - java 此处也支持多种解决方案。通过检查接收方的 HMAC,您可以防止填充 oracle 攻击。

为了比较不同的加密结果,我会比较它们的 base64 编码。

注意:可以将 IV 保存在密文旁边 - 它只是用来防止预计算攻击。

您出于某种原因正在初始化 byte[] array = new byte[toEncrypt.length];,但您从未在加密前将 toEncrypt 的内容写入其中。您可以使用 System.arraycopy(toEncrypt, 0, array, 0, array.length);,但只使用

会更容易
byte[] array;
...
array = cipher.doFinal(toEncrypt);
...
return array;
public String notify(String message, String encryptionKey) {
    Security.addProvider(new BouncyCastleProvider());
    // System.out.println(message);
    byte[] key = Base64.decode(encryptionKey);
    SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
    byte[] data = Base64.decode(message);
    String decryptedString = "";
    try {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(data);
        decryptedString = new String(decrypted);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println(decryptedString);
    return decryptedString;
}

此代码应解密添加所需填充的加密消息 - 默认 128 位,但您需要提供加密密钥。 然而,这是我的相同代码的 C# 版本

 void DecryptMessage(string message)
    {
        var deserializedMessage = JsonConvert.DeserializeObject<List<string>>(message.ToString());
        byte[] decodedEncryptionKey = Convert.FromBase64String(encryptkey);
        byte[] data = Convert.FromBase64String(deserializedMessage[0]);
        byte[] iv = new byte[16];
        AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
        aes.BlockSize = 128;
        aes.KeySize = 128;
        aes.Mode = CipherMode.ECB;
        aes.Padding = PaddingMode.PKCS7;

        using (ICryptoTransform decrypt = aes.CreateDecryptor(decodedEncryptionKey, iv))
        {
            byte[] dest = decrypt.TransformFinalBlock(data, 0, data.Length);
            decrypt.Dispose();
            Console.WriteLine(Encoding.UTF8.GetString(dest));
        }
    }