华硕zenfone5 t00j AES 256解密问题

ASUS zenfone5 t00j AES 256 decryption issue

我正在开发移动应用程序和客户端,我们在服务器端使用 JavaScript (kony) java。这适用于除英特尔芯片组设备(华硕 Zenfone)之外的所有其他设备。 PFB 加密的JS代码

function encryptDataModeCBC() 
{
    var encData = "Test";
    try 
    {
        var encText = CryptoJS.AES.encrypt(encData, "3f4c57006f7d2d9528de3c46b626df06cdc405cb0243b10ca7612d967c688744", {
            iv: "31fd1ae51454cd55db81f1fa60a343ed",
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        }).ciphertext.toString(CryptoJS.enc.Base64); 
        alert ("encText => "+encText);    
        kony.print("$$$$ encText => "+encText);    
    } 
    catch (e) 
    {
        alert(kony.i18n.getLocalizedString("technicalError"));      
    }
}

此处使用 sha256 和 sha512 哈希算法创建 IV 和密钥。

PFB 我们在服务器端用于解密加密字符串的代码片段

密钥生成代码

private SecretKeySpec getKey(String mode, String msgDigest, String encryptionKey, boolean is256) throws Exception {
    byte[] key = encryptionKey.getBytes("UTF-8");
    MessageDigest sha = MessageDigest.getInstance(msgDigest); // This is SHA-256
    key = sha.digest(key);
    if (is256) {  // This is true in our case.
      key = Arrays.copyOf(key, 32);
      this.logger.debug("Secret Key " + DigestUtils.sha256Hex(encryptionKey).substring(0, 32));
    } else {
      key = Arrays.copyOf(key, 16);
      this.logger.debug("Secret Key " + DigestUtils.sha256Hex(encryptionKey).substring(0, 16));
    }
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
    String modeStr = mode.equals("ECB") ? "AES/ECB/PKCS5Padding" : "AES/CBC/PKCS5Padding";
    cipher = Cipher.getInstance(modeStr);
    return secretKeySpec;
}

服务器端IV生成

private IvParameterSpec getIV(String uid, String pin) throws Exception {
  String ivValue = new StringBuilder(uid).reverse().toString() + new StringBuilder(pin).reverse();
  byte[] key = ivValue.getBytes("UTF-8");
  MessageDigest sha = MessageDigest.getInstance("SHA-256");
  key = sha.digest(key);
  key = Arrays.copyOf(key, 16);
  IvParameterSpec iv = new IvParameterSpec(key);
  return iv;
}

正如我上面提到的,这在英特尔芯片组设备中失败了。这是我在解密字符串

时遇到的异常
javax.crypto.BadPaddingException: Given final block not properly padded
  at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
  at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
  at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
  at javax.crypto.Cipher.doFinal(DashoA13*..)

当我尝试加密字符串 "Test" 时,我得到 "Tn2SzI8dmgCmEvQrzdqLxw==" 作为我在下面 java 代码中使用的加密字符串,并尝试解密我得到以下错误的地方

enc text => 7b9UNDI4IWNITNAQlYNP8w==
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.ust.Encryptor.decrypt(Encryptor.java:92)
at com.ust.Encryptor.main(Encryptor.java:113)

这是我用来解密

的JAVA代码
package com.ust;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;    
import org.apache.commons.codec.digest.DigestUtils;

public class Encryptor {
    private static final String AES_PASS = "0ca763dc6b05b5230e44beb6b90e346440204b6d334b09623eafd3fcfbad6a302faca28b0994872e3fd782e7353026684b7ac9385662144e0ed1e2a8e3e14fab79059929681e3794eb97271328ecccda6dbfb3a7991ea1324615cf5908fabdf6"; // Hashed into an AES key later
    private SecretKeySpec keyObj;
    private Cipher cipher;
    private IvParameterSpec ivObj;
    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();


    public Encryptor() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException {
        // A constant IV, since CBC requires an IV but we don't really need one

        String ivValue = new StringBuilder("astring").reverse().toString() + new StringBuilder("0ca763dc6b05b5230e44beb6b90e346440204b6d334b09623eafd3fcfbad6a302faca28b0994872e3fd782e7353026684b7ac9385662144e0ed1e2a8e3e14fab").reverse();
        System.out.println("ivValue => "+ivValue);
        try {
            byte[] ivkey = ivValue.getBytes("UTF-8");
            MessageDigest shaIv = MessageDigest.getInstance("SHA-256");
            ivkey = shaIv.digest(ivkey);
            ivkey = Arrays.copyOf(ivkey, 16);
            System.out.println("IV => "+bytesToHex(ivkey));
            this.ivObj = new IvParameterSpec(ivkey);
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // Create an SHA-256 256-bit hash of the key
        byte[] key = AES_PASS.getBytes();
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        key = sha.digest(key);
        key = Arrays.copyOf(key, 32); // Use only first 256 bit
        System.out.println("SEC KEY => "+bytesToHex(key));
        this.keyObj = new SecretKeySpec(key, "AES");

        // Create a Cipher by specifying the following parameters
        //  a. Algorithm name - here it is AES 
        //  b. Mode - here it is CBC mode 
        //  c. Padding - e.g. PKCS7 or PKCS5
        this.cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    }

    public String encrypt(String strDataToEncrypt) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException {
        String strCipherText = new String();

        this.cipher.init(Cipher.ENCRYPT_MODE, this.keyObj, this.ivObj);

        // Encrypt the Data 
        //  a. Declare / Initialize the Data. Here the data is of type String 
        //  b. Convert the Input Text to Bytes 
        //  c. Encrypt the bytes using doFinal method
        byte[] byteDataToEncrypt = strDataToEncrypt.getBytes();

        byte[] byteCipherText = this.cipher.doFinal(byteDataToEncrypt);

        // b64 is done differently on Android
        strCipherText = Base64.encodeBase64String(byteCipherText);

        return strCipherText;
    }

    public String decrypt(String strCipherText) throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException {
        String strDecryptedText = new String();

        // Initialize the Cipher for Encryption
        this.cipher.init(Cipher.DECRYPT_MODE, this.keyObj, this.ivObj);

        // Decode the Base64 text
        byte[] cipherBytes = Base64.decodeBase64(strCipherText);

        // Decrypt the Data
        //  a. Initialize a new instance of Cipher for Decryption (normally don't reuse the same object)
        //     Be sure to obtain the same IV bytes for CBC mode.
        //  b. Decrypt the cipher bytes using doFinal method
        byte[] byteDecryptedText = this.cipher.doFinal(cipherBytes);
        strDecryptedText = new String(byteDecryptedText);

        return strDecryptedText;
    }
    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        int v;
        for ( int j = 0; j < bytes.length; j++ ) {
            v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    public static void main (String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException{
        Encryptor aesCipher = new Encryptor();
        try {
            String encText = aesCipher.encrypt("Test");
            System.out.println("enc text => "+encText);
            String plaintext = aesCipher.decrypt("Tn2SzI8dmgCmEvQrzdqLxw==");//("eat6f1uCCXVqJgTNUA8BCqXSA4kG4GhKajXdkyV0TewK+jgDkbQ/lPVaevv4rW3XdSmtVyOKLVJjPw9Akeblrh+ejIv9u48n7PkRKniwfxq/URuPU7lhS/sO5JMiJ7+ufgKFvJapxhSfftCtigtDc8F6Y2lJIPEUeQeQKOVc1noeLqPFggz55hWjWvDtpYh/sG76MwLlWDM7cj+uu6ru3ImmDA7qoM4tJOWBBkfng8u20R1ZcF3gM45TgDLUdL912AE1WO+grGBGjqzTXlK2/jgu3OOsLVI0jndB49K5q3/oKJc7JEoIZb0eZJcuZ80A");
            System.out.println("plain text => "+plaintext);
        } catch (InvalidKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

CryptoJS 假设

  • 作为字符串传递的密钥实际上是一个密码,它将与随机生成的盐一起再次散列,或者如果它是 WordArray 和
  • ,它将按原样使用密钥
  • IV 应该是 WordArray

WordArray 是 CryptoJS 的内部二进制数据表示。

代码应该是:

try {
    var key = CryptoJS.enc.Hex.parse("3f4c57006f7d2d9528de3c46b626df06cdc405cb0243b10ca7612d967c688744");
    var iv = CryptoJS.enc.Hex.parse("31fd1ae51454cd55db81f1fa60a343ed44");
    var encText = CryptoJS.AES.encrypt(encData, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    }).ciphertext.toString(CryptoJS.enc.Base64); 
    alert ("encText => "+encText);    
    kony.print("$$$$ encText => "+encText);    
} 
catch (e) 
{
    alert(kony.i18n.getLocalizedString("technicalError"));
}

需要思考的事情:

  • 如果您将对称密钥从服务器发送到客户端,那么可能正在监听的任何人都将获得密钥并可以解密您发送的密文。此解决方案不提供安全性,而是提供混淆。您应该使用 TLS,这将使连接真正安全。

  • IV 必须是不可预测的(读作:随机)。不要使用静态 IV,因为这会使密码具有确定性,因此在语义上不安全。观察密文的攻击者可以确定之前发送相同消息前缀的时间。 IV 不是秘密的,因此您可以将它与密文一起发送。通常,它只是简单地添加到密文中并在解密之前被切掉。

  • 最好对您的密文进行身份验证,以便像 padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC 方案一样进行攻击。