简体   繁体   English

Android 和 iPhone 中使用 AES-128 加密(结果不同)

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

I am trying to encrypt some text using the AES algorithm on both the Android and IPhone platforms.我正在尝试在 Android 和 iPhone 平台上使用 AES 算法加密一些文本。 My problem is, even using the same encryption/decryption algorithm (AES-128) and same fixed variables (key, IV, mode), I get different result on both platforms.我的问题是,即使使用相同的加密/解密算法 (AES-128) 和相同的固定变量(密钥、IV、模式),我在两个平台上也会得到不同的结果。 I am including code samples from both platforms, that I am using to test the encryption/decryption.我包含了来自两个平台的代码示例,用于测试加密/解密。 I would appreciate some help in determining what I am doing wrong.我很感激在确定我做错了什么方面的帮助。

  • Key: “123456789abcdefg”密钥:“123456789abcdefg”
  • IV: “1111111111111111”四:“1111111111111111”
  • Plain Text: “HelloThere”纯文本:“HelloThere”
  • Mode: “AES/CBC/NoPadding”模式:“AES/CBC/NoPadding”

Android Code:安卓代码:

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) Code: iPhone (Objective-C) 代码:

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

Some of my references :我的一些参考资料:

For iPhone I used AESCrypt-ObjC , and for Android use this code:对于 iPhone,我使用AESCrypt-ObjC ,对于 Android 使用以下代码:

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

It makes me no wonder that you get different results.难怪你得到不同的结果。

Your problem is that you use misuse a SHA1PRNG for key derivation.您的问题是您滥用 SHA1PRNG 进行密钥派生。 AFAIK there is no common standard how a SHA1PRNG work internally. AFAIK SHA1PRNG 在内部如何工作没有通用标准。 AFAIR even the J2SE and Bouncycaste implementation output different results using the same seed. AFAIR 甚至 J2SE 和 Bouncycaste 实现使用相同的种子输出不同的结果。

Hence your implementation of your getRawKey(byte[] seed) will generate you a random key.因此,您对getRawKey(byte[] seed)将为您生成一个随机密钥。 If you use the key for encryption you are getting an result that depends on that key.如果您使用密钥进行加密,您将获得取决于该密钥的结果。 As the key is random you will not get the same key on iOS and therefore you are getting a different result.由于密钥是随机的,您不会在 iOS 上获得相同的密钥,因此您会得到不同的结果。

If you want a key derivation function use a function like PBKDF2 with is nearly fully standardized regarding the key derivation.如果您想要密钥派生函数,请使用 PBKDF2 之类的函数,其中密钥派生几乎完全标准化。

On Android, you are using getBytes() .在 Android 上,您使用的是getBytes() This is an error as it means you are using the default charset rather than a known charset.这是一个错误,因为这意味着您使用的是默认字符集而不是已知字符集。 Use getBytes("UTF-8") instead so you know exactly what bytes you are going to get.改用getBytes("UTF-8")以便您确切地知道您将获得哪些字节。

I don't know the equivalent for Objective-C, but don't rely on the default.我不知道 Objective-C 的等价物,但不要依赖默认值。 Explicitly specify UTF-8 when converting strings to bytes.将字符串转换为字节时明确指定 UTF-8。 That way you will get the same bytes on both sides.这样,您将在两侧获得相同的字节。

I also note that you are using MD5 in the Objective-C code but not in the Android code.我还注意到您在 Objective-C 代码中使用了 MD5,但在 Android 代码中没有使用。 Is this deliberate?这是故意的吗?

See my answer for password-based AES encryption, since, you are effectively using your "seed" as a password.请参阅我对基于密码的 AES 加密的回答因为您实际上是在使用“种子”作为密码。 (Just change the key length of 256 to 128, if that's what you want.) (只需将 256 的密钥长度更改为 128,如果这是您想要的。)

Trying to generate the same key by seeding a DRBG with the same value is not reliable.试图通过用相同的值播种 DRBG 来生成相同的密钥是不可靠的。

Next, you are not using CBC or the IV in your Android encryption.接下来,您没有在 Android 加密中使用 CBC 或 IV。 My example shows how to do that properly too.我的例子也展示了如何正确地做到这一点。 By the way, you need to generate a new IV for every message you encrypt, as my example shows, and send it along with the cipher text.顺便说一下,您需要为您加密的每条消息生成一个新的 IV,如我的示例所示,并将其与密文一起发送。 Otherwise, there's no point in using CBC.否则,使用 CBC 毫无意义。

If you want an example of compatible code for Android and iPhone, look at the RNCryptor library for iOS and the JNCryptor library for Java/Android.如果你想为Android和iPhone,看看兼容的代码示例RNCryptor库适用于iOS和JNCryptor库为Java / Android系统。

Both projects are open source and share a common data format.这两个项目都是开源的,共享一个通用的数据格式。 In these libraries, AES 256-bit is used, however it would be trivial to adapt the code if necessary to support 128-bit AES.在这些库中,使用 AES 256 位,但是如果需要支持 128 位 AES,调整代码将是微不足道的。

As per the accepted answer, both libraries use PBKDF2.根据接受的答案,两个库都使用 PBKDF2。

Note: For android in java注意:对于java中的android

I have written this manager file and its functions are working perfectly fine for me.我已经编写了这个管理器文件,它的功能对我来说非常好。 This is for AES 128 and without any salt.这是针对 AES 128 的,没有任何盐分。

public class CryptoManager {

private static CryptoManager shared;
private String privateKey = "your_private_key_here";
private String ivString = "your_iv_here";


private CryptoManager(){
}

public static CryptoManager getShared() {
    if (shared != null ){
        return shared;
    }else{
        shared = new CryptoManager();
        return shared;
    }
}

public String encrypt(String value) {
    try {
        IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("UTF-8"));
        SecretKeySpec skeySpec = new SecretKeySpec(privateKey.getBytes("UTF-8"), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

        byte[] encrypted = cipher.doFinal(value.getBytes());
        return android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}


public String decrypt(String encrypted) {
    try {
        IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("UTF-8"));
        SecretKeySpec skeySpec = new SecretKeySpec(privateKey.getBytes("UTF-8"), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        byte[] original = new byte[0];
        original = cipher.doFinal(android.util.Base64.decode(encrypted, android.util.Base64.DEFAULT));

        return new String(original);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return null;
}
}

You need to call the functions like this.您需要像这样调用函数。

String dataToEncrypt = "I need to encrypt myself";
String encryptedData = CryptoManager.getShared().encrypt(data);

And you will get your encrypted string with the following line您将使用以下行获得加密字符串

String decryptedString = CryptoManager.getShared().decrypt(encryptedData);

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

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