AES在iOS(Obj-C)和Android(Java)中得到不同的结果

4

我对这种加密技术完全是新手,但我有一个Java应用程序和一个iOS应用程序,我希望它们都能够将文本加密为相同的结果。我使用AES。

我找到了这些代码,稍加修改当然,但它们返回不同的结果

iOS 代码:

- (NSData *)AESEncryptionWithKey:(NSString *)key {    
    unsigned char keyPtr[kCCKeySizeAES128] = { 'T', 'h', 'e', 'B', 'e', 's', 't', 'S', 'e', 'c', 'r','e', 't', 'K', 'e', 'y' };
    size_t bufferSize = 16;
    void *buffer = malloc(bufferSize);
    size_t numBytesEncrypted = 0;
    const char iv2[16] = {  65, 1, 2, 23, 4, 5, 6, 7, 32, 21, 10, 11, 12, 13, 84, 45 };
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionECBMode | kCCOptionPKCS7Padding,,
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          iv2,
                                          @"kayvan",
                                          6,
                                          dataInLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesEncrypted);


    if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer);
    return nil;
}

Java代码如下:

public static void main(String[] args) throws Exception {
    String password = "kayvan";
    String key = "TheBestSecretKey";
    String newPasswordEnc = AESencrp.newEncrypt(password, key);
    System.out.println("Encrypted Text : " + newPasswordEnc);
}

在另一个Java类(AESencrp.class)中,我有以下内容:

public static final byte[] IV = { 65, 1, 2, 23, 4, 5, 6, 7, 32, 21, 10, 11, 12, 13, 84, 45 };
public static String newEncrypt(String text, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte[] keyBytes= new byte[16];
    byte[] b= key.getBytes("UTF-8");
    int len = 16; 
    System.arraycopy(b, 0, keyBytes, 0, len);
    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(IV);
    System.out.println(ivSpec);
    cipher.init(Cipher.ENCRYPT_MODE,keySpec,ivSpec);
    byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
    String result = DatatypeConverter.printBase64Binary(results);
    return result;
}

我想要加密的字符串是 kayvan,使用密钥 TheBestSecretKey 进行加密后,经过 Base64 编码的结果如下:

iOS 平台:9wXUiV+ChoLHmF6KraVtDQ==

Java 平台:/s5YyKb3tDlUXt7pqA5OFA==

接下来该怎么做呢?


你的加密配置在两个实现之间存在某种不匹配。正如你所看到的,在iOS上有很多关于CCCrypt的配置参数。我对Java的Cipher不熟悉,因此你可能需要阅读两者的文档,并确保你使用的是相同的密钥大小、算法、填充等……此外,在你上面的Java代码中,我没有看到你设置要加密的字符串。你是否可能省略了这一步并加密了一个空字符串? - Nicholas Hart
是的,有很多参数,我感到非常困惑。我必须再读一遍。但是关于你最后的问题,我想要加密的字符串在Java的主方法中设置。我已经编辑了问题并添加了这部分内容。 - 0xKayvan
再次强调,我不确定Java Cipher...但是看起来你在Objective C中使用了不同的填充方式。例如:kCCOptionPKCS7Padding与AES/CBC/PKCS5Padding。(由于你正在使用块密码算法,所以该算法需要知道如何填充最后一个块,如果没有足够的数据填满整个块的话。)然而,如果我记得正确,PKCS5和PKCS7基本上是相同的算法,因此我不确定这是问题- 你可以尝试更改其中一种实现,以确保它们使用相同的填充方式。 - Nicholas Hart
1
另外,看起来你在iOS中使用了ECB,在Java中使用了CBC。又出现了一个问题。 - Nicholas Hart
为什么需要加密数据相等呢?可能没有理由的话,你会使你的代码变得不够安全。在大多数情况下,只有解密后的数据必须相等。请参阅http://crypto.stackexchange.com/q/5094 - Christian Strempfer
5个回答

8

你好,也许你可以在复制的帖子中添加一个引用。我也看了一下你在gist中的代码。在iOS的第14行似乎有一个小错误。也许你还需要在Java版本的第23和75行更改字节大小,因为它看起来必须从16字节更改为32字节。在iOS中,你会得到一个值为32的枚举,表示AES256。 - Alex Cio
当然,我会添加它。也许有一点小错误,但是它运行良好,所以我们可能不会更改它,因为我们已经实现了系统的某些部分,但还是感谢您的评论。 - Michal Zaborowski
加密较长的字符串可能会出现问题,记得注意。 - Alex Cio
我不确定所有这些密码是否自动进行IV填充到32位,这就是为什么它能正常工作的原因。 - Michal Zaborowski
非常感谢,运行得很好。此外,CommonCrypto中的iOS AES加密函数与您发布的Java代码完美配合。 - DarkSun
2
如何调用它? - 9to5ios

5

这里是 Android 版本,它生成用于加密 / 解密消息的字符串,并使用 Cipher 生成正确的矢量,以产生与 iOS 相同的结果。这对应于 @亚历山大在此主题中提供的 iOS 版本。

public class MyCrypter {

private static String TAG = "MyCrypter";

public MyCrypter() {

}

/**
 * Encodes a String in AES-128 with a given key
 * 
 * @param context
 * @param password
 * @param text
 * @return String Base64 and AES encoded String
 * @throws NoPassGivenException
 * @throws NoTextGivenException
 */
public String encode(Context context, String password, String text)
        throws NoPassGivenException, NoTextGivenException {
    if (password.length() == 0 || password == null) {
        throw new NoPassGivenException("Please give Password");
    }

    if (text.length() == 0 || text == null) {
        throw new NoTextGivenException("Please give text");
    }

    try {
        SecretKeySpec skeySpec = getKey(password);
        byte[] clearText = text.getBytes("UTF8");

        //IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
        final byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        // Cipher is not thread safe
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);

        String encrypedValue = Base64.encodeToString(
                cipher.doFinal(clearText), Base64.DEFAULT);
        Log.d(TAG, "Encrypted: " + text + " -> " + encrypedValue);
        return encrypedValue;

    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
    return "";
}

/**
 * Decodes a String using AES-128 and Base64
 * 
 * @param context
 * @param password
 * @param text
 * @return desoded String
 * @throws NoPassGivenException
 * @throws NoTextGivenException
 */
public String decode(Context context, String password, String text)
        throws NoPassGivenException, NoTextGivenException {

    if (password.length() == 0 || password == null) {
        throw new NoPassGivenException("Please give Password");
    }

    if (text.length() == 0 || text == null) {
        throw new NoTextGivenException("Please give text");
    }

    try {
        SecretKey key = getKey(password);

        //IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
        final byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        byte[] encrypedPwdBytes = Base64.decode(text, Base64.DEFAULT);
        // cipher is not thread safe
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
        cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
        byte[] decrypedValueBytes = (cipher.doFinal(encrypedPwdBytes));

        String decrypedValue = new String(decrypedValueBytes);
        Log.d(TAG, "Decrypted: " + text + " -> " + decrypedValue);
        return decrypedValue;

    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
    return "";
}

/**
 * Generates a SecretKeySpec for given password
 * @param password
 * @return SecretKeySpec
 * @throws UnsupportedEncodingException
 */
public SecretKeySpec getKey(String password)
        throws UnsupportedEncodingException {


    int keyLength = 128;
    byte[] keyBytes = new byte[keyLength / 8];
    // explicitly fill with zeros
    Arrays.fill(keyBytes, (byte) 0x0);

    // if password is shorter then key length, it will be zero-padded
    // to key length
    byte[] passwordBytes = password.getBytes("UTF-8");
    int length = passwordBytes.length < keyBytes.length ? passwordBytes.length
            : keyBytes.length;
    System.arraycopy(passwordBytes, 0, keyBytes, 0, length);
    SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
    return key;
}

public class NoTextGivenException extends Exception {
    public NoTextGivenException(String message) {
        super(message);
    }
}

public class NoPassGivenException extends Exception {
    public NoPassGivenException(String message) {
        super(message);
    }
}
}

我收到一个错误,说“ECB模式不能使用IV”。现在我该怎么办?定义密码算法有问题吗? - 0xKayvan
更新了我的 cipher.getInstance(...),请看一下。 - A.S.
获取相同的加密数据的正确答案,但这几乎从不必要。因此,请不要在生产代码中使用固定的IV。http://crypto.stackexchange.com/a/5095 - Christian Strempfer

1
我和我的朋友创建了一款iOS和Android应用程序,可以加密信息。要使用它,您应该使用以下代码片段从此网站创建NSData的扩展。请注意,保留HTML标签。
- (NSData *)AES128EncryptWithKey:(NSString *)key {

    // 'key' should be 32 bytes for AES256,
    // 16 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128 + [key length]]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // insert key in char array
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;

    // the encryption method, use always same attributes in android and iPhone (f.e. PKCS7Padding)
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          NULL                      /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize,       /* output */
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {

        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer);
    return nil;
}

- (NSData *)AES128DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128 + [key length]]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // insert key in char array
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          NULL                      /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize,       /* output */
                                          &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {

        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer);
    return nil;
}

+ (NSData *)base64DataFromString: (NSString *)string
{
    unsigned long ixtext, lentext;
    unsigned char ch, inbuf[4], outbuf[3];
    short i, ixinbuf;
    Boolean flignore, flendtext = false;
    const unsigned char *tempcstring;
    NSMutableData *theData;

    if (string == nil){
        return [NSData data];
    }

    ixtext = 0;
    tempcstring = (const unsigned char *)[string UTF8String];
    lentext = [string length];
    theData = [NSMutableData dataWithCapacity: lentext];
    ixinbuf = 0;

    while (true){
        if (ixtext >= lentext){
            break;
        }

        ch = tempcstring [ixtext++];
        flignore = false;

        if ((ch >= 'A') && (ch <= 'Z')){
            ch = ch - 'A';
        } else if ((ch >= 'a') && (ch <= 'z')){
            ch = ch - 'a' + 26;
        } else if ((ch >= '0') && (ch <= '9')){
            ch = ch - '0' + 52;
        } else if (ch == '+'){
            ch = 62;
        } else if (ch == '=') {
            flendtext = true;
        } else if (ch == '/') {
            ch = 63;
        } else {
            flignore = true;
        }

        if (!flignore){
            short ctcharsinbuf = 3;
            Boolean flbreak = false;

            if (flendtext){
                if (ixinbuf == 0){
                    break;
                }

                if ((ixinbuf == 1) || (ixinbuf == 2)) {
                    ctcharsinbuf = 1;
                } else {
                    ctcharsinbuf = 2;
                }

                ixinbuf = 3;
                flbreak = true;
            }

            inbuf [ixinbuf++] = ch;

            if (ixinbuf == 4){
                ixinbuf = 0;

                outbuf[0] = (inbuf[0] << 2) | ((inbuf[1] & 0x30) >> 4);
                outbuf[1] = ((inbuf[1] & 0x0F) << 4) | ((inbuf[2] & 0x3C) >> 2);
                outbuf[2] = ((inbuf[2] & 0x03) << 6) | (inbuf[3] & 0x3F);

                for (i = 0; i < ctcharsinbuf; i++) {
                    [theData appendBytes: &outbuf[i] length: 1];
                }
            }

            if (flbreak) {
                break;
            }
        }
    }

    return theData;
}

然后在你想要使用加密方法的类中,将以下内容插入到顶部:

#import "NSData+Crypt.h"

然后像这样加密您的字符串:

 NSData *value = [aString dataUsingEncoding:NSUTF8StringEncoding];
 NSData *encryptedData = [value AES128EncryptWithKey:myKey];
 NSString *myString = [encryptedData base64Encoding];

并且像这样解密数据:
NSData *myData = [NSData base64DataFromString:_textView.text];
NSData *decryptedData = [myData AES128DecryptWithKey:_textField.text];
NSString *myString2 = [[NSString alloc] initWithData:decryptedData
                                            encoding:NSUTF8StringEncoding];

我使用了Matt Gallagher的网站中的base64DataFromString方法。如果您使用其他方法,则需注意。
[[NSData alloc] base64EncodedDataWithOptions:NSUTF8StringEncoding];

该方法仅适用于 iOS 7.0 或更高版本。

我修复了超过16个字符的长键问题。 - Alex Cio
这个有 Swift 的版本吗? - Jayprakash Dubey
如果你想在项目中使用它,为什么不使用Objective-C类进行计算并通过Swift访问它呢? - Alex Cio

1
实施AES加密时需要注意以下几点:
1.绝不要使用明文作为加密密钥。始终对明文密钥进行哈希处理,然后再用于加密。
2.在加密和解密过程中,始终使用随机IV(初始化向量)。真正的随机化非常重要。在上面的示例中,没有设置初始化向量。这是一个安全漏洞。
我最近为C#,iOS和Android编写了跨平台AES加密和解密库,并将其发布在Github上。您可以在此处查看 - https://github.com/Pakhee/Cross-platform-AES-encryption

给这个答案点踩的人 - 你能解释一下原因吗? - Navneet Kumar
随机 IV(初始化向量)在 Android 和 iOS 中总是不同的...因此加密结果在 Android 和 iOS 上始终是不同的..对吗? - Jayprakash Dubey
由于随机性,IV必须与加密数据一起传输,这是一种常见的做法。双方都必须同意起始数字是IV并且需要首先提取。我曾经看到过实现中还使用了随机盐,因此您需要传输随机盐+随机IV+加密数据。 - Amar Deep Singh

0

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