我的任务是将C#加密方法转换为Java并且卡住了。我知道C#代码有效,但是我无法使Java代码工作。
这是C#代码:
private const int Nb = 4; // Legal values: 4 = 128-bit blocks
public static void Decrypt(byte[] input, Stream output)
{
var s1 = new MemoryStream(input);
const int BufferSize = 1024;
byte[] buffer = new byte[BufferSize];
input.Read(buffer, 0, 4);
int pad = buffer[3];
RijndaelManaged rijndael = new RijndaelManaged();
rijndael.BlockSize = Nb * 32;
rijndael.KeySize = buffer[1] * 32;
rijndael.Mode = CipherMode.ECB;
rijndael.Padding = PaddingMode.None;
byte[] key = GetKey(buffer[1]);
ICryptoTransform decryptor = rijndael.CreateDecryptor(key, GetIV());
int bytes;
while ((bytes = input.Read(buffer, 0, BufferSize)) > 0)
{
for (int i = 0; i < bytes; i += rijndael.BlockSize)
{
decryptor.TransformBlock(buffer, i, rijndael.BlockSize, buffer, i);
}
output.Write(buffer, 0, bytes);
}
output.SetLength(output.Length - pad - 4);
}
到目前为止,这是我在Java中的尝试:
public static String decrypt(byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
byte[] key = getKey(input[1]);
SecretKey secretKey = new SecretKeySpec(key, 0, key.length, "AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(getIV()));
// remove first 4 since C# code reads past those
byte[] finalDecoded = Arrays.copyOfRange(input, 4, input.length);
byte[] decryptedVal = cipher.doFinal(finalDecoded);
return new String(decryptedVal);
}
更多信息
对于GetIV
和GetKey
,我可以保证java中的结果是相同的(我比较了每个字节),但我不包括那些方法,因为我相信这是敏感的信息。我还可以保证输入字节[]是相同的并且(冗余地)相同的长度。
调试尝试: Java中的当前错误是ECB mode cannot use IV
。
new IvParameterSpec(getIV())
我收到此错误:Wrong algorithm: AES or Rijndael required
AES
或仅Rijndael
,我会收到此错误:Input length must be multiple of 16 when decrypting with padded cipher
。读取过去/删除前4个字节后,输入长度开始为424
和420
。我已经验证了Java和C#的输入字节是相同的。我在Java代码中哪里出错?
答案 0 :(得分:6)
您收到错误ECB mode cannot use IV
,因为ECB不执行链接,因此IV无意义。不同之处是Java会抛出错误而C#只会忽略IV。
当我删除此代码时:
new IvParameterSpec(getIV())
我收到此错误:Wrong algorithm:
AES or Rijndael
required
如果我将算法更改为仅AES或仅Rijndael,我会收到此错误:
Input length must be multiple of 16 when decrypting with
padded cipher.
你有正确的想法,但你走得太远了。此错误仅与SecretKeySpec
有关,Cipher
不关心模式,只关心算法。 Cipher cipher = Cipher.getInstance("Rijndael/ECB/NoPadding");
byte[] key = getKey(input[1]);
SecretKey secretKey = new SecretKeySpec(key, 0, key.length, "Rijndael");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
是您指定模式的位置。此外,Rijndael和AES并不完全相同。
首先,将前几行更改为:
key
请注意,由于您使用的是整个SecretKey secretKey = new SecretKeySpec(key, "Rijndael");
,因此您不需要偏移和长度参数,因此您可以这样做
while ((bytes = input.Read(buffer, 0, BufferSize)) > 0)
{
for (int i = 0; i < bytes; i += rijndael.BlockSize)
{
decryptor.TransformBlock(buffer, i, rijndael.BlockSize, buffer, i);
}
output.Write(buffer, 0, bytes);
}
原始C#代码有一些不那么明显的行为:
input
当循环到达buffer
的末尾时,它会复制到Read
。除非最后一个input
恰好是1024个字节,否则在输入结束后,前一个循环(或初始化,如果它通过一个Read
操作得到整个output.Write
)将有残留。
内循环一次解密一个16字节的块。对于420字节的示例,最后一个块将包含剩余的4个字节的输入和12个字节的垃圾。但它没关系,因为bytes
只写"$type": "System.Web.Http.HttpError, System.Web.Http",
"Message": "An error has occurred.",
"ExceptionMessage": "Exception of type 'System.OutOfMemoryException' was thrown.",
"ExceptionType": "System.OutOfMemoryException",
"StackTrace": " at Microsoft.WindowsAzure.StorageClient.Tasks.Task`1.get_Result()
at Microsoft.WindowsAzure.StorageClient.Tasks.Task`1.ExecuteAndWait()
at Microsoft.WindowsAzure.StorageClient.TaskImplHelper.ExecuteImplWithRetry(Func`1 impl, RetryPolicy policy)
at Microsoft.WindowsAzure.StorageClient.CloudBlobContainer.SetPermissions(BlobContainerPermissions permissions, BlobRequestOptions options)
个字节来截断垃圾。您必须在Java代码中复制此行为。
旁注:你绝对必须使用ECB吗?它不是很安全......