使用AES和Base64编码进行加密和解密

35

我有以下用于加密数据的程序。

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Test {

    private static final String ALGORITHM = "AES";
    private static final byte[] keyValue = "ADBSJHJS12547896".getBytes();

    public static void main(String args[]) throws Exception {
        String encriptValue = encrypt("dude5");
        decrypt(encriptValue);

    }

    /**
     * @param args
     * @throws Exception
     */

    public static String encrypt(String valueToEnc) throws Exception {

        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGORITHM);
        c.init(Cipher.ENCRYPT_MODE, key);

        System.out.println("valueToEnc.getBytes().length "+valueToEnc.getBytes().length);
        byte[] encValue = c.doFinal(valueToEnc.getBytes());
        System.out.println("encValue length" + encValue.length);
        byte[] encryptedByteValue = new Base64().encode(encValue);
        String encryptedValue = encryptedByteValue.toString();
        System.out.println("encryptedValue " + encryptedValue);

        return encryptedValue;
    }

    public static String decrypt(String encryptedValue) throws Exception {
        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGORITHM);
        c.init(Cipher.DECRYPT_MODE, key);

        byte[] enctVal = c.doFinal(encryptedValue.getBytes());
        System.out.println("enctVal length " + enctVal.length);

        byte[] decordedValue = new Base64().decode(enctVal);

        return decordedValue.toString();
    }

    private static Key generateKey() throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGORITHM);
        return key;
    }

}

我在这里遇到了以下异常输出?

valueToEnc.getBytes().length 5
encValue length16
encryptedValue [B@aa9835
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
    at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
    at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)

能否有人向我解释一下原因?为什么只有在解密时才会说长度应该为16。它不会像使用doFinal方法加密一样转换成16吗。

正如异常信息所说,"如何在没有填充的情况下解密密码?"


你说的对,除了两个点。我在另一个答案中提到了。 - Danyal Sandeelo
6个回答

66

您的加密命令: getBytes, encrypt, encode, toString
您的解密命令(错误*): getBytes, decrypt, decode, toString

两个问题:

  1. 正如其他人已经提到的,您应该为解密操作反转操作顺序。您没有这样做。
  2. 加密操作会生成 16 字节,编码后变成 24 字节,但使用 toString 后会变成 106 字节。这可能与无效字符占用了额外的空间有关。

注意:此外,您不需要两次调用 generateKey()。

通过将 xxx.toString() 替换为 new String(xxx),在加密和解密函数中解决问题#2

您的解密应该像这样:

解决问题#1,方法是使用反转顺序进行解密。
正确的解密顺序: getBytes, decode, decrypt, toString

c.init(Cipher.DECRYPT_MODE, key)
val decodedValue = new Base64().decode(encryptedValue.getBytes())
val decryptedVal = c.doFinal(decodedValue)
return new String(decryptedVal)

这应该会返回 "dude5"


1
在你的回答顶部,你给出了解密顺序为:getBytes、decrypt、decode、toString。然后稍后你给出了正确的顺序为:getBytes、decode、decrypt、toString。我相信第二个顺序是正确的。 - Magnus
4
Magnus,在我的回答顶部,当我提到“你的顺序…”时,那是OP执行该序列的顺序。 “你的”指的是OP。那不是“我的”顺序。 - Babu Srinivasan
4
需要注意的是,无论是从字节转换为字符串还是从字符串转换为字节,都应该始终使用特定的字符集,例如 new String(bytes, "UTF-8")string.getBytes("UTF-8")。这样可以确保如果加密和解密在不同的系统上执行且系统字符集不同,也不会导致失败。 - Artjom B.
谢谢!我也遇到了同样的错误。加密并写入外部文件后,读取并解密时,我没有按照正确的顺序进行操作。我错过了从字符串获取字节的getBytes()部分。 - yonikawa

5

这行代码

String encryptedValue = encryptedByteValue.toString();

问题出在这里。encryptedByteValue的类型是byte[],在其上调用toString并不是你想要做的事情。相反,尝试使用

String encryptedValue = Base64.getEncoder().encodeToString(encValue);

在解密时,请使用Base64.decodeBase64(encryptedValue)。但在尝试解密之前,您必须这样做。您必须以与加密方法相反的顺序撤消操作。


1
只需在方法中添加“return”即可返回一个字符串。此外,在Base64中没有名为encodeToString的方法。 - Harshana
关于toString方法,对数组调用该方法几乎永远不是您想要做的事情。它返回对象在内存中的地址,而不是有用的字符串表示形式。关于Base64,您难道不是在使用这个吗?http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html请参见此处的方法:http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeToString%28byte[]%29 - laz
我已经放置了commons-codec-1.2,也尝试了1.3的jar包,但似乎该方法没有显示出来。 - Harshana
根据我提供的Javadoc,它在commons-code 1.4中。 - laz
我没有找到任何Base64.encodeToString()方法,但是只使用new String(encValue)就为我解决了问题。 - Magnus

2

那很好,你只需要:

1)使用new String而不是toString(),因为toString()在这两种情况下(加密和解密)都无法返回您所需的内容

2)首先需要解码,因为该值以base64进行编码。

我遇到过这个线程,但花了一些时间才找出实际问题..我将我的代码发布给其他遇到此问题的人参考。

public abstract class EncryptionDecryption {
static  byte[]  key = "!@#$!@#$%^&**&^%".getBytes();
final static String algorithm="AES";

public static String encrypt(String data){

    byte[] dataToSend = data.getBytes();
    Cipher c = null;
    try {
        c = Cipher.getInstance(algorithm);
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    SecretKeySpec k =  new SecretKeySpec(key, algorithm);
    try {
        c.init(Cipher.ENCRYPT_MODE, k);
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    byte[] encryptedData = "".getBytes();
    try {
        encryptedData = c.doFinal(dataToSend);
    } catch (IllegalBlockSizeException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (BadPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    byte[] encryptedByteValue =    new Base64().encode(encryptedData);
    return  new String(encryptedByteValue);//.toString();
}

public static String decrypt(String data){

    byte[] encryptedData  = new Base64().decode(data);
    Cipher c = null;
    try {
        c = Cipher.getInstance(algorithm);
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    SecretKeySpec k =
            new SecretKeySpec(key, algorithm);
    try {
        c.init(Cipher.DECRYPT_MODE, k);
    } catch (InvalidKeyException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    byte[] decrypted = null;
    try {
        decrypted = c.doFinal(encryptedData);
    } catch (IllegalBlockSizeException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (BadPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return new String(decrypted);
}

public static void main(String[] args){
    String password=EncryptionDecryption.encrypt("password123");
    System.out.println(password);
    System.out.println(EncryptionDecryption.decrypt(password));
}
}

2

你从哪里获取到了包含encodeToString或encodeBase64String的版本的apache codec?

我从apache官网下载了1.5版本,虽然文档中提到这些方法存在,但是在代码提示中却没有显示出来,当你使用它们时会创建一个未知的方法。

我成功地执行了:

byte raw[] = md.digest(); //step 4
byte hashBytes[] = Base64.encodeBase64(raw); //step 5
StringBuffer buffer = new StringBuffer();
for( int i=0; i<hashBytes.length; i++ )
    buffer.append(hashBytes[i]);
return buffer.toString(); //step 6

然后我得到的字符串非常长,但是它被正确地解密了。

我不认为这是“正确”的做法,但是我找不到文档中所说的方法。


2
基本上,您的加密函数和解密函数之间存在一种不对称性。当您进行加密时,会执行AES加密,然后进行base64编码,而在解密时,您不会先撤消base64编码步骤。
我认为您的base64编码存在问题,因为在base64编码的字符串中不应出现[
查看org.apache.commons.codec.binary.Base64的文档,您应该能够在编码时执行此操作:
String encryptedValue = Base64.encodeBase64String(encValue);

并且这是关于解码的内容:

byte[] encValue = Base64.decodeBase64(encryptedValue);

@Harshana: 那这个是什么?http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64String%28byte[]%29 - CB Bailey

1

我已经在示例中替换了一行:

String encryptedValue = encryptedByteValue.toString();

接下来是:

String encryptedValue = new String(encryptedByteValue);

一切正常!


1
我非常怀疑这个修复填充异常的方法是否有效;更有可能的是你正在运行不同版本的JVM。 - Cory Kendall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接