Шифрование AES с помощью C# для соответствия шифрованию Java

мне дали реализацию Java для шифрования, но, к сожалению, мы магазин .net, и у меня нет возможности включить Java в наше решение. К сожалению, я также не Java-парень, поэтому я боролся с этим в течение нескольких дней и думал, что, наконец, обращусь за помощью.

Я искал повсюду способ, чтобы соответствовать тому, как работает шифрование Java, и я пришел к разрешению, которое мне нужно использовать RijndaelManaged в C#. Я действительно очень близок. Этот строки, которые я возвращаю в c#, соответствуют первой половине, но вторая половина отличается.

вот фрагмент реализации java:

private static String EncryptBy16( String str, String theKey) throws Exception
{

    if ( str == null || str.length() > 16)
    {
        throw new NullPointerException();
    }
    int len = str.length();
    byte[] pidBytes = str.getBytes();
    byte[] pidPaddedBytes = new byte[16];

    for ( int x=0; x<16; x++ )
    {
        if ( x<len )
        {
            pidPaddedBytes[x] = pidBytes[x];
        }
        else
        {
            pidPaddedBytes[x] = (byte) 0x0;
        }

    }

    byte[] raw = asBinary( theKey );
    SecretKeySpec myKeySpec = new SecretKeySpec( raw, "AES" );
    Cipher myCipher = Cipher.getInstance( "AES/ECB/NoPadding" );
    cipher.init( Cipher.ENCRYPT_MODE, myKeySpec );
    byte[] encrypted = myCipher.doFinal( pidPaddedBytes );
    return( ByteToString( encrypted ) );
}

public static String Encrypt(String stringToEncrypt, String key) throws Exception
{

    if ( stringToEncrypt == null ){
        throw new NullPointerException();
    }
    String str = stringToEncrypt;

    StringBuffer result = new StringBuffer();
    do{
        String s = str;
        if(s.length() > 16){
            str = s.substring(16);
            s = s.substring(0,16);
        }else {
            str = null;
        }
        result.append(EncryptBy16(s,key));
    }while(str != null);

    return result.toString();
}

Я не совсем уверен, почему они проходят только 16 символов за раз, но w/e. Я попробовал то же самое с моей реализацией c#, используя строковый конструктор и отправляя только 16 символов за раз, и получил тот же результат, что и при передаче всей строки сразу.

вот фрагмент моего c# реализация, которая в основном является копией и вставкой с сайта MS для RijndaelManaged:

public static string Encrypt(string stringToEncrypt, string key)
        {
            using (RijndaelManaged myRijndael = new RijndaelManaged())
            {
                myRijndael.Key = StringToByte(key);
                myRijndael.IV = new byte[16];
                return EncryptStringToBytes(stringToEncrypt, myRijndael.Key, myRijndael.IV);
            }
        }

static string EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
        {
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");
            byte[] encrypted;
            using (RijndaelManaged rijAlg = new RijndaelManaged())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;
                ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }
            return ByteToString(encrypted);
        }

как я уже сказал выше, первая половина зашифрованного строке (см. пример ниже), но вторая половина-мимо. Я добавил пробелы в выводах ниже, чтобы лучше проиллюстрировать, где разница. Я недостаточно знаю о шифровании и Java, чтобы знать, куда обратиться дальше. Любое руководство будет очень признателен

вывод Java:

49a85367ec8bc387bb44963b54528c97 8026d7eaeff9e4cb7cf74f8227f80752

В C# выход:

49a85367ec8bc387bb44963b54528c97 718f574341593be65034627a6505f13c

обновление по предложению Криса ниже:

static string EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
    if (plainText == null || plainText.Length <= 0)
        throw new ArgumentNullException("plainText");
    if (Key == null || Key.Length <= 0)
        throw new ArgumentNullException("Key");
    if (IV == null || IV.Length <= 0)
        throw new ArgumentNullException("Key");
    byte[] encrypted;
    using (RijndaelManaged rijAlg = new RijndaelManaged())
    {
        rijAlg.Key = Key;
        rijAlg.IV = IV;
        rijAlg.Padding = PaddingMode.None;
        rijAlg.Mode = CipherMode.ECB;
        ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
        using (MemoryStream msEncrypt = new MemoryStream())
        {
            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                {

                    swEncrypt.Write(plainText);
                    if (plainText.Length < 16)
                    {
                        for (int i = plainText.Length; i < 16; i++)
                        {
                            swEncrypt.Write((byte)0x0);
                        }
                    }
                }
                encrypted = msEncrypt.ToArray();
            }
        }
    }
    return ByteToString(encrypted);
}

2 ответов


ваш перевод C# выглядит так, как будто он делает правильную вещь по большей части, потому что первый блок совпадает. То, что не соответствует,-это последний блок, и это потому, что код Java-это нулевое заполнение последнего блока, чтобы заполнить его, тогда как ваш код C# этого не делает, поэтому он будет использовать PKCS #5 по умолчанию.

PKCS #5 заполнение намного лучше, чем нулевое заполнение, конечно, но так как последнее-это то, что использовал код Java, вам придется сделать то же самое. (Это означает, позвоните swEncrypt.Write((byte) 0) еще несколько раз, пока количество байтов не будет кратным 16.)

есть еще одна тонкость. Код Java переводит строку в байты с помощью String.getBytes(), который использует "кодировку по умолчанию" среды выполнения Java. Это означает, что если строка содержит символы, отличные от ASCII, возникнут проблемы совместимости. Лучшая практика-использовать UTF-8, но, поскольку вы не можете изменить код Java, я думаю, что с этим мало что можно сделать.


большой вопрос, это распространенная ошибка при работе с тем же алгоритмом шифрования, но на разных языках. Реализация деталей алгоритма требует внимания. Я не тестировал код, но в вашем случае варианты заполнения двух реализаций разные, попробуйте использовать те же варианты заполнения для реализаций c# и java. Вы можете прочитать комментарии и больше о реализации от здесь. Пожалуйста, обратите внимание на заполнение по умолчанию.

  • Padding = PaddingMode.Pkcs7 в,
  • private final String cipherTransformation = "AES/CBC / PKCS5Padding";

реализация c#:

public RijndaelManaged GetRijndaelManaged(String secretKey)
    {
        var keyBytes = new byte[16];
        var secretKeyBytes = Encoding.UTF8.GetBytes(secretKey);
        Array.Copy(secretKeyBytes, keyBytes, Math.Min(keyBytes.Length, secretKeyBytes.Length));
        return new RijndaelManaged
        {
            Mode = CipherMode.CBC,
            Padding = PaddingMode.PKCS7,
            KeySize = 128,
            BlockSize = 128,
            Key = keyBytes,
            IV = keyBytes
        };
    }

    public byte[] Encrypt(byte[] plainBytes, RijndaelManaged rijndaelManaged)
    {
        return rijndaelManaged.CreateEncryptor()
            .TransformFinalBlock(plainBytes, 0, plainBytes.Length);
    }

    public byte[] Decrypt(byte[] encryptedData, RijndaelManaged rijndaelManaged)
    {
        return rijndaelManaged.CreateDecryptor()
            .TransformFinalBlock(encryptedData, 0, encryptedData.Length);
    }


    // Encrypts plaintext using AES 128bit key and a Chain Block Cipher and returns a base64 encoded string

    public String Encrypt(String plainText, String key)
    {
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        return Convert.ToBase64String(Encrypt(plainBytes, GetRijndaelManaged(key)));
    }


    public String Decrypt(String encryptedText, String key)
    {
        var encryptedBytes = Convert.FromBase64String(encryptedText);
        return Encoding.UTF8.GetString(Decrypt(encryptedBytes, GetRijndaelManaged(key)));
    }

Java-реализации:

private final String characterEncoding = "UTF-8";
private final String cipherTransformation = "AES/CBC/PKCS5Padding";
private final String aesEncryptionAlgorithm = "AES";

public  byte[] decrypt(byte[] cipherText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
    Cipher cipher = Cipher.getInstance(cipherTransformation);
    SecretKeySpec secretKeySpecy = new SecretKeySpec(key, aesEncryptionAlgorithm);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpecy, ivParameterSpec);
    cipherText = cipher.doFinal(cipherText);
    return cipherText;
}

public byte[] encrypt(byte[] plainText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
    Cipher cipher = Cipher.getInstance(cipherTransformation);
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, aesEncryptionAlgorithm);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
    plainText = cipher.doFinal(plainText);
    return plainText;
}

private byte[] getKeyBytes(String key) throws UnsupportedEncodingException{
    byte[] keyBytes= new byte[16];
    byte[] parameterKeyBytes= key.getBytes(characterEncoding);
    System.arraycopy(parameterKeyBytes, 0, keyBytes, 0, Math.min(parameterKeyBytes.length, keyBytes.length));
    return keyBytes;
}


public String encrypt(String plainText, String key) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
    byte[] plainTextbytes = plainText.getBytes(characterEncoding);
    byte[] keyBytes = getKeyBytes(key);
    return Base64.encodeToString(encrypt(plainTextbytes,keyBytes, keyBytes), Base64.DEFAULT);
}


public String decrypt(String encryptedText, String key) throws KeyException, GeneralSecurityException, GeneralSecurityException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException{
    byte[] cipheredBytes = Base64.decode(encryptedText, Base64.DEFAULT);
    byte[] keyBytes = getKeyBytes(key);
    return new String(decrypt(cipheredBytes, keyBytes, keyBytes), characterEncoding);
}