使用 AES 的 C# 到 Java 加密/解密的奇怪行为
本文关键字:解密 加密 AES Java 使用 | 更新日期: 2023-09-27 18:35:08
我在使用 AES 算法进行字符串解密时遇到了非常奇怪的问题。我的 C# 应用程序将加密数据(字符串)发送到 Java 应用程序。即使我使用相同的密钥字符串,解密也会导致异常:
javax.crypto.BadPaddingException:给定最终块未正确填充 at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:810)
但是,仅当要加密的纯文本的长度(在 C# 端)超过 1393 个字符时...但是,如果长度等于 1393 个字符,或小于 1393 个字符,则工作正常。
下面是用于加密的 C# 代码:
private static string Encrypt(string textToEncrypt, string key)
{
try
{
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.CBC;
rijndaelCipher.Padding = PaddingMode.PKCS7;
rijndaelCipher.KeySize = 0x80; // 256bit key
rijndaelCipher.BlockSize = 0x80;
byte[] pwdBytes = Encoding.UTF8.GetBytes(key);
byte[] keyBytes = new byte[0x10];
int len = pwdBytes.Length;
if (len > keyBytes.Length)
{
len = keyBytes.Length;
}
Array.Copy(pwdBytes, keyBytes, len);
rijndaelCipher.Key = keyBytes;
rijndaelCipher.IV = keyBytes;
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
byte[] plainText = Encoding.UTF8.GetBytes(textToEncrypt);
return Convert.ToBase64String(transform.TransformFinalBlock(plainText, 0, plainText.Length));
}
catch (Exception ex)
{
throw ex;
}
}
还有一个用于解密的 Java 代码:
public static String Decrypt(String text, String key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes = new byte[16];
byte[] b = key.getBytes("UTF-8");
int len = b.length;
if (len > keyBytes.length) {
len = keyBytes.length;
}
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
BASE64Decoder decoder = new BASE64Decoder();
byte[] results = cipher.doFinal(decoder.decodeBuffer(text));
return new String(results, "UTF-8");
}
我尝试将 apache 中的 BASE64Decoder 替换为 Base64 编解码器,但结果是一样的......我将不胜感激任何建议或想法。谢谢。
如果通过网络传输密文,则应使用消息身份验证代码确保其完整性。在尝试解密密文之前,应验证 MAC。这将防止网络上的消息意外损坏和恶意篡改。在这种情况下,它应该可以帮助您确保传输的密文与您接收的密文完全相同。
我重写了java解密端以进行MAC验证。它假设 MAC 是使用 HMACSHA1 算法生成的,该算法使用用于 AES 密码的相同密钥材料初始化(注意:这只是一个示例,加密密钥和 HMAC 密钥在实际系统中应该不同)并附加到密文前面。消息的前 20 个字节应该是 MAC,紧跟在后面的密文。
public static String Decrypt(String message, String key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes = new byte[16];
byte[] b = key.getBytes("UTF-8");
int len = b.length;
if (len > keyBytes.length) {
len = keyBytes.length;
}
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
byte[] messageBytes = DatatypeConverter.parseBase64Binary(message);
byte[] macBytes = new byte[20];
byte[] ciphertext = new byte[messageBytes.length - 20];
System.arraycopy(messageBytes, 0, macBytes, 0, macBytes.length);
System.arraycopy(messageBytes, 20, ciphertext, 0, ciphertext.length);
Mac mac = Mac.getInstance("HMACSHA1");
mac.init(keySpec);
verifyMac(mac.doFinal(ciphertext), macBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] results = cipher.doFinal(ciphertext);
return new String(results, "UTF-8");
}
private static void verifyMac(byte[] mac1, byte[] mac2) throws Exception {
MessageDigest sha = MessageDigest.getInstance("SHA1");
byte[] mac1_hash = sha.digest(mac1);
sha.reset();
byte[] mac2_hash = sha.digest(mac2);
if(!Arrays.equals(mac1_hash, mac2_hash)){
throw new RuntimeException("Invalid MAC");
}
}
只是关于安全措施的一些说明。使用键入的密码的 UTF-8 字节不会在密钥中提供足够的熵。如果您打算使用密码作为加密密钥,则应使用密钥拉伸算法对其进行处理。PBKDF2是一个受欢迎的选择。AES 密码的 IV 应随机生成(使用加密安全的随机源),并与密文一起以明文形式传输。用于初始化 AES 密码的密钥应与用于 MAC 的密钥不同。您可以使用 PBKDF2 从您提供的密码中创建两倍长的密钥,并将前半部分用于 AES,后半部分用于 HMAC-SHA1。最后,在验证 MAC 时,您应该直接验证 MAC 的哈希而不是 MAC(如上面的 verifyMac
方法所示)。验证 MA 会直接暴露可能的计时攻击媒介。