关于java:在Android和IPhone中使用AES-128进行加密(结果不同)

Encryption using AES-128 in Android and IPhone (Different result)

我尝试在Android和iPhone平台上使用AES算法加密一些文本。我的问题是,即使使用相同的加密/解密算法(aes-128)和相同的固定变量(key、iv、mode),在两个平台上也会得到不同的结果。我包含了两个平台的代码示例,用于测试加密/解密。如果能帮我判断我做错了什么,我将不胜感激。

  • 密钥:"123456789abcdefg"
  • 四:"1111111111111111"
  • 纯文本:"hellothere"
  • 模式:"aes/cbc/nopadding"

Android代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class Crypto {
    private final static String HEX ="0123456789ABCDEF";

    public static String encrypt(String seed, String cleartext)
            throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String seed, String encrypted)
            throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("CBC");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }

    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw,"AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] raw, byte[] encrypted)
            throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw,"AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }

    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),
                    16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return"";

        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }

        return result.toString();
    }

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }
}

iPhone(Objective-C)代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
- (NSData *) transform:(CCOperation) encryptOrDecrypt data:(NSData *) inputData {

    NSData* secretKey = [Cipher md5:cipherKey];

    CCCryptorRef cryptor = NULL;
    CCCryptorStatus status = kCCSuccess;

    uint8_t iv[kCCBlockSizeAES128];
    memset((void *) iv, 0x0, (size_t) sizeof(iv));

    status = CCCryptorCreate(encryptOrDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                         [secretKey bytes], kCCKeySizeAES128, iv, &cryptor);

    if (status != kCCSuccess) {
        return nil;
    }

    size_t bufsize = CCCryptorGetOutputLength(cryptor, (size_t)[inputData length], true);

    void * buf = malloc(bufsize * sizeof(uint8_t));
    memset(buf, 0x0, bufsize);

    size_t bufused = 0;
    size_t bytesTotal = 0;

    status = CCCryptorUpdate(cryptor, [inputData bytes], (size_t)[inputData length],
                         buf, bufsize, &bufused);

    if (status != kCCSuccess) {
        free(buf);
        CCCryptorRelease(cryptor);
        return nil;
    }

    bytesTotal += bufused;

    status = CCCryptorFinal(cryptor, buf + bufused, bufsize - bufused, &bufused);

    if (status != kCCSuccess) {
        free(buf);
        CCCryptorRelease(cryptor);
        return nil;
    }

    bytesTotal += bufused;

    CCCryptorRelease(cryptor);

    return [NSData dataWithBytesNoCopy:buf length:bytesTotal];
}

+ (NSData *) md5:(NSString *) stringToHash {

    const char *src = [stringToHash UTF8String];

    unsigned char result[CC_MD5_DIGEST_LENGTH];

    CC_MD5(src, strlen(src), result);

    return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}

我的一些参考资料:

  • http://code.google.com/p/aes-encryption-samples/wiki/howtoEncryptWithJava
  • http://automagical.rationalmind.net/2009/02/12/aes-interoperability-between-net-and-iphone/
  • .NET和iPhone之间的AES互操作性?


对于iPhone,我使用了aescrypt objc,对于Android,使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class AESCrypt {

  private final Cipher cipher;
  private final SecretKeySpec key;
  private AlgorithmParameterSpec spec;


  public AESCrypt(String password) throws Exception
  {
    // hash password with SHA-256 and crop the output to 128-bit for key
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    digest.update(password.getBytes("UTF-8"));
    byte[] keyBytes = new byte[32];
    System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);

    cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
    key = new SecretKeySpec(keyBytes,"AES");
    spec = getIV();
  }      

  public AlgorithmParameterSpec getIV()
  {
    byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
    IvParameterSpec ivParameterSpec;
    ivParameterSpec = new IvParameterSpec(iv);

    return ivParameterSpec;
  }

  public String encrypt(String plainText) throws Exception
  {
    cipher.init(Cipher.ENCRYPT_MODE, key, spec);
    byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
    String encryptedText = new String(Base64.encode(encrypted, Base64.DEFAULT),"UTF-8");

    return encryptedText;
  }

  public String decrypt(String cryptedText) throws Exception
  {
    cipher.init(Cipher.DECRYPT_MODE, key, spec);
    byte[] bytes = Base64.decode(cryptedText, Base64.DEFAULT);
    byte[] decrypted = cipher.doFinal(bytes);
    String decryptedText = new String(decrypted,"UTF-8");

    return decryptedText;
  }
}


难怪你会得到不同的结果。

您的问题是您使用了错误的sha1prng进行密钥派生。阿法克没有一个共同的标准,一个sha1prng如何内部工作。即使是J2SE和BouncyCast实现也会使用相同的种子输出不同的结果。

因此,您的getRawKey(byte[] seed)的实现将生成一个随机密钥。如果使用密钥进行加密,则会得到依赖于该密钥的结果。由于密钥是随机的,所以在iOS上您将无法获得相同的密钥,因此您将获得不同的结果。

如果您想要一个密钥派生函数,那么使用一个类似pbkdf2的函数,它几乎完全标准化了密钥派生。


在Android上,您使用的是getBytes()。这是一个错误,因为这意味着您使用的是默认字符集而不是已知字符集。使用getBytes("UTF-8")代替,这样您就可以确切地知道将得到什么字节。

我不知道Objective-C的等价物,但不依赖于默认值。将字符串转换为字节时显式指定UTF-8。这样,您将在两侧获得相同的字节。

我还注意到您在Objective-C代码中使用MD5,但在Android代码中没有。这是故意的吗?


请参阅我对基于密码的AES加密的答案,因为您有效地将"seed"用作密码。(如果需要,只需将密钥长度从256更改为128即可。)

试图通过使用相同的值植入DRBG来生成相同的密钥是不可靠的。

接下来,在你的Android加密中,你没有使用CBC或IV。我的示例也演示了如何正确地执行该操作。顺便说一下,您需要为您加密的每个消息生成一个新的IV,如我的示例所示,并将其与密码文本一起发送。否则,使用CBC没有意义。


如果你想要一个Android和iPhone兼容代码的例子,请看iOS的RnCuor库和Java/Android的JNCHILTER库。

这两个项目都是开放源码的,并且共享一种通用的数据格式。在这些库中,使用了aes 256位,但是如果需要支持128位aes,则很容易修改代码。

根据接受的答案,两个库都使用pbkdf2。