【问题标题】:AES Algorithm returns junk characters in the middle of my data stringAES 算法在我的数据字符串中间返回垃圾字符
【发布时间】:2018-11-07 06:33:59
【问题描述】:

我取一个数据字符串=“AkhilRanjanBiharabcdefghijklmnopMovedtoChennai18”,先加密再解密。我解密后得到的字符串是“AkhilRanjanBiharÙ†+™¸„À–ýæó@Movedtoñhennai18”,对于前 16 个和最后 16 个字符来说几乎没问题,但中间的 16 个字符绝对是垃圾。可能出了什么问题?

我的加密代码:-

public String encrypt(String value) {
    log.info("This method is not going to be used");
    String key = "theabcd@heymaths";
    initVector = "{{{{{{{{{{{{{{{{";
    String encryptedStr="";
    byte[] encrBytes =null;
    try {
        IvParameterSpec iv = new IvParameterSpec(initVector.getBytes());
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        encrBytes = cipher.doFinal(value.getBytes());
        encryptedStr = new String(encrBytes);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    String strToBeEncoded = encryptedStr +"::"+initVector;
    encrBytes = strToBeEncoded.getBytes();
    //String encoded = Base64.encodeBase64String(encrBytes);
    String encoded = Base64.getEncoder().encodeToString(encrBytes);
    String urlEncoded = null;
    try {
        urlEncoded = java.net.URLEncoder.encode(encoded, CHARSET);
    } catch (UnsupportedEncodingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return urlEncoded;
}

解密代码:-

public String decrypt(String encrypted) {
    String decryptedStr = null;
    byte[] base64Bytes = null;
    String urlDecoded = null;
    String key = HmCommonProperty.getProperty("abcd_crypt_key");
    if(key == null || key.isEmpty()) {
        key = securityKey;
    }
    String encryptionMech = HmCommonProperty.getProperty("abcd_crypt_algo");
    if(encryptionMech == null || encryptionMech.isEmpty()) {
        encryptionMech = CRYPT_MECHANISM;
    }
    try {
        //Url and Base64 decoding
        urlDecoded = java.net.URLDecoder.decode(encrypted, CHARSET);
        //base64Bytes = Base64.decodeBase64(urlDecoded);
        base64Bytes = Base64.getDecoder().decode(urlDecoded);
        //Generating IV
        String str = new String(base64Bytes);
        String[] bodyIVArr = str.split("::");
        initVector = bodyIVArr[1];
        String bodyStr = bodyIVArr[0];

        //AES Decryption
        Cipher cipher = Cipher.getInstance(encryptionMech);
        IvParameterSpec iv = new IvParameterSpec(initVector.getBytes());

        System.out.println("initVector Length ->  "
                +iv.getIV().length);
        System.out.println("input length ->  "
                +bodyStr.getBytes().length);

        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        byte[] decryptedBytes = cipher.doFinal(bodyStr.getBytes());
        decryptedStr =  new String(decryptedBytes);
    } catch (Exception ex) {
        ex.printStackTrace();
        log.error("Error occurred while decryption abcd data",ex);
    }

    return decryptedStr;
}

【问题讨论】:

  • CHARSET的值是多少?
  • 这是默认字符集 windows-1252。我不得不在这里使用它,因为 java.net.URLEncoder.encode(String s, String enc) 强制要求它。
  • 请勿将二进制数据 (byte[]) 转换为未经适当编码(例如 base64 或 hex)的字符串。
  • @Henry 好的。但是,一旦我使用 Base64 编码器将 byte[] 转换为字符串(比如 str),为了获得我想要的实际字符串,我现在必须在字符串 str 上使用 Base64 解码器,对吧?问题是解码器方法会再次给我一个字节[]。
  • 让您的代码运行起来是一个小挑战。下次请发布一个最小的可运行示例。

标签: java encryption aes


【解决方案1】:

您的加密数据是一个字节序列。如果您需要将其编码为字符串,则应使用 base64 或用于编码任意字节数组的类似编码。假装你的任意字节数组是一个有效的字符串编码会给你带来麻烦,即使你使用ISO_8859_1

替换

encryptedStr = new String(encrBytes)

encryptedStr = Base64.getEncoder().encodeToString(encrBytes)

并替换

bodyStr.getBytes()

Base64.getDecoder().decode(bodyStr)

另请参阅:How to correctly and consistely get bytes from a string for AES encryption?

【讨论】:

  • 很有帮助。谢谢你。我最初认为使用 ISO_8859_1 是解决方法。
【解决方案2】:

你的错误在这里:

encryptedStr = new String(encrBytes);
strToBeEncoded.getBytes();

这些方法使用平台默认字符集,当您从byte[] 转换为String 再转换回byte[] 时,一般情况下该过程有损。唯一不会有损的方法是平台默认字符集是"ISO_8859_1"

我将所有 11 个此类调用更改为:

encryptedStr = new String(encrBytes, StandardCharsets.ISO_8859_1);
strToBeEncoded.getBytes(StandardCharsets.ISO_8859_1);

(我没有更改CHARSET)。我现在得到的输出是:

initVector 长度 -> 16
输入长度 -> 48
AkhilRanjanBiharabcdefghijklmnopMovedtoChennai18

奖励警告 1:加密使用硬编码 "AES/CBC/NoPadding",但解密是动态的(当然也应该使用 "AES/CBC/NoPadding")。

奖励警告 2:机会很低,但 "::" 完全有可能出现在 encrBytes 中,从而搞砸了您的 str.split("::");。一种解决方案是搜索"::"最后 个出现,然后仅对其进行拆分。

【讨论】:

  • 非常感谢。将字符集更改为 ISO_8859_1 解决了它:)
  • 虽然使用 ISO_8859_1 有效(因为它是一种每个字符使用 1 个字节的编码),但从概念上讲,表示二进制数据并不正确。
  • ISO_8859_1 以 1:1 的方式将字符映射到字节,换句话说,通过保持低 8 位不变(假设高 8 位为零)。没有其他编码可以做到这一点。
  • @MarkJeronimus 但是您不应该期望所有传输都能够安全地传送 0x00 字节(其他控制字符也可能有问题)。这就是为什么您应该使用为将任意字节数组作为字符串移动而设计的编码。 Base64 是最常见的此类编码。
猜你喜欢
  • 2013-10-30
  • 1970-01-01
  • 2013-02-07
  • 1970-01-01
  • 2015-01-28
  • 2012-10-31
  • 2013-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多