简体   繁体   English

Objective-C AES 128位加密

[英]Objective-C AES 128-bit encryption

I am writing an iPhone application that needs to encrypt a password using AES encryption. 我正在编写一个iPhone应用程序,该应用程序需要使用AES加密来加密密码。 I have found many different examples for AES encryption but I'm finding that the implementation differs from sample to sample. 我发现了许多用于AES加密的示例,但发现每个示例的实现都不同。 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. 如果我也控制了解密过程,那会很好,但是我不这样做-我需要将加密的密码发送到.NET API,后者将使用.NET代码对密码进行解密。

I am including the C# code below. 我包括下面的C#代码。 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? 有人可以指出我正确的方向,甚至更好的方向,提供一些用于加密NSString的Objective-C代码,该代码可与此C#代码一起使用吗?

The sharedSecret I have been provided with is 126 characters in length, so I'm assuming this is 128-bit encryption. 我提供的sharedSecret的长度为126个字符,因此我假设这是128位加密。 Or should the sharedSecret then be 128 characters? 还是sharedSecret应该是128个字符?

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;
    }
}

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 : 您可以在这里看到C#如何通过Rfc2898DeriveBytes派生密钥:

Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);

RFC 2898 defines the PKCS5 standard (which means PBKDF2). RFC 2898定义了PKCS5标准(即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. 根据Microsoft的文档,默认的迭代次数看起来​​是1000,因此您具有共享的机密,盐和迭代次数。 If you plug that into another PBKDF2 implementation that will give you the raw key you need to use to encrypt. 如果将其插入另一个PBKDF2实现中,它将为您提供加密所需要的原始密钥。

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). 接下来,它创建一个RijndaelManaged对象(Rijndael在标准化之前是AES的名称),并获取默认的密钥大小(以位为单位)(然后将其除以8得到字节)。 It then gets that many bytes from the key variable. 然后,它从key变量中获取了那么多字节。 If you find out the default key size for this class then that's the size of the AES key. 如果您找到此类的默认密钥大小,那么这就是AES密钥的大小。

(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) (顺便说一句,当创建这些对象之一时,文档指出生成了一个随机IV,并且默认为CBC,因此我们可以从这里开始假设)

Next it writes the length of the IV, then the IV itself. 接下来,它写入IV的长度,然后是IV本身。

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. 毕竟,它会写入密文,并且整个blob已完成。

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. 首先,它获取密钥,然后获取整个加密的blob,并将其提供给ReadByteArray,后者再提取IV。 Then it uses the key + IV to decrypt. 然后,它使用密钥+ IV进行解密。

Implementing this in Objective-C shouldn't be too difficult given a sample encrypted blob and the shared secret! 鉴于示例加密的blob和共享的机密,在Objective-C中实现此目标应该不会太困难!

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. 这也意味着在服务器端,您根本不会存储密码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM