我该如何使我的AES加密在Java和Objective-C(iPhone)之间保持一致?

25

我正在使用Objective-C将一个字符串进行AES加密,并且使用Java也对同一字符串进行了加密,但是出现了一些奇怪的问题。结果的前部分与某个特定点相匹配,但之后就不同了,因此当我试图从Java解码结果到iPhone时,无法进行解密。

我使用的源字符串是"Now then and what is this nonsense all about. Do you know?", 使用的密钥为"1234567890123456"

Objective-C加密代码如下:注意:这是一个NSData类别,假设该方法被调用在一个NSData对象上,因此'self'包含要加密的字节数据。

   - (NSData *)AESEncryptWithKey:(NSString *)key {
 char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused)
 bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

 // fetch key data
 [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

 NSUInteger dataLength = [self 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, kCCKeySizeAES128,
            NULL /* initialization vector (optional) */,
            [self bytes], dataLength, /* input */
            buffer, bufferSize, /* output */
            &numBytesEncrypted);
 if (cryptStatus == kCCSuccess) {
  //the returned NSData takes ownership of the buffer and will free it on deallocation
  return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
 }

 free(buffer); //free the buffer;
 return nil;
}

而且Java加密代码是...

public byte[] encryptData(byte[] data, String key) {
    byte[] encrypted = null;

    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    byte[] keyBytes = key.getBytes();

    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

    try {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);

        encrypted = new byte[cipher.getOutputSize(data.length)];
        int ctLength = cipher.update(data, 0, data.length, encrypted, 0);
        ctLength += cipher.doFinal(encrypted, ctLength);
    } catch (Exception e) {
        logger.log(Level.SEVERE, e.getMessage());
    } finally {
        return encrypted;
    }
}

这段 Objective-C 代码的十六进制输出为 -

7a68ea36 8288c73d f7c45d8d 22432577 9693920a 4fae38b2 2e4bdcef 9aeb8afe 69394f3e 1eb62fa7 74da2b5c 8d7b3c89 a295d306 f1f90349 6899ac34 63a6efa0

而Java的输出结果是 -

7a68ea36 8288c73d f7c45d8d 22432577 e66b32f9 772b6679 d7c0cb69 037b8740 883f8211 748229f4 723984beb 50b5aea1 f17594c9 fad2d05e e0926805 572156d

正如你所看到的,一切都很好,直到 -

7a68ea36 8288c73d f7c45d8d 22432577

我猜想我的设置有些不同,但是无法弄清楚具体问题,我试过在Java端之间切换ECB和CBC,但没有效果。

有人能帮忙吗?拜托了....


你们救了我脱离了一个自我维持的噩梦...谢谢! - Shade
4个回答

18

因为CCCrypt需要IV,所以它不使用链接块密码模式(如CBC)吗?这与您看到的情况一致:第一个块是相同的,但在第二个块中,Java版本应用原始密钥进行加密,而OSX版本似乎使用了其他东西。

编辑:

这里我看到一个示例。似乎需要将kCCOptionECBMode传递给CCCrypt:

ccStatus = CCCrypt(encryptOrDecrypt,
        kCCAlgorithm3DES,
        kCCOptionECBMode, <-- this could help
        vkey, //"123456789012345678901234", //key
        kCCKeySize3DES,
        nil, //"init Vec", //iv,
        vplainText, //"Your Name", //plainText,
        plainTextBufferSize,
        (void *)bufferPtr,
        bufferPtrSize,
        &movedBytes);

编辑2:

我尝试了一些命令行来确定哪一个是正确的。 我认为我可以做出贡献:

$ echo "Now then and what is this nonsense all about. Do you know?" | openssl enc -aes-128-ecb -K $(echo 1234567890123456 | xxd -p) -iv 0 | xxd 
0000000: 7a68 ea36 8288 c73d f7c4 5d8d 2243 2577  zh.6...=..]."C%w
0000010: e66b 32f9 772b 6679 d7c0 cb69 037b 8740  .k2.w+fy...i.{.@
0000020: 883f 8211 7482 29f4 7239 84be b50b 5aea  .?..t.).r9....Z.
0000030: eaa7 519b 65e8 fa26 a1bb de52 083b 478f  ..Q.e..&...R.;G.

我曾考虑过使用这个,但是我将Java代码更改为如下内容...Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");...但这没有任何区别。除非我需要改变其他东西来让Java代码使用CBC? - Simon Lee
1
非常准确!非常感谢。现在我可以停止对着墙撞头了 :)再次感谢。 - Simon Lee
没问题,很高兴能帮上忙。我尝试使用openssl来弄清楚Java或Mac哪个是正确的。我添加了我用于生成密码的openssl命令行。希望它能有所帮助。 - Alexander Torstling
哎呀,我遇到了同样的问题,尽管我的 Objective C 代码完全相同,但是我无法再更改 ObjC 部分,因为数据已经加密了。你知道如何通过更改 Java 代码来解决这个问题吗?请看我的问题:https://dev59.com/jU7Sa4cB1Zd3GeqP4pfk - Janusz
你们救了我脱离了一个自我维持的噩梦...谢谢! - Shade
大家好!这个算法和 Rijndael 128 一样吗?我在 Obj-C 和 Java 中也遇到了同样的问题。这是我的帖子链接:https://dev59.com/FYbca4cB1Zd3GeqPbNuI - olleh

11
我花了几周时间解密一个经过Base64编码、AES256加密的字符串。加密是由在iPad上使用CCCrypt(Objective-C)完成的。要在Java中(使用Bouncy Castle)完成解密。
最终我成功了并且在这个过程中学到了很多。加密代码与上面完全相同(我猜测它是从iPhone开发者文档中的Objective-C示例中获取的)。
CCCrypt()文档没有提到的是,如果您不指定选项(如kCCOptionECBMode),它默认使用CBC模式。它确实提到,如果未指定IV,则默认为全零(因此IV将成为长度为16的字节数组0x00的数组)。
利用这两个信息,您可以使用CBC在Java和OSx/iphone/ipad(CCCrypt)上创建功能上相同的加密模块(避免使用不太安全的ECB)。
Cipher init函数将把IV字节数组作为第三个参数:
cipher.init(Cipher.ENCRYPT_MODE, keySpec, IV).

CCCrypt文档确实提到了默认使用CBC模式 - 至少我很确定在某个地方看到过。但是我没有在任何地方看到默认IV的提及。这就是缺失的要素 - 谢谢! - David
对于其他人阅读此内容...您不能再将IV传递给Cipher.init()。您需要做的是(1)获取一个AlgorithmParameters对象(使用getInstance()类工厂方法),(2)将IV存储在参数对象中:params.init(new IvParameterSpec(iv)),并且(3)将params对象作为第三个参数传递给cipher.init() - David

9

对于其他需要此信息的人,disown是完全正确的...在Objective-C中调用创建加密的修订版本如下(请注意,您需要ECB模式和填充)...

CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode + kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES128,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);

请注意,为了安全起见,CBC模式被认为比ECB模式优越得多。ECB会泄露有关哪些输入块彼此相同的信息,并且这样的相同块在“正常数据”中经常发生。CBC可以被视为一种数据随机化,这使得这样的块出现的可能性大大降低。 - Thomas Pornin
谢谢!我本来要花一整天的时间来尝试解决这个问题。 - ebi
这很有帮助!被接受的答案会抛出一个像“方法调用中有更多参数”的错误。这应该是正确答案。 - abhimuralidharan

2
仅补充第一篇文章:在您的Objective C / cocoa代码中,您使用了CBC模式,在您的Java代码中,您使用了EBC,并且两者都没有使用IV初始化向量。EBC密码是块对块的,而CBC则链接到前面的块,因此,如果您的文本小于1个块(= 16个字节),则两者生成的密文都可以被另一个解密(相同)。
如果您正在寻找一种标准化密码使用的方法,NIST Special Publication 800-38A,2001 Edition具有测试向量。如果有帮助,我可以发布AES CBC和EBC向量的代码。

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