Java AES / CBC解密后,初始字节不正确


116

以下示例出了什么问题?

问题在于解密字符串的第一部分是胡说八道。但是,其余的都很好,我得到了...

Result: eB6OgeS��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();
  }
}

48
请勿在严重的项目中使用此问题的任何答案!此问题中提供的所有示例都容易受oracle填充的影响,并且总体而言是非常糟糕的密码用法。您将使用下面的任何代码片段在您的项目中引入严重的加密漏洞。
HoLyVieR

16
@HoLyVieR,关于以下引号:“您不应该开发自己的加密库”“使用框架提供的高级API”。这里没有人在开发自己的密码库。我们只是在使用Java框架提供的已经存在的高级API。先生,您太不准确了。
k170

10
@MaartenBodewes,仅因为你们俩都同意并不意味着你们都是正确的。优秀的开发人员知道包装高级API和重写低级API之间的区别。好的读者会注意到,OP要求提供“简单的Java AES加密/解密示例”,而这正是他所得到的。我也不同意其他答案,这就是为什么我发布自己的答案。也许你们应该尝试相同的方法,并以您的专业知识启发我们所有人。
k170

6
@HoLyVieR那真是我在SO上读过的最荒谬的东西!您是谁来告诉别人他们可以和不能发展的东西?
TedTrippin

14
我仍然没有看到@HoLyVieR的示例。让我们看看一些还是指向库的指针?根本没有建设性。
danieljimenez

Answers:


245

许多人(包括我自己)由于缺少某些信息(例如,忘记转换为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")));
    }
}

47
如果您不希望依赖第三方Apache Commons Codec库,则可以使用JDK的javax.xml.bind.DatatypeConverter来执行Base64编码/解码: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
您是否使用恒定的IV?
vianna77

36
Java 8已经具有Base64工具:java.util.Base64.getDecoder()和java.util.Base64.getEncoder()
Hristo Stoyanov

11
IV不必一定是秘密的,但对于CBC模式(对于CTR来说是唯一的),它必须是不可预测的。它可以与密文一起发送。一种常见的方法是在密文前面加上IV,并在解密之前将其切成薄片。它应通过SecureRandom
Artjom B.

6
密码不是密钥。IV应该是随机的。
Maarten Bodewes

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);
    }
}

用法示例:

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!

5
这是一个完美的功能示例,就像@chandpriyankara的示例一样。但是,为什么要定义encrypt(String)而不是签名encrypt(byte[] )?加密(也是解密)是一个基于字节的过程(无论如何都是AES)。加密将字节作为输入,并输出字节,解密也是如此(例如:Cipher对象确实如此)。现在,一种特殊的用例可能是来自字符串的加密字节,或者以字符串形式发送(邮件的base64 MIME附件...),但这是对字节进行编码的问题,为此存在数百个与AES /加密完全无关的解决方案。
GPI

3
@GPI:是的,但是我发现它更有用,Strings因为基本上这就是我95%的时间都在工作,并且最终还是要转换。
BullyWiiPlaza 2015年

9
不,这不等于chandpriyankara的代码!您的代码使用了通常不安全且不需要的ECB。应明确指定CBC。指定CBC后,您的代码将中断。
2015年

功能完善,完全不安全并且使用了非常糟糕的编程习惯。这个班名不好。密钥大小未事先检查。但最重要的是,代码使用了不安全的ECB模式,将问题隐藏在原始问题中。最后,它没有指定字符编码,这意味着在其他平台上,对文本的解码可能会失败。
Maarten Bodewes,

24

在我看来,您没有正确处理初始化向量(IV)。自从我上一次阅读有关AES,IV和块链的信息以来已经有很长时间了,但是您的观点

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

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

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

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

并且您应该使用此byte [] 初始化Cipherin DECRYPT_MODE

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

然后,您的解密应该可以了。希望这可以帮助。


感谢您帮助新手。我从其他帖子中整理了这个示例。我不认为您知道如何避免需要静脉注射吗?我已经看到但没有尝试过其他不使用它的AES示例。
TedTrippin

忽略它,我找到了答案!我需要使用AES / ECB / PKCS5Padding。
TedTrippin

20
大多数时候,你希望使用ECB。只是谷歌为什么。
若奥·费尔南德斯

2
@Mushy:同意从受信任的随机来源中选择并明确设置IV会比让Cihper实例选择一个更好。另一方面,此答案解决了混淆密钥初始化向量的原始问题。这就是为什么它最初被赞成的原因。现在,这篇文章已成为更多示例代码的起点,并且这里的人们提出了一个很好的例子-除了原始问题是什么。
GPI

3
@GPI推荐。其他“最好的例子”并不是那么好,它们实际上根本没有解决这个问题。取而代之的是,这似乎是新手盲目复制密码样本而又不了解可能存在安全性问题的地方,而且一如既往。
Maarten Bodewes '16

17

您用于解密的IV错误。替换此代码

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

有了这个代码

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

那应该可以解决您的问题。


下面包括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)));
    }
}

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


首先,您还没有回答原始问题。第二,为什么您要回答一个已经被广泛接受的问题?我以为应该阻止这种垃圾邮件。
TedTrippin

14
像接受的答案一样,我选择通过示例回答您的问题。我提供了一个功能齐全的代码,它向您展示了如何正确处理初始化向量等。至于第二个问题,我觉得需要更新的答案,因为不再需要Apache编解码器。因此,这不是垃圾邮件。停止绊倒。
k170

7
一个IV具有是特定目的随机密文,并提供语义安全。如果您使用相同的key + IV对,则攻击者可以确定您是否已发送具有与以前相同的前缀的消息。IV不必是秘密的,但必须是不可预测的。一种常见的方法是简单地在密文前面加上IV,然后在解密之前将其切开。
Artjom B.

4
downvote:硬编码的IV,请参阅Artjom B.在上面的评论为什么不好
Murmel,2016年

1
点击率模式应与NoPadding配对。CTR模式肯定不需要,而不是CBC的(除非填充神谕申请),但如果点击率使用,那么使用"/NoPadding"。CTR是一种以流密码转换AES的模式,流密码以字节而不是块为单位进行操作。
Maarten Bodewes

16

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

这是我有关Java AES加密的博客文章的简单摘要,因此我建议在实施任何内容之前先通读它。但是,我仍将提供一个简单的示例,并提供一些注意事项。

在这个例子中,我会选择使用加密认证伽罗瓦/计数器模式或GCM模式。原因是,在大多数情况下,您需要 完整性和真实性以及机密性(请参阅Blog中的更多信息)。

AES-GCM加密/解密教程

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

1.创建密钥

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

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

重要:

2.创建初始化向量

使用初始化向量(IV),以便同一秘密密钥将创建不同的密文

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

重要:

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);

重要:

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

3.序列化为单个消息

只需附加IV和密文。如上所述,IV不必是秘密的。

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

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

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

4.准备解密:反序列化

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

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

重要:

5.解密

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

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。旧版本可能缺少它。我仍然选择这种模式,因为与类似的Encrypt-then-Mac模式(例如AES-CBC + HMAC)相比,它不仅更高效,而且更易于实现。请参阅本文,了解如何通过HMAC实现AES-CBC


问题在于,索要示例显然不在SO的主题范围内。更大的问题是这些是未经审核的代码,很难验证。我很感谢您的努力,但我不认为应该将SO放在这个地方。
Maarten Bodewes,

1
不过,我确实很欣赏这项工作,所以我只想指出一个错误:“ iv必须与唯一性结合起来才能不可预测(即,使用随机iv)”-对于CBC模式,这是正确的,但对于GCM,则不是。
Maarten Bodewes,

this is true for CBC mode but not for GCM您是指整个部分,还是只是实际上不需要不可预测的部分?
帕特里克·法夫尔

1
“如果您没有得到主题,那么您可能首先不应该使用低级基元”,应该如此,许多开发人员仍会这样做。我不确定在通常没有太多东西的地方提供有关安全性/密码学的高质量内容的正确解决方案。-谢谢我指出我的错误
帕特里克·法

1
好的,只是因为我喜欢答案wrt的内容(而不是目的):IV的处理可以简化,尤其是在解密过程中:毕竟,Java使直接从现有的字节数组创建IV变得容易。解密也是如此,它不必从偏移量0开始。完全不需要复制。另外,如果您必须发送IV的长度(对吗?),那为什么不使用单个(无符号)字节-IV不会超过255个字节,对吗?
Maarten Bodewes,

2

在线编辑器可运行版本:-

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            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);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } 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.getDecoder().decode(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(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}

很酷,很高兴它有所帮助!
Bhupesh Pant

密码不是密钥,IV不应是静态的。仍然是字符串类型的代码,这使得无法销毁密钥。没有说明如何处理IV,也没有任何说明它应该是不可预测的。
Maarten Bodewes,

1

依靠标准库提供的解决方案通常是个好主意:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

这将打印“要编码的文本”。

解决方案基于《Java密码学体系结构参考指南》https://stackoverflow.com/a/20591539/146745答案。


5
切勿使用ECB模式。期。
Konstantino Sparakis

1
如果使用相同的密钥加密多个数据块,则不应使用ECB,因此对于“要编码的文本”来说已经足够了。stackoverflow.com/a/1220869/146745
andrej'5

@AndroidDev密钥在“准备密钥”部分中生成:aesKey = keygen.generateKey()
andrej

1

这是对已接受答案的改进。

变化:

(1)使用随机IV并将其添加到加密文本之前

(2)使用SHA-256从密码生成密钥

(3)不依赖于Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}

哈希仍然不是基于密码的密钥生成功能/ PBKDF。您可以使用随机密钥,也可以使用PBKDF(例如PBKDF2 /基于密码的加密)。
Maarten Bodewes,

@MaartenBodewes您可以提出改进建议吗?
wvdz

PBKDF2存在于Java中,所以我认为我只是建议一个。好,我没有编码,但是我认为这要求太多。有很多基于密码的加密示例。
Maarten Bodewes,

@MaartenBodewes我认为这可能是一个简单的修复。出于好奇,按原样使用此代码时将有哪些具体漏洞?
wvdz

0

将java.util.Base64与Spring Boot结合使用的另一种解决方案

加密类

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

EncryptorController类

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

http:// localhost:8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http:// localhost:8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

耶门多萨


-1

接受答案的优化版本。

  • 没有第三方库

  • 在加密的邮件中包含IV(可以是公开的)

  • 密码可以是任何长度

码:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

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

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

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

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

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

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

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

        return null;
    }
}

用法:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

输出示例:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

您的密码派生功能不安全。我不希望e.printStackTrace()使用所谓的优化代码。
Maarten Bodewes,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.