iOS和.NET生成不同的AES256结果

7
我已经做了几天了。 我最初的(也是最终的)目标是在iOS上使用CommonCrypto使用给定IV和密钥加密密码,然后在.NET上成功解密它。 经过大量的研究和失败,我已将我的目标缩小到仅在iOS和.NET上产生相同的加密字节,然后从那里开始。
我在.NET(C#,框架4.5)和iOS(8.1)中创建了简单的测试项目。 请注意,以下代码不旨在安全,而是为了缩小较大过程中的变量范围。 另外,iOS是这里的变量。 最终的.NET加密代码将由客户端部署,因此我必须使iOS加密与之保持一致。 除非确认不可能,否则将不更改.NET代码。
相关的.NET加密代码:
    static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
    {
        byte[] encrypted;
        // Create an Aes object 
        // with the specified key and IV. 
        using (Aes aesAlg = Aes.Create())
        {
            aesAlg.Padding = PaddingMode.PKCS7;
            aesAlg.KeySize = 256;
            aesAlg.BlockSize = 128;

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

            // Create the streams used for encryption. 
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }
        return encrypted;
    }

相关的iOS加密代码:
+(NSData*)AES256EncryptData:(NSData *)data withKey:(NSData*)key iv:(NSData*)ivector
{
Byte keyPtr[kCCKeySizeAES256+1]; // Pointer with room for terminator (unused)
// Pad to the required size
bzero(keyPtr, sizeof(keyPtr));

// fetch key data
[key getBytes:keyPtr length:sizeof(keyPtr)];

// -- IV LOGIC
Byte ivPtr[16];
bzero(ivPtr, sizeof(ivPtr));
[ivector getBytes:ivPtr length:sizeof(ivPtr)];

// Data length
NSUInteger dataLength = data.length;

// See the doc: For block ciphers, the output size will always be less than or equal to the input size plus the size of one block.
// That's why we need to add the size of one block here
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);

size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                      keyPtr, kCCKeySizeAES256,
                                      ivPtr,
                                      data.bytes, dataLength,
                                      buffer, bufferSize,
                                      &numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
    return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}

free(buffer);
return nil;
}

在.NET中传递pass、key和IV的相关代码,并打印结果:

byte[] c_IV = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
byte[] c_Key = { 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
String passPhrase = "X";

// Encrypt
byte[] encrypted = EncryptStringToBytes_Aes(passPhrase, c_Key, c_IV);
// Print result
for (int i = 0; i < encrypted.Count(); i++)
{
    Console.WriteLine("[{0}] {1}", i, encrypted[i]);
}

iOS中传递参数和打印结果的相关代码:

Byte c_iv[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
Byte c_key[16] = { 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
NSString* passPhrase = @"X";
// Convert to data
NSData* ivData = [NSData dataWithBytes:c_iv length:sizeof(c_iv)];
NSData* keyData = [NSData dataWithBytes:c_key length:sizeof(c_key)];
// Convert string to encrypt to data
NSData* passData = [passPhrase dataUsingEncoding:NSUTF8StringEncoding];
NSData* encryptedData = [CryptoHelper AES256EncryptData:passData withKey:keyData iv:ivData];

long size = sizeof(Byte);
for (int i = 0; i < encryptedData.length / size; i++) {
    Byte val;
    NSRange range = NSMakeRange(i * size, size);
    [encryptedData getBytes:&val range:range];
    NSLog(@"[%i] %hhu", i, val);
}

运行.NET代码后,加密后的输出字节如下所示:
[0] 194 [1] 154 [2] 141 [3] 238 [4] 77 [5] 109 [6] 33 [7] 94 [8] 158 [9] 5 [10] 7 [11] 187 [12] 193 [13] 165 [14] 70 [15] 5
相反,iOS加密后的输出字节如下所示:
[0] 77 [1] 213 [2] 61 [3] 190 [4] 197 [5] 191 [6] 55 [7] 230 [8] 150 [9] 144 [10] 5 [11] 253 [12] 253 [13] 158 [14] 34 [15] 138
我无法确定是什么原因导致了这种差异。以下是我已经确认过的一些事情:
1. iOS和.NET都可以成功解密其加密数据。 2. .NET项目中的代码行: aesAlg.Padding = PaddingMode.PKCS7; aesAlg.KeySize = 256; aesAlg.BlockSize = 128; 不会影响结果。它们可以被注释掉,输出结果仍然相同。我认为这意味着它们是默认值。我只是留下它们,以便更明显地表明我尽可能地匹配iOS的加密属性。 3. 如果我打印出iOS NSData对象“ivData”和“keyData”中的字节,它会产生与我创建它们时相同的字节列表-因此,我认为这不是初始参数的C<->ObjC桥接问题。 4. 如果我打印出iOS变量“passData”中的字节,它会输出与.NET相同的单个字节(88)。因此,我相当确定他们正在使用完全相同的数据开始加密。
由于.NET代码非常简洁,我已经没有明显的实验途径了。 我唯一的想法是有人可能能指出我的“AES256EncryptData:withKey:iv:”方法中的问题。 该代码已从普遍存在的iOS AES256代码修改,因为我们提供的密钥是一个字节数组,而不是一个字符串。 我很擅长ObjC,但对C无意中操作的舒适程度远不如-所以我肯定可能会搞砸必需的修改。
非常感谢任何帮助或建议。

注意:它们只会为不同的输入生成不同的输出。检查所有的输入、模式、密钥长度等。 - zaph
在加密调用之前和之后记录所有数据的十六进制日志。对于测试,请使用长度恰好为一个块的数据。记录将涵盖字符串编码的情况。顺便说一句,十六进制转储很重要,因为除其他外,许多编码具有明显提供有关数据(如编码)信息的模式。 - zaph
@Zaph 这绝对是我的最基本前提。在检查了所有相关参数后,我已经向SO提出了申诉。 - Sean G
1
我注意到你正在使用AES256,但只有128位的密钥!16字节x 8位。您不能指望各种函数以相同方式填充密钥,这是未定义的。请提供所需长度的所有输入。 - zaph
@Zaph 你赢了!如果将iOS密钥和.NET密钥填充到32字节,它们绝对匹配!如果你将其发布为答案,我会接受它。 - Sean G
显示剩余2条评论
2个回答

3

我注意到你正在使用AES256,但只有128位的密钥!16个字节x 8个比特。您不能指望各种函数以相同的方式填充密钥,这是未定义的。


你能否再详细解释一下吗?我遇到了类似的问题。我正在尝试在Swift中使用CommonCrypto库,但是它生成的输出与Objective-C中生成的不同。这个加密输出会在.Net服务器上进一步解密。 - Nah
创建一个包含ObjC和Swift代码、示例数据和结果的[mcve]问题。 - zaph

0

你可能正在处理字符串编码问题。在你的iOS代码中,我看到你将字符串传递为UTF-8,这将导致一个字节的字符串“X”。.NET默认使用UTF-16,这意味着你有一个两个字节的字符串“X”。

你可以使用如何将字符串转换为UTF8?在.NET中将你的字符串转换为UTF-8字节数组。你可以尝试在两种情况下写出纯文本字符串的字节数组,以确定你实际上传递了相同的字节。


所以你的意思是传递了“plainText”参数的.NET StreamWriter对象假定使用UTF-16编码,从而产生了不同的数据?如果是这样,我应该能够简单地使用iOS提供的某种形式的UTF-16编码,对吗?我尝试了所有提供的NSUTF16*编码选项,并使用任何这些选项对我的passPhrase进行编码都会产生与.NET不同的结果。然而,如果字符串没有先被转换为UTF-8,NSUTF16LittleEndianStringEncoding确实会产生与.NET相同的字节数据([88, 0])。这似乎有关联。 - Sean G
如果你只看十六进制的前几个字节,就相对容易确定编码方式。 - zaph
请注意,内部 NSString 使用 UTF-16 编码。 - zaph
UTF-8和ASCII,使用解决方案建议的填充,产生相同的结果。任何NSUTF16 编码都不与我发布的.NET代码在应用填充修复后匹配的结果相匹配。 - Sean G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接