简单的Java AES加密/解密示例

Ted*_*pin 110 java encryption aes

以下示例有什么问题?

问题是解密字符串的第一部分是无意义的.但是,剩下的很好,我明白了......

Result: `£eB6O?geS??i are you? Have a nice day.
Run Code Online (Sandbox Code Playgroud)
@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();
  }
}
Run Code Online (Sandbox Code Playgroud)

Cha*_*ara 244

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

希望这对大家都有用:要编译,你需要额外的Apache Commons Codec 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")));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果您不想依赖第三方Apache Commons Codec库,您可以使用JDK的*[javax.xml.bind.DatatypeConverter](http://docs.oracle.com/javase/7/docs/api/javax/ xml/bind/DatatypeConverter.html)*执行Base64编码/解码:`System.out.println("encrypted string:"+ DatatypeConverter.printBase64Binary(encrypted));``byte [] original = cipher.doFinal(DatatypeConverter. parseBase64Binary(加密));` (45认同)
  • Java 8已经有了Base64工具:java.util.Base64.getDecoder()和java.util.Base64.getEncoder() (34认同)
  • IV不必是秘密的,但它必须是CBC模式不可预测的(并且对于CTR是唯一的).它可以与密文一起发送.执行此操作的常用方法是在解密之前将IV添加到密文并将其切片.它应该通过`SecureRandom`生成 (11认同)
  • 你在使用恒定的IV吗?! (8认同)
  • 密码不是密钥.IV应该是随机的. (6认同)
  • 正如Hristo Stoyanov所说,以Java 8方式替换加密方法:`return Base64.getEncoder().encodeToString(encrypted);`和解密方法:`byte [] original = cipher.doFinal(Base64.getDecoder().解码(加密));` (3认同)
  • 请参阅 https://littlemaninmyhead.wordpress.com/2021/09/15/if-you-copied-any-of-these-popular-stackoverflow-encryption-code-snippets-then-you-did-it-wrong/ (3认同)
  • 在Android中,您可以使用`android.util.Base64`而不是使用Apache Commons Codec库.我使用的代码是`return Base64.encodeToString(encrypted,Base64.DEFAULT);`和`byte [] original = cipher.doFinal(Base64.decode(encrypted,Base64.DEFAULT));`. (2认同)

Bul*_*aza 40

这里没有办法解决Apache Commons CodecBase64:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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));
Run Code Online (Sandbox Code Playgroud)

打印:

Hello world!
?;??LA+??b*
Hello world!
Run Code Online (Sandbox Code Playgroud)

  • 不,这不等于chandpriyankara的代码!您的代码使用的ECB通常不安全且不需要.应明确指定CBC.指定CBC时,您的代码将中断. (8认同)
  • 这是一个完美的功能范例,就像@ chandpriyankara一样.但为什么要定义`encrypt(String)`而不是`encrypt(byte [])`的签名?加密(也是解密)是基于字节的过程(无论如何都是AES).加密以字节为输入,输出字节,解密也是如此(例如:`Cipher`对象确实如此).现在,一个特定的用例可能是来自String的加密字节,或者作为String发送(对于Mail的base64 MIME附件......),但这是一个编码字节的问题,其中存在数百个解决方案,与AES /加密完全无关. (5认同)
  • @GPI:是的,但我觉得它对`Strings`更有用,因为这基本上是我95%的时间工作,你最终还是会转换. (3认同)
  • 功能完美,完全不安全,并且使用非常糟糕的编程实践。这个班级的名字很糟糕。不会提前检查密钥大小。但最重要的是,代码使用了不安全的 ECB 模式,**将问题隐藏在原始问题中**。最后,它没有指定字符编码,这意味着在其他平台上解码为文本可能会失败。 (2认同)

GPI*_*GPI 23

在我看来,你没有正确处理初始化向量(IV).自从我上次阅读有关AES,IV和块链接以来,已经有很长一段时间了

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
Run Code Online (Sandbox Code Playgroud)

似乎没有问题.在AES的情况下,您可以将初始化向量视为密码实例的"初始状态",并且此状态是您无法从密钥获得的一些信息,而是来自加密密码的实际计算.(有人可能会争辩说,如果可以从密钥中提取IV,那么它就没有用了,因为密钥已经在密钥实例的初始阶段给出了).

因此,您应该在加密结束时从密码实例获取IV作为byte []

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();
Run Code Online (Sandbox Code Playgroud)

你应该用这个字节[] 初始化你的Cipherin DECRYPT_MODE:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Run Code Online (Sandbox Code Playgroud)

然后,你的解密应该没问题.希望这可以帮助.

  • **大多数**次**你不**想要使用ECB.只是谷歌为什么. (20认同)
  • @GPI Upvoted.其他"伟大的例子"并不那么好,他们实际上根本没有解决这个问题.相反,这似乎是新手盲目复制加密样本而不了解可能存在安全问题的地方 - 并且一如既往地存在. (3认同)
  • @Mushy:同意从一个可靠的随机源中选择并明确地设置IV,比让Cihper实例选择一个更好.另一方面,这个答案解决了混淆键的初始化向量的原始问题.这就是为什么它最初被投票的原因.现在,这篇文章已经成为一个典型的代码示例,这里的人们提出了一些很好的例子 - 仅仅是原始问题的内容. (2认同)

k17*_*170 17

您用于解密的IV不正确.替换此代码

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

有了这段代码

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
Run Code Online (Sandbox Code Playgroud)

那应该可以解决你的问题.


下面包含Java中简单AES类的示例.我不建议在生产环境中使用此类,因为它可能无法满足您的应用程序的所有特定需求.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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 java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,AES与编码无关,这就是我选择单独处理它而不需要任何第三方库的原因.

  • 与接受的答案一样,我选择通过示例回答您的问题.我提供了一个功能齐全的代码,它向您展示了如何正确处理初始化向量等等.至于你的第二个问题,我觉得需要更新的答案,因为不再需要Apache编解码器.所以这不是垃圾邮件.停止trippin. (14认同)
  • **IV**具有特定的目的,即**使密文**随机化并提供语义安全性.如果您使用相同的密钥+ IV对,则攻击者可以确定您是否发送了与之前相同的前缀的消息.IV不一定要保密,但必须是不可预测的.一种常见的方法是简单地将IV加密到密文并在解密之前将其切掉. (7认同)
  • downvote:hardcoded IV,见Artjom B.评论上面为什么它是坏的 (4认同)

pat*_*ckf 15

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

这是关于Java中AES加密的博客文章的简单摘要,所以我建议在实现任何内容之前先阅读它.但是,我仍然会提供一个简单的示例来使用并给出一些指示,注意什么.

在这个例子中,我会选择使用经过验证的加密伽罗瓦/计数器模式或GCM模式.原因是在大多数情况下,您需要 完整性和真实性以及机密性(在博客中阅读更多内容).

AES-GCM加密/解密教程

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

1.创建密钥

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

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

重要:

2.创建初始化向量

使用初始化向量(iv),以便相同的密钥将创建不同的密文.

byte[] IV = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(IV);
Run Code Online (Sandbox Code Playgroud)

重要:

3.使用IV和密钥加密

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);
Run Code Online (Sandbox Code Playgroud)

重要:

  • 使用16字节/ 128位身份验证标记(用于验证完整性/真实性)
  • 身份验证标记将自动附加到密文(在JCA实现中)
  • 由于GCM的行为类似于流密码,因此不需要填充
  • 使用CipherInputStream加密大量数据时
  • 想要检查其他(非秘密)数据是否已更改?您可能希望在此处使用更多关联数据cipher.updateAAD(associatedData); .

3.序列化为单个消息

只需附加iv和密文.如上所述,iv不需要保密.

ByteBuffer byteBuffer = ByteBuffer.allocate(4 + IV.length + cipherText.length);
byteBuffer.putInt(IV.length);
byteBuffer.put(IV);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Run Code Online (Sandbox Code Playgroud)

如果需要字符串表示,可以选择使用Base64进行编码.使用AndroidJava 8的内置实现(不要使用Apache Commons Codec - 这是一个糟糕的实现).编码用于将字节数组"转换"为字符串表示,以使其安全,例如:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
Run Code Online (Sandbox Code Playgroud)

4.准备解密:反序列化

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

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Run Code Online (Sandbox Code Playgroud)

然后解构消息

ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameter
    throw new IllegalArgumentException("invalid IV length");
}
byte[] IV = new byte[ivLength];
byteBuffer.get(IV);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
Run Code Online (Sandbox Code Playgroud)

重要:

  • 小心验证输入参数,以便通过分配太多内存来避免拒绝服务攻击(例如,攻击者可能会将长度值更改为例如2³¹分配2GB堆)

解密

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

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, IV));
byte[] plainText= cipher.doFinal(cipherText);
Run Code Online (Sandbox Code Playgroud)

重要:


请注意,最新的Android(SDK 21+)和Java(7+)实现应该具有AES-GCM.较旧的版本可能缺乏它.我仍然选择这种模式,因为与类似的Encrypt-then-Mac模式(例如AES-CBC + HMAC)相比,它更容易实现.请参阅此文章,了解如何使用HMAC实施AES-CBC.

  • 不过,我确实很钦佩这种努力,所以我只想指出一个错误:“iv 必须是不可预测的,并且是唯一的(即使用随机 iv)” - 这对于 CBC 模式是正确的,但对于 GCM 则不然。 (2认同)
  • “但我认为 SO 不应该是这个地方。”你可能是对的,但似乎大多数人都会坚持使用 SO。也许大多数用户不会投入必要的时间来完全理解该主题,但也许有一些用户会朝着正确的方向前进 - 您认为应该如何发布初学者指南?事实上,例如,在 Java/JCE 中,架构确实很难理解,特别是对于不是来自密码学研究的人来说,而且几乎没有好的架构? (2认同)
  • “如果你不明白这个主题,那么你可能不应该首先使用低级原语”当然,情况应该如此,许多开发人员仍然这样做。我不确定在通常没有太多内容的地方避免发布有关安全/加密的高质量内容是否是正确的解决方案。- 谢谢你指出我的错误 (2认同)