locked
How can I generate the exact same ciphertext in WinRT as this Windows Phone encryption code using AesManaged and Rfc2898DeriveBytes? RRS feed

  • Question

  • How would I get the same result using WinRt as this code for Windows Phone?

            public static string Encrypt(string dataToEncrypt, string password, string salt)
           
    {
               
    AesManaged aes = null;
               
    MemoryStream memoryStream = null;
               
    CryptoStream cryptoStream = null;

               
    try
               
    {
                   
    //Generate a Key based on a Password, Salt and HMACSHA1 pseudo-random number generator
                   
    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));

                   
    //Create AES algorithm with 256 bit key and 128-bit block size
                    aes
    = new AesManaged();
                    aes
    .Key = rfc2898.GetBytes(aes.KeySize / 8);
                    aes
    .IV = rfc2898.GetBytes(aes.BlockSize / 8);

                   
    //Create Memory and Crypto Streams
                    memoryStream
    = new MemoryStream();
                    cryptoStream
    = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write);

                   
    //Encrypt Data
                   
    byte[] data = Encoding.Unicode.GetBytes(dataToEncrypt);
                    cryptoStream
    .Write(data, 0, data.Length);
                    cryptoStream
    .FlushFinalBlock();

                   
    //Return Base 64 String
                   
    return Convert.ToBase64String(memoryStream.ToArray());
               
    }
               
    catch (Exception eEncrypt)
               
    {
                   
    App.LogException("Data encryption error: " + eEncrypt);
                   
    return "";
               
    }
               
    finally
               
    {
                   
    if (cryptoStream != null)
                        cryptoStream
    .Close();

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

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

               
    }
           
    }

    Thanks!

    Ginny

    Monday, April 9, 2012 5:38 PM

Answers

  • This appears to be most of the solution. The .NET encryption code must be changed from this

                    aes = new AesManaged();
                    aes.Key = rfc2898.GetBytes(32);
                    aes.IV = rfc2898.GetBytes(16);
    

    to this

                    aes = new AesManaged();
                    aes.Key = rfc2898.GetBytes(32);
                    rfc2898.Reset(); // needed to match WinRT version
                    aes.IV = rfc2898.GetBytes(16);
    

    Then the following WinRT code can be used for matching encryption

            public static string Encrypt(string plainText, string pw, string salt)
            {
                IBuffer pwBuffer = CryptographicBuffer.ConvertStringToBinary(pw, BinaryStringEncoding.Utf8);
                IBuffer saltBuffer = CryptographicBuffer.ConvertStringToBinary(salt, BinaryStringEncoding.Utf16LE);
                IBuffer plainBuffer = CryptographicBuffer.ConvertStringToBinary(plainText, BinaryStringEncoding.Utf16LE);
    
                // Derive key material for password size 32 bytes for AES256 algorithm
                KeyDerivationAlgorithmProvider keyDerivationProvider = Windows.Security.Cryptography.Core.KeyDerivationAlgorithmProvider.OpenAlgorithm("PBKDF2_SHA1");
                // 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);
    
                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;
            }
    

    This is close, but because the KeyDerivationAlgorithmProvider apparently gets reset internally after each use, the derived material is very similar. And my goal was to NOT have to change the .NET encryption/decryption code at all as it is in production. I'd still welcome any hints as to how to improve this.

    Ginny Caughey

    Tuesday, April 17, 2012 2:22 PM

All replies

  • This appears to be most of the solution. The .NET encryption code must be changed from this

                    aes = new AesManaged();
                    aes.Key = rfc2898.GetBytes(32);
                    aes.IV = rfc2898.GetBytes(16);
    

    to this

                    aes = new AesManaged();
                    aes.Key = rfc2898.GetBytes(32);
                    rfc2898.Reset(); // needed to match WinRT version
                    aes.IV = rfc2898.GetBytes(16);
    

    Then the following WinRT code can be used for matching encryption

            public static string Encrypt(string plainText, string pw, string salt)
            {
                IBuffer pwBuffer = CryptographicBuffer.ConvertStringToBinary(pw, BinaryStringEncoding.Utf8);
                IBuffer saltBuffer = CryptographicBuffer.ConvertStringToBinary(salt, BinaryStringEncoding.Utf16LE);
                IBuffer plainBuffer = CryptographicBuffer.ConvertStringToBinary(plainText, BinaryStringEncoding.Utf16LE);
    
                // Derive key material for password size 32 bytes for AES256 algorithm
                KeyDerivationAlgorithmProvider keyDerivationProvider = Windows.Security.Cryptography.Core.KeyDerivationAlgorithmProvider.OpenAlgorithm("PBKDF2_SHA1");
                // 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);
    
                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;
            }
    

    This is close, but because the KeyDerivationAlgorithmProvider apparently gets reset internally after each use, the derived material is very similar. And my goal was to NOT have to change the .NET encryption/decryption code at all as it is in production. I'd still welcome any hints as to how to improve this.

    Ginny Caughey

    Tuesday, April 17, 2012 2:22 PM
  • Here's the matching decryption code

            public static string Decrypt(string cipherText, string pw, string salt)
            {
                IBuffer pwBuffer = CryptographicBuffer.ConvertStringToBinary(pw, BinaryStringEncoding.Utf8);
                IBuffer saltBuffer = CryptographicBuffer.ConvertStringToBinary(salt, BinaryStringEncoding.Utf16LE);
                IBuffer cipherBuffer = CryptographicBuffer.DecodeFromBase64String(cipherText);
    
                // Derive key material for password size 32 bytes for AES256 algorithm
                KeyDerivationAlgorithmProvider keyDerivationProvider = Windows.Security.Cryptography.Core.KeyDerivationAlgorithmProvider.OpenAlgorithm("PBKDF2_SHA1");
                // 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 keys - because KeyDerivationProvider always gets cleared after each use, they are very similar unforunately
                string keyMaterialString = CryptographicBuffer.EncodeToBase64String(keyMaterial);
                string saltMaterialString = CryptographicBuffer.EncodeToBase64String(saltMaterial);
    
                SymmetricKeyAlgorithmProvider symProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
                // create symmetric key from derived password material
                CryptographicKey symmKey = symProvider.CreateSymmetricKey(keyMaterial);
    
                // encrypt data buffer using symmetric key and derived salt material
                IBuffer resultBuffer = CryptographicEngine.Decrypt(symmKey, cipherBuffer, saltMaterial);
                string result = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf16LE, resultBuffer);
                return result;
            }
    


    Ginny Caughey

    Tuesday, April 17, 2012 6:54 PM
  • I have almost absolutely same code, but your solution doesn't work.. :(

    I'm getting exception "Additional information: Data error (cyclic redundancy check). (Exception from HRESULT: 0x80070017)"

    on line "IBuffer resultBuffer = CryptographicEngine.Decrypt(symmKey, cipherBuffer, saltMaterial);"

    Here is my topic on Forum

    Sunday, May 6, 2012 5:37 PM
  • Did you use my Encrypt code to encrypt the string and my Decrypt code to decrypt it? This works for me:

    stringfoo = Encrypt("bar", "mypassword", "mysalt");

    stringbar = Decrypt(foo, "mypassword", "mysalt");

    Of course the password and salt must be identical for encryption and ecryption.


    Ginny Caughey

    Monday, May 7, 2012 12:03 PM
  • I have already encrypted text which was encrypt with help of almost same code like yours. The only difference is that you are using Unicode in line "Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));" and I use UTF8.

    So, I want to decrypt it in WinRT. When I use exactly your code - I see Exception. But when I change in line "IBuffer saltBuffer = CryptographicBuffer.ConvertStringToBinary(salt, BinaryStringEncoding.Utf16LE);" encoding to UTF8 - I see chinese text.

    Here is all my code for decryption:

    public static string Decrypt(string encryptedString, string password)
            {
                    byte[] encryptedBytes;
                    encryptedBytes = Convert.FromBase64String(encryptedString);
                    byte[] saltBytes = System.Text.Encoding.UTF8.GetBytes(password);
                    using (AesManaged aes = new AesManaged())
                    {
                        Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(password, saltBytes);
                        aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
                        aes.KeySize = aes.LegalKeySizes[0].MaxSize;
                        aes.Key = rfc.GetBytes(Convert.ToInt32(aes.KeySize / 8));
                        aes.IV = rfc.GetBytes(Convert.ToInt32(aes.BlockSize / 8));
                        using (ICryptoTransform decryptTransform = aes.CreateDecryptor())
                        {
                            using (MemoryStream decryptedStream = new MemoryStream())
                            {
                                using (CryptoStream decryptor = new CryptoStream(decryptedStream, decryptTransform, CryptoStreamMode.Write))
                                {
                                    decryptor.Write(encryptedBytes, 0, encryptedBytes.Length);
                                    decryptor.Flush();
                                    decryptor.Close();
                                    byte[] decryptedBytes = decryptedStream.ToArray();
                                    System.Text.UTF8Encoding encoding2 = new System.Text.UTF8Encoding();
                                    decryptedString = encoding2.GetString(decryptedBytes, 0, decryptedBytes.Length);
                                }
                            }
                        }
                    }
                    return decryptedString;
            }


             

    Monday, May 7, 2012 3:40 PM
  • As long as you change all occurrences of Unicode to UTF8 in both encryption and decryption methods, I would certainly think that should work. On the other hand if that doesn't, can you change the encryption code to use Unicode?

    Ginny Caughey

    Monday, May 7, 2012 4:48 PM
  • Unfortunately I have already encoded with UTF8 text with algorithm above, so I can't change it for using Unicode..

    So, should decrypt this text. With changing UTF16LE to UTF8 I'm getting hieroglyphs.

    One more difference between my code and yours are lines:

    aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
    aes.KeySize = aes.LegalKeySizes[0].MaxSize;

    Your code uses default 256 and 128..

    Have tried to change numbers in line:

    IBuffer keyMaterial = CryptographicEngine.DeriveKeyMaterial(keyOriginal, pbkdf2Parms, 32);

    But still with no result.
    Monday, May 7, 2012 6:02 PM
  • I don't really understand why you can't change the encryption code if you have access to it. That would make things a lot easier.

    But everything has to be the same for encryption and decryption. You can try stepping the encryption code through the debugger to see what the blocksize and keysize should be for decryption, but it you can't change the encryption code, I am not optimistic that you'll be successful decrypting it.


    Ginny Caughey

    Monday, May 7, 2012 6:18 PM
  • In your Decrypt code, did you already try inserting rfc.Reset() between getting the Key and the IV?

    Ginny Caughey

    Tuesday, May 8, 2012 3:37 PM
  • Ginny, unfortunately I can't change encryption code cause it is already exist in program which is working. I can only manipulate with decryption program which should be written for windows 8. So, I think you have mean encryption part ( my decryption part doesn't use rfc ). I have watching in debug KeySize, BlockSize, iterations count, number of decrypted bytes - everything is same except that bytes are not as should be. And I have also checked your algorithm - seems that it is correctly what I need, but I can't change encryption code with rfc2898.Reset(); So Microsoft doesn't leave any compatibility with old encryption without rfc2898.Reset(); ( may be ask them? ), or I can change this snippet somehow?

    Tuesday, May 8, 2012 6:40 PM
  • Then I am afraid there is no solution for you unless Microsoft changes this API slightly. I have already filed a bug report on this issue, but I have not heard anything back yet. I would also prefer not to change my .NET encryption to accommodate the WinRT decryption limitation.

    Ginny Caughey

    Tuesday, May 8, 2012 6:46 PM
  • What's up, Ginny ? Have you heard about any changes in Release Preview?
    Wednesday, June 13, 2012 9:43 AM
  • The behavior is unchanged in the Release Preview from my brief testing.

    Ginny


    Ginny Caughey

    Wednesday, June 13, 2012 10:16 AM
  • Hi Ginny,

    is it possible for you to provide the decrypt source code of your windows phone solution?

    in this post I found the encrypt and decrypt solution for win RT and the encrypt solution for windows phone.

    To complete the functionality I would love it to get the windows phone based decrypt coding.

    Thanks!

    Stefan

    Sunday, September 23, 2012 4:18 PM
  • Sure. Here is the Windows Phone Encrypt and Decrypt code showing the change needed for compatibility with WinRT versions:

            public static string Encrypt(string dataToEncrypt, string password, string salt)
            {
                return EncryptRT(dataToEncrypt, password, salt, false);
            }
    
            public static string EncryptRT(string dataToEncrypt, string password, string salt, bool WinRTCompatible)
            {
                AesManaged aes = null;
                MemoryStream memoryStream = null;
                CryptoStream cryptoStream = null;
    
                try
                {
                    //Generate a Key based on a Password, Salt and HMACSHA1 pseudo-random number generator 
                    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));
    
                    //Create AES algorithm with 256 bit key and 128-bit block size 
                    aes = new AesManaged();
                    aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
                    if (WinRTCompatible) rfc2898.Reset();
                    aes.IV = rfc2898.GetBytes(aes.BlockSize / 8);
    
                    //Create Memory and Crypto Streams 
                    memoryStream = new MemoryStream();
                    cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
    
                    //Encrypt Data 
                    byte[] data = Encoding.Unicode.GetBytes(dataToEncrypt);
                    cryptoStream.Write(data, 0, data.Length);
                    cryptoStream.FlushFinalBlock();
    
                    //Return Base 64 String 
                    return Convert.ToBase64String(memoryStream.ToArray());
                }
                catch (Exception eEncrypt)
                {
                    App.LogException("Data encryption error: " + eEncrypt);
                    return "";
                }
                finally
                {
                    if (cryptoStream != null)
                        cryptoStream.Close();
    
                    if (memoryStream != null)
                        memoryStream.Close();
    
                    if (aes != null)
                        aes.Clear();
    
                }
            }
    
            public static string Decrypt(string dataToDecrypt, string password, string salt)
            {
                return DecryptRT(dataToDecrypt, password, salt, false);
            }
    
            public static string DecryptRT(string dataToDecrypt, string password, string salt, bool WinRTCompatible)
            {
                AesManaged aes = null;
                MemoryStream memoryStream = null;
                CryptoStream cryptoStream = null;
    
                string returnData = "";
    
                try
                {
                    //Generate a Key based on a Password, Salt and HMACSHA1 pseudo-random number generator 
                    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));
    
                    //Create AES algorithm with 256 bit key and 128-bit block size 
                    aes = new AesManaged();
                    aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
                    if (WinRTCompatible) rfc2898.Reset();
                    aes.IV = rfc2898.GetBytes(aes.BlockSize / 8);
    
                    //Create Memory and Crypto Streams 
                    memoryStream = new MemoryStream();
                    cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write);
    
                    //Decrypt Data 
                    byte[] data = Convert.FromBase64String(dataToDecrypt);
                    cryptoStream.Write(data, 0, data.Length);
                    cryptoStream.FlushFinalBlock();
    
                    //Return Decrypted String 
                    byte[] decryptBytes = memoryStream.ToArray();
                    returnData = Encoding.Unicode.GetString(decryptBytes, 0, decryptBytes.Length);
                }
                catch (Exception eDecrypt)
                {
                    if (!eDecrypt.Message.ToLower().Contains("padding"))
                        App.LogException("Data decryption error: " + eDecrypt.Message);
                    else
                        MyMessageBox.Show("You must use the same master password for both encryption and decryption.");
                }
                finally
                {
                    if (cryptoStream != null)
                        cryptoStream.Close();
    
                    if (memoryStream != null)
                        memoryStream.Close();
    
                    if (aes != null)
                        aes.Clear();
                }
    
                return returnData;
    
            }
    


    Ginny Caughey

    Sunday, September 23, 2012 7:23 PM
  • Hi Ginny,

    thanks for the quick response. Copy & pasted the code and now it works perfect.

    Great help.

    Stefan

    Monday, September 24, 2012 9:38 AM