解密 Java 中的 C# AES 加密文本

Decrypt C# AES encrypted text in Java

我在 Java 第三方应用程序中实施,但应用程序的某些部分我得到了这样的加密字符串:eGlhV2xNbmdqSFBkbEhkZDNpZ3gwQT09 并且必须解密。

编写此应用程序代码的人已不在,因此我需要一些帮助来制作 Java 中的解密代码。 这是密码:CB=Z8#P@0!N2/8$%3K-9C(5S9*FDH+0Z

public static class SystemCriptografia
{
    #region Atributos        
    private static string chave = "CB=Z8#P@0!N2/8$%3K-9C(5S9*FDH+0Z";
    private static SymmetricAlgorithm algoritmo = new RijndaelManaged();

    #endregion

    #region Métodos

    #region Métodos privados

    private static string base64Encode(string data)
    {
        byte[] encData_byte = new byte[data.Length];
        encData_byte = System.Text.Encoding.UTF8.GetBytes(data);
        string encodedData = Convert.ToBase64String(encData_byte);
        return encodedData;
    }

    private static string base64Decode(string data)
    {
        UTF8Encoding encoder = new UTF8Encoding();
        Decoder utf8Decode = encoder.GetDecoder();

        byte[] todecode_byte = Convert.FromBase64String(data);
        int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
        char[] decoded_char = new char[charCount];
        utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
        string result = new String(decoded_char);
        return result;
    }

    private static string Criptografa(string valor, string chave)
    {
        byte[] ByteValor = Encoding.UTF8.GetBytes(valor);

        // Seta a chave privada
        algoritmo.Mode = CipherMode.CBC;
        algoritmo.Key = Encoding.Default.GetBytes(chave);
        algoritmo.IV = Encoding.Default.GetBytes("brasilshopsoft07");

        // Interface de criptografia / Cria objeto de criptografia
        ICryptoTransform cryptoTransform = algoritmo.CreateEncryptor();

        MemoryStream _memoryStream = new MemoryStream();
        CryptoStream _cryptoStream = new CryptoStream(_memoryStream, cryptoTransform, CryptoStreamMode.Write);

        // Grava os dados criptografados no MemoryStream
        _cryptoStream.Write(ByteValor, 0, ByteValor.Length);
        _cryptoStream.FlushFinalBlock();

        // Busca o tamanho dos bytes encriptados
        byte[] cryptoByte = _memoryStream.ToArray();

        // Converte para a base 64 string para uso posterior em um xml
        return Convert.ToBase64String(cryptoByte, 0, cryptoByte.GetLength(0));
    }

    private static string Descriptografa(string valor, string chave)
    {
        // Converte a base 64 string em num array de bytes
        byte[] cryptoByte = Convert.FromBase64String(valor);

        // Seta a chave privada
        algoritmo.Mode = CipherMode.CBC;
        algoritmo.Key = Encoding.Default.GetBytes(chave);
        algoritmo.IV = Encoding.Default.GetBytes("brasilshopsoft07");

        // Interface de criptografia / Cria objeto de descriptografia
        ICryptoTransform cryptoTransform = algoritmo.CreateDecryptor();

        MemoryStream _memoryStream = new MemoryStream(cryptoByte, 0, cryptoByte.Length);
        CryptoStream _cryptoStream = new CryptoStream(_memoryStream, cryptoTransform, CryptoStreamMode.Read);

        // Busca resultado do CryptoStream
        StreamReader _streamReader = new StreamReader(_cryptoStream);
        return _streamReader.ReadToEnd();
    }

    #endregion

    public static string ToCriptografa(this string valor)
    {
        return Criptografa(valor, chave);
    }

    public static string ToDescriptografa(this string valor)
    {
        return Descriptografa(valor, chave);
    }

    public static string ToCriptografaQueryString(this string valor)
    {
        return base64Encode(Criptografa(valor, chave));
    }

    public static string ToDescriptografaQueryString(this string valor)
    {
        return Descriptografa(base64Decode(valor), chave);
    }

    #endregion
}

这是我正在尝试执行的 java 代码:

public class Criptografia {

    private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5PADDING";
    private static final int KEY_SIZE = 256;

    public static void main(final String[] args) throws Exception {
        System.out.println(decryptAuthorizationString(
                "eGlhV2xNbmdqSFBkbEhkZDNpZ3gwQT09", "CB=Z8#P@0!N2/8$%3K-9C(5S9*FDH+0Z"));
    }

    private static String decryptAuthorizationString(final String authString,
            final String password) {
        try {
            // --- check if AES-256 is available
            if (Cipher.getMaxAllowedKeyLength(AES_CBC_PKCS5PADDING) < KEY_SIZE) {
                throw new IllegalStateException("Unlimited crypto files not present in this JRE");
            }

            // --- create cipher
            final Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);

            // --- create the key and initial vector bytes
            final byte[] passwordEncoded = password.getBytes(UTF_16LE);
            final byte[] keyData = Arrays.copyOf(passwordEncoded, KEY_SIZE
                    / Byte.SIZE);
            final byte[] ivBytes = Arrays.copyOf(keyData, cipher.getBlockSize());

            // --- init cipher
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyData, "AES"),
                    new IvParameterSpec(ivBytes));

            // --- decode & decrypt authentication string
            final byte[] authBytes = Base64.decode(authString);
            final byte[] decryptedData = cipher.doFinal(authBytes);

            // WARNING: may still decrypt to wrong string if
            // authString or password are incorrect - 
            // BadPaddingException may *not* be thrown
            return new String(decryptedData, UTF_16LE);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            // failure to authenticate
            return null;
        } catch (final GeneralSecurityException e) {
            throw new IllegalStateException(
                    "Algorithms or unlimited crypto files not available", e);
        }
    }
}

您的代码问题:

  • 选择了错误的字符集。如果 Encoding.Default 在 C# 中是 UTF-8,那么 "password" 和 IV 编码在 Java.

  • 中也必须是 "UTF-8"
  • IV不是从key推导出来的,也是固定值

  • 密文其实是经过双重Base64编码的。我想有人把 "two is better than one" 看得太直白了。

完整代码:

private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5PADDING";
private static final int KEY_SIZE = 256;
<b>private static String UTF_8 = "UTF-8";</b>

public static void main(final String[] args) throws Exception {
    System.out.println(decryptAuthorizationString(
            "eGlhV2xNbmdqSFBkbEhkZDNpZ3gwQT09", "CB=Z8#P@0!N2/8$%3K-9C(5S9*FDH+0Z"));
}

private static String decryptAuthorizationString(final String authString,
                                                 final String password) throws UnsupportedEncodingException {
    try {
        // --- check if AES-256 is available
        if (Cipher.getMaxAllowedKeyLength(AES_CBC_PKCS5PADDING) < KEY_SIZE) {
            throw new IllegalStateException("Unlimited crypto files not present in this JRE");
        }

        // --- create cipher
        final Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);

        // --- create the key and initial vector bytes
        <b>final byte[] passwordEncoded = password.getBytes(UTF_8);
        final byte[] ivBytes = "brasilshopsoft07".getBytes(UTF_8);</b>

        // --- init cipher
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(<b>passwordEncoded</b>, "AES"),
                new IvParameterSpec(ivBytes));

        // --- decode & decrypt authentication string
        final byte[] authBytes = Base64.decode(authString);
        <b>final byte[] authBytes2 = Base64.decode(authBytes);</b>
        final byte[] decryptedData = cipher.doFinal(<b>authBytes2</b>);

        // WARNING: may still decrypt to wrong string if
        // authString or password are incorrect -
        // BadPaddingException may *not* be thrown
        return new String(decryptedData, UTF_8);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
        // failure to authenticate
        return null;
    } catch (final GeneralSecurityException e) {
        throw new IllegalStateException(
                "Algorithms or unlimited crypto files not available", e);
    }
}

输出:

1

备注:

  • 使用固定 IV 是不安全的。为了达到语义安全,必须随机选择 IV。它不必是秘密的,因此它可以与密文一起发送。一种常见的方法是将其添加到密文中并在解密之前将其切掉。

  • 丢失第二个Base64编码。它只是带走了 space,但没有提供任何有用的功能。

  • 始终使用特定的编码。 Encoding.Default 非常适合测试,但它可能会根据 运行 所在的机器而改变,并且您将失去多个 clients/servers.

  • 之间的兼容性
  • 验证密文!如果你不添加消息认证码或者使用像GCM或EAX这样的认证模式,那么攻击者可能会操纵密文而你将无法确定这一点。甚至可以完全恢复特定加密消息的明文(padding oracle 攻击)