【问题标题】:Encryption in Android equivalent to php's MCRYPT_RIJNDAEL_256Android 中的加密相当于 php 的 MCRYPT_RIJNDAEL_256
【发布时间】:2013-08-08 17:18:07
【问题描述】:

我正在使用以下 php 代码进行加密:


$enc_request = base64_encode(
    mcrypt_encrypt(MCRYPT_RIJNDAEL_256, 
                 $this->_app_key, 
                 json_encode($request_params), 
                 MCRYPT_MODE_ECB)
);

现在尝试在 android 中加密并获取不同的加密字符串。下面是安卓代码:


public void enc(){
    byte[] rawKey = getRawKey("my_key".getBytes());
    SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    byte[] encrypted = cipher.doFinal("my_message".getBytes());
    String result=Base64.encodeToString(encrypted, Base64.DEFAULT);
}

private static byte[] getRawKey(byte[] seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    sr.setSeed(seed);
    kgen.init(256, sr); 
    SecretKey skey = kgen.generateKey();
    byte[] raw = skey.getEncoded();
    return raw;
}

谁能帮帮我,我哪里错了?并在 android 中获得相同的正确加密字符串。

【问题讨论】:

  • 嗨 Raghu,我没有 iv,所以我删除了 init() 形式的 iv 参数,但仍然得到不同的加密字符串。尽管我只有一个密钥/盐,但我不明白我可以在哪里使用 iv。像在 php 代码中一样加密。请查看。
  • 了解加密的块模式、填充模式和实际的 AES 算法。目前,您甚至没有在 PHP 中使用 AES,并且您在 Android 中使用了有缺陷的密钥派生机制。除了在 Java 中使用 ECB 模式加密外,其他一切都是错误的。
  • @Pankaj 我将尝试在 Java 中实现一种兼容性方法,但要做到这一点,我将不得不重复 PHP 所犯的错误,而且我真的不希望这些错误传播开来。最好围绕 mcrypt_encrypt() 编写一个包装库,以消除所犯的错误......

标签: php android encryption


【解决方案1】:

我使用 Bouncy Castle 在 Java 中创建了一个 main 方法,以展示您的代码示例中使用的 mcrypt_encrypt() 的内部工作原理。

这主要是为了向其他开发者展示PHP的mcrypt_encrypt()是一种非常危险的使用方法。它不会失败太多,但那是因为它宁愿继续它早就应该停止的地方。例如,它从键中添加或删除值。执行此操作时会发出警告,但不会直接显示在代码中。

public static void main(String[] args) throws DataLengthException, IllegalStateException, InvalidCipherTextException {

    // just some constants
    boolean ENCRYPT = true;
    boolean DECRYPT = false;

    // the key is either in binary in PHP or a string (dynamic isn't it?), lets assume ASCII
    byte[] givenKey = args[0].getBytes(Charset.forName("ASCII"));

    // determine the key size dynamically, somebody thought this was a good idea...
    // NOTE: PHP will emit a warning if the key size is larger, but will simply use the
    // largest key size otherwise
    final int keysize;
    if (givenKey.length <= 128 / Byte.SIZE) {
        keysize = 128;
    } else if (givenKey.length <= 192 / Byte.SIZE) {
        keysize = 192;
    } else {
        keysize = 256;
    }

    // create a 256 bit key by adding zero bytes to the decoded key
    byte[] keyData = new byte[keysize / Byte.SIZE];
    System.arraycopy(givenKey, 0, keyData, 0, Math.min(givenKey.length, keyData.length));
    KeyParameter key = new KeyParameter(keyData);

    // create a Rijndael cipher with 256 bit block size, this is not AES
    BlockCipher rijndael = new RijndaelEngine(256);

    // use a padding method that only works on data that cannot end with zero valued bytes
    ZeroBytePadding c = new ZeroBytePadding();

    // use ECB mode encryption, which should never be used
    PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);

    // initialize the cipher using the key (no need for an IV, this is ECB)
    pbbc.init(ENCRYPT, key);

    // create a plain text byte array
    byte[] plaintext = args[1].getBytes(Charset.forName("UTF8"));

    // create a buffer for the ciphertext
    byte[] ciphertext = new byte[pbbc.getOutputSize(plaintext.length)];

    int offset = 0;
    offset += pbbc.processBytes(plaintext, 0, plaintext.length, ciphertext, offset);
    offset += pbbc.doFinal(ciphertext, offset);

    // show the ciphertext
    System.out.println(new String(Hex.encode(ciphertext), Charset.forName("ASCII")));

    // reverse the encryption
    pbbc.init(DECRYPT, key);
    byte[] decrypted = new byte[pbbc.getOutputSize(ciphertext.length)];
    offset = 0;
    offset += pbbc.processBytes(ciphertext, 0, ciphertext.length, decrypted, offset);
    offset += pbbc.doFinal(decrypted, offset);

    // this will probably print out correctly, but it isn't actually correct
    System.out.println(new String(decrypted, Charset.forName("UTF8")));

    // check out the zero's at the end
    System.out.println(new String(Hex.encode(decrypted), Charset.forName("UTF8")));

    // so lets make it a bit shorter... the PHP way
    // note that in PHP, the string may *not* contain a null terminator
    // add it yourself before printing the string
    System.out.println(new String(decrypted, Charset.forName("UTF8")).replaceAll("\\x00+$", ""));
}

警告:以上代码包含ZeroBytePadding。后来我发现 Bouncy Castle 和 PHP 在这方面是有区别的:Bouncy Castle 要求你总是要垫,而 PHP 不需要。所以 Bouncy 增加了 1..n 个字节,而 PHP 增加了 0..(n-1) 个字节,其中 n 是块大小(Rijndael-256/256 为 32 个字节)。因此,您可能必须自己进行填充/取消填充;一定要测试边缘情况!

【讨论】:

  • 对于长度为 16 或更少的密钥可以正常工作,但是一旦我使用长度超过 16 的密钥,它就会显示误导性的字符串。我是否需要查看 KeyParameter 或建议其他内容?我尝试了我想要的。谢谢
  • 可能是它正在将其切割为 128 位而不是升级密钥。我不由自主地编写了这个程序,我会检查一下。
  • 啊,很简单,这是比较的拼写错误,使用&lt;= 128 代替&lt;= 192。感到困惑,在源代码中查找它,我的初始实现还不错。当然,PHP 的实现过于宽松了。
  • 呃,现在很有趣……!!感谢 owlstead 我从你那里学到的每一件事......!!
  • 这是唯一有效的例子。非常感谢。
猜你喜欢
  • 1970-01-01
  • 2015-10-04
  • 2014-07-07
  • 2014-05-08
  • 2012-01-03
  • 1970-01-01
  • 2018-01-24
  • 2012-01-11
  • 1970-01-01
相关资源
最近更新 更多