How to implement AES Encrypt (AesManaged Rfc2898De

2019-09-06 16:44发布

问题:

I met a blocking issue when I tried to immigrate my project from Windows Phone Silverlight 8.1 to Windows Runtime. In Windows Runtime the AES-encrypted string is not the same as the one on Silverlight before.

In Silverlight:

    public static string EncryptAES(string encryptString)
    {
        AesManaged aes = null;
        MemoryStream ms = null;
        CryptoStream cs = null;

        string encryptKey = "testtest123";
        string salt = "abcabcabcd";
        try
        {
            Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(encryptKey, Encoding.UTF8.GetBytes(salt));

            aes = new AesManaged();
            aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
            aes.IV = rfc2898.GetBytes(aes.BlockSize / 8);

            ms = new MemoryStream();
            cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);

            byte[] data = Encoding.UTF8.GetBytes(encryptString);
            cs.Write(data, 0, data.Length);
            cs.FlushFinalBlock();

            return Convert.ToBase64String(ms.ToArray());
        }
        catch
        {
            return encryptString;
        }
        finally
        {
            if (cs != null)
                cs.Close();

            if (ms != null)
                ms.Close();

            if (aes != null)
                aes.Clear();
        }
    }

In Windows Runtime:

    public static string EncryptAES(string plainText)
    {
        string pw = "testtest123";
        string salt = "abcabcabcd";
        IBuffer plainBuffer = CryptographicBuffer.ConvertStringToBinary(plainText, BinaryStringEncoding.Utf8);

        IBuffer saltBuffer = CryptographicBuffer.ConvertStringToBinary(salt, BinaryStringEncoding.Utf8);
        IBuffer pwBuffer = CryptographicBuffer.ConvertStringToBinary(pw, BinaryStringEncoding.Utf8);

        KeyDerivationAlgorithmProvider keyDerivationProvider = Windows.Security.Cryptography.Core.KeyDerivationAlgorithmProvider.OpenAlgorithm(KeyDerivationAlgorithmNames.Pbkdf2Sha256);
        // using salt and 1000 iterations
        KeyDerivationParameters pbkdf2Parms = KeyDerivationParameters.BuildForPbkdf2(saltBuffer, 1000);

        // create a key based on original key and derivation parmaters
        CryptographicKey keyOriginal = keyDerivationProvider.CreateKey(pwBuffer);
        IBuffer keyMaterial = CryptographicEngine.DeriveKeyMaterial(keyOriginal, pbkdf2Parms, 32);
        CryptographicKey derivedPwKey = keyDerivationProvider.CreateKey(pwBuffer);

        // derive buffer to be used for encryption salt from derived password key 
        IBuffer saltMaterial = CryptographicEngine.DeriveKeyMaterial(derivedPwKey, pbkdf2Parms, 16);

        // display the buffers - because KeyDerivationProvider always gets cleared after each use, they are very similar unforunately
        string keyMaterialString = CryptographicBuffer.EncodeToBase64String(keyMaterial);
        string saltMaterialString = CryptographicBuffer.EncodeToBase64String(saltMaterial);
        //AES_CBC_PKCS7
        SymmetricKeyAlgorithmProvider symProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
        // create symmetric key from derived password key
        CryptographicKey symmKey = symProvider.CreateSymmetricKey(keyMaterial);

        // encrypt data buffer using symmetric key and derived salt material
        IBuffer resultBuffer = CryptographicEngine.Encrypt(symmKey, plainBuffer, saltMaterial);
        string result = CryptographicBuffer.EncodeToBase64String(resultBuffer);
        return result;
    }

In Silverlight Project, string "123456" encrypted by AES is "4UfdhC/0MFQlMhl7N7gqLg=="; While in Windows Runtime Project, the AES-encrypted string is "jxsR5EuhPXgRsHLs4N3EGQ=="

So how can I get the same string on WinRT as the one on Silverlight ?

回答1:

The AES classes default to a random IV on the Microsoft runtimes. To get the same ciphertext you'll need to use a static IV. That's however not secure. Instead you should just check if you get the same key bytes and let the ciphertext differ for each run. Otherwise you can clearly distinguish identical plaintext.

You also seem to be using the wrong hash algorithm, Rfc2898DeriveBytes uses SHA-1 instead of SHA-256 as underlying hash function.



回答2:

I've created class which works for me as replacement for System.Security.Cryptography.Rfc2898DeriveBytes:

public class Rfc2898DeriveBytes
{
    private readonly string _password;
    private readonly byte[] _salt;

    public Rfc2898DeriveBytes(string password, byte[] salt)
    {
        _password = password;
        _salt = salt;

        IterationCount = 1000;
    }

    public uint IterationCount { get; set; }

    public byte[] GetBytes(uint cb)
    {
        var provider = KeyDerivationAlgorithmProvider.OpenAlgorithm(KeyDerivationAlgorithmNames.Pbkdf2Sha1);

        var buffSecret = CryptographicBuffer.ConvertStringToBinary(_password, BinaryStringEncoding.Utf8);
        var buffsalt = CryptographicBuffer.CreateFromByteArray(_salt);

        var keyOriginal = provider.CreateKey(buffSecret);

        var par = KeyDerivationParameters.BuildForPbkdf2(buffsalt, IterationCount);
        var keyMaterial = CryptographicEngine.DeriveKeyMaterial(keyOriginal, par, cb);

        byte[] result;
        CryptographicBuffer.CopyToByteArray(keyMaterial, out result);

        return result;
    }
}