0

I am writing an iPhone application that needs to encrypt a password using AES encryption. I have found many different examples for AES encryption but I'm finding that the implementation differs from sample to sample. This would be fine if I controlled the decryption process as well, but I do not - I need to send the encrypted password to a .NET API, which will decrypt the password using .NET code.

I am including the C# code below. Can someone point me in the right direction, or even better, provide some Objective-C code for encrypting an NSString which will work with this C# code?

The sharedSecret I have been provided with is 126 characters in length, so I'm assuming this is 128-bit encryption. Or should the sharedSecret then be 128 characters?

public class Crypto
{
    private static byte[] _salt = Encoding.ASCII.GetBytes("SALT GOES HERE");

    /// <summary>
    /// Encrypt the given string using AES.  The string can be decrypted using 
    /// DecryptStringAES().  The sharedSecret parameters must match.
    /// </summary>
    /// <param name="plainText">The text to encrypt.</param>
    /// <param name="sharedSecret">A password used to generate a key for encryption.</param>
    public static string EncryptStringAES(string plainText, string sharedSecret)
    {
        if (string.IsNullOrEmpty(plainText))
            throw new ArgumentNullException("plainText");
        if (string.IsNullOrEmpty(sharedSecret))
            throw new ArgumentNullException("sharedSecret");

        string outStr = null;                       // Encrypted string to return
        RijndaelManaged aesAlg = null;              // RijndaelManaged object used to encrypt the data.

        try
        {
            // generate the key from the shared secret and the salt
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);

            // Create a RijndaelManaged object
            aesAlg = new RijndaelManaged();
            aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);

            // Create a decrytor to perform the stream transform.
            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            // Create the streams used for encryption.
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                // prepend the IV
                msEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length), 0, sizeof(int));
                msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(plainText);
                    }
                }
                outStr = Convert.ToBase64String(msEncrypt.ToArray());
            }
        }
        finally
        {
            // Clear the RijndaelManaged object.
            if (aesAlg != null)
                aesAlg.Clear();
        }

        // Return the encrypted bytes from the memory stream.
        return outStr;
    }

    /// <summary>
    /// Decrypt the given string.  Assumes the string was encrypted using 
    /// EncryptStringAES(), using an identical sharedSecret.
    /// </summary>
    /// <param name="cipherText">The text to decrypt.</param>
    /// <param name="sharedSecret">A password used to generate a key for decryption.</param>
    public static string DecryptStringAES(string cipherText, string sharedSecret)
    {
        if (string.IsNullOrEmpty(cipherText))
            throw new ArgumentNullException("cipherText");
        if (string.IsNullOrEmpty(sharedSecret))
            throw new ArgumentNullException("sharedSecret");

        // Declare the RijndaelManaged object
        // used to decrypt the data.
        RijndaelManaged aesAlg = null;

        // Declare the string used to hold
        // the decrypted text.
        string plaintext = null;

        try
        {
            // generate the key from the shared secret and the salt
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);

            // Create the streams used for decryption.                
            byte[] bytes = Convert.FromBase64String(cipherText);
            using (MemoryStream msDecrypt = new MemoryStream(bytes))
            {
                // Create a RijndaelManaged object
                // with the specified key and IV.
                aesAlg = new RijndaelManaged();
                aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
                // Get the initialization vector from the encrypted stream
                aesAlg.IV = ReadByteArray(msDecrypt);
                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))

                        // Read the decrypted bytes from the decrypting stream
                        // and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                }
            }
        }
        finally
        {
            // Clear the RijndaelManaged object.
            if (aesAlg != null)
                aesAlg.Clear();
        }

        return plaintext;
    }

    private static byte[] ReadByteArray(Stream s)
    {
        byte[] rawLength = new byte[sizeof(int)];
        if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
        {
            throw new SystemException("Stream did not contain properly formatted byte array");
        }

        byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)];
        if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
        {
            throw new SystemException("Did not read byte array properly");
        }

        return buffer;
    }
}
Ben Williams
  • 4,695
  • 9
  • 47
  • 72

2 Answers2

1

The shared secret's length is not relevant to the bit length of the key in this case. You can see here how the C# is deriving the key with Rfc2898DeriveBytes:

Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);

RFC 2898 defines the PKCS5 standard (which means PBKDF2). Based on Microsoft's documentation it looks like the default iteration count is 1000, so you've got the shared secret, the salt, and the iteration count. If you plug that into another PBKDF2 implementation that will give you the raw key you need to use to encrypt.

It next creates a RijndaelManaged object (Rijndael was the name of AES before it was standardized) and gets the default key size in bits (which it then divides by 8 to get the bytes). It then gets that many bytes from the key variable. If you find out the default key size for this class then that's the size of the AES key.

(Incidentally, when creating one of these objects the documentation states that a random IV is generated and that it defaults to CBC so we can assume that from here on)

Next it writes the length of the IV, then the IV itself.

msEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length), 0, sizeof(int));
msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);

After all that it writes the ciphertext and the entire blob is complete.

On the decrypt side it does mostly the same thing in reverse. First it derives the key, then it grabs the whole encrypted blob and feeds it to ReadByteArray, which extracts the IV. Then it uses the key + IV to decrypt.

Implementing this in Objective-C shouldn't be too difficult given a sample encrypted blob and the shared secret!

Paul Kehrer
  • 13,466
  • 4
  • 40
  • 57
  • Thanks, this was a really nice explanation. I ended up finding an alternate solution, but hopefully your answer can help someone in the future. For this interested in the alternate solution: http://automagical.rationalmind.net/2009/02/12/aes-interoperability-between-net-and-iphone/ – Ben Williams May 26 '13 at 23:38
  • Nice Reply !! i appreciate but can you please tell me in case of Rfc2898DeriveBytes we are using int rounds = CCCalibratePBKDF(kCCPBKDF2, myPassData.length, saltData.length, kCCPRFHmacAlgSHA256, 32, 100); unsigned char key[32]; CCKeyDerivationPBKDF(kCCPBKDF2, myPassData.bytes, myPassData.length, saltData.bytes, saltData.length, kCCPRFHmacAlgSHA256, rounds, key, 32); how can i generate " aesAlg.IV " in objective c and how can i use it. Any help will be appreciable – ManiaChamp Feb 19 '14 at 11:55
0

If you send passwords - you doing it wrong.

Never send a password even in encrypted form, it is a security vulnerability: you have to maintain client and server to use the latest encryption/decryption library. You must be sure that the key is not compromised, you need to update the key from time to time, and hence transfer it both to server and client. You must use different keys for different passwords. You have to be sure that server is secured and not compromised, you need to know that you actually speak to the server etc etc.

Instead, create a strong cryptographic hash (a one-way function) of a password and send that over a secured channel. It would also mean that on the server side you never store passwords at all.

oleksii
  • 35,458
  • 16
  • 93
  • 163