当前位置: 首页 > 知识库问答 >
问题:

JavaAES/CBC解密后初始字节不正确

岳景明
2023-03-14

以下示例有什么问题?

问题是解密字符串的第一部分是无意义的。不过,其余的都很好,我明白了...

Result: `£eB6O�geS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}

共有3个答案

能逸清
2023-03-14
匿名用户

这里有一个没有Apache Commons编解码器的解决方案Base64

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

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

使用示例:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

印刷品:

Hello world!
դ;��LA+�ߙb*
Hello world!

祁俊喆
2023-03-14

在这个回答中,我选择了“简单的Java AES加密/解密示例”主题,而不是具体的调试问题,因为我认为这将使大多数读者受益。

这是我关于Java AES加密的博文的一个简单总结,所以我建议在实现任何东西之前通读一遍。然而,我仍然会提供一个简单的例子来使用,并给出一些注意事项。

在此示例中,我将选择将经过身份验证的加密与伽罗瓦/计数器模式或 GCM 模式结合使用。原因是在大多数情况下,您希望完整性和真实性与机密性相结合(在博客中阅读更多内容)。

以下是使用带有Java加密体系结构(JCA)的AES-GCM进行加密/解密所需的步骤。不要与其他示例混合使用,因为细微的差异可能会使您的代码完全不安全。

由于这取决于您的用例,我将假设最简单的情况:随机密钥。

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

重要的是:

  • 始终使用强伪随机数生成器,例如SecureRandom
  • 使用16字节/128位长密钥(或更多-但很少需要更多)
  • 如果您想要从用户密码派生的密钥,请查看具有扩展属性的密码哈希函数(或KDF),例如PBKDF2或bcrypt
  • 如果您想要从其他来源派生密钥,请使用适当的密钥派生函数(KDF),如HKDF(此处Java实现)。不要为此使用简单的加密哈希(如SHA-256)。

使用初始化向量(IV),使得相同的密钥将创建不同的密码文本。

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

重要的是:

  • 切勿重复使用具有相同密钥的相同 IV(在 GCM/CTR 模式下非常重要)
  • IV必须是唯一的(即使用随机IV或计数器)
  • IV不需要是秘密的
  • 始终使用强大的伪随机数生成器,如 SecureRandom
  • 12 字节 IV 是 AES-GCM 模式的正确选择
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

重要的是:

  • 使用16字节/128位身份验证标签(用于验证完整性/真实性)
  • 身份验证标记将自动附加到密码文本(在JCA实现中)
  • 由于GCM的行为类似于流密码,因此不需要填充。
  • 加密大数据块时,请使用CipherInputStream
  • 如果更改了其他(非机密)数据,是否希望对其进行检查?您可能希望将关联数据与密码一起使用。updateAAD(关联数据) 更多信息

只需追加IV和密文。如上所述,静脉注射不需要保密。

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

如果需要字符串表示形式,可以选择使用 Base64 进行编码。要么使用Android的,要么使用Java 8的内置实现(不要使用Apache Commons编解码器 - 这是一个糟糕的实现)。编码用于将字节数组“转换”为字符串表示,以使其ASCII安全,例如:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

如果已对消息进行编码,请首先将其解码为字节数组:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

重要的是:

  • 请小心验证输入参数,以避免分配过多内存的拒绝服务攻击。

初始化密码并设置与加密相同的参数:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

重要的是:

  • 不要忘记使用 cipher.updateAAD(associatedData) 添加关联数据;如果您在加密期间添加了它。

在这个要点中可以找到一个工作代码片段。

注意,最近的Android (SDK 21)和Java (7)实现应该有AES-GCM。旧版本可能缺少它。我仍然选择这种模式,因为与类似的先加密后Mac的模式(例如AES-CBC HMAC)相比,这种模式更容易实现,而且更有效。参见这篇关于如何用HMAC实现AES-CBC的文章。

宗安翔
2023-03-14

包括我在内的很多人在制作这项工作时面临很多问题,因为缺少一些信息,比如忘记转换为Base64、初始化向量、字符集等。所以我想制作一个功能齐全的代码。

希望这对大家都有用:要编译,您需要额外的Apache Commons编解码器jar,可以在这里找到:http://commons.apache.org/proper/commons-codec/download_codec.cgi

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

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

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

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

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

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

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

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

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

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}
 类似资料:
  • 问题内容: 以下示例出了什么问题? 问题在于解密字符串的第一部分是胡说八道。但是,其余的都很好,我得到了… 问题答案: 由于缺少一些信息,例如忘记转换为Base64,初始化向量,字符集等信息,包括我在内的很多人在进行这项工作时都面临很多问题。因此,我想到了编写功能全面的代码。 希望这对大家有用:要进行编译,你需要其他Apache Commons Codec jar,可从以下位置获得:http :

  • 我有一个应用程序,需要在配置文件中存储一些秘密密码,如数据库和ftp密码/详细信息。我环顾四周,发现了许多使用AES的加密/解密解决方案,但我似乎不知道如何在不改变密钥的情况下使其工作。这意味着我可以加密和解密(使用相同的秘密密钥),但在重启等过程中保持持久性。我似乎无法让秘密钥匙保持不变。下面的示例显示了我的工作方法: 到目前为止还不错。然而,如果我运行它一次,我可能会得到'2Vhht/L80U

  • 问题内容: PHP加密功能 当我尝试使用下面的函数在Java中解密此结果时,我得到的只是“ Test @ string”,而我则是“ @@BKxnfÈ〜¯Ô’M”。有什么想法我错了吗?谢谢 问题答案: 编辑:从Java 8开始,Java现在包括可接受的Base64类。 这条线 看起来错了。而是使用apache commons编解码器类或Harder base64 类。同样,mcrypt使用的默认填

  • 我用java编写了这段代码,以便解密密文。我有钥匙。对我来说,一切都是正确的,但我有我要解释的问题。 这是我的代码: 我收到以下错误: 出了什么问题?我知道这个问题在某种程度上与衬垫有关,但我不知道确切的解决方案。我只有一个密文IV和密钥。

  • 我做了一个简单的文件加密/解密器。它将模式和要操作的文件作为参数。加密时,它生成随机字符串并使用该字符串加密文件。解密文件时,它会提示用户输入密码,并在解密时使用该密码。 我的问题是,解密时得到的不是明文,而是胡言乱语,尽管我小心翼翼地将相同的密钥写入输入。 非常感谢James K Polk提供的加密/解密代码!

  • 当我用AES加密和解密字节[128]{1,2,…,126,127}时,一切正常: 解密后将输出字节[128]{1,2,…,126,127}。但当我将raw改为字节[127]{128,129,…,253,254}为相同的加密/解密逻辑时,结果变成字节[381],在[2391189]的循环中: 现在解密后将输出字节[381]{239,191,189,…,239,191,189} 一开始我认为超过127