如何在 Java 中加密字符串

我需要的是加密字符串,这将显示在2D 条形码(PDF-417) ,所以当有人得到一个想法,扫描将得不到任何可读的。

其他要求:

  • 不应该是复杂的
  • 它不应该由 RSA、 PKI 基础设施、密钥对等组成。

它必须足够简单,能够摆脱那些到处窥探的人,并且容易为其他有兴趣获得这些数据的公司解密。他们打电话给我们,我们告诉他们标准或给他们一些简单的密钥,然后可以用于解密。

也许这些公司可以使用不同的技术,所以最好坚持一些标准,而不是一些特殊的平台或技术。

你有什么建议?是否有一些 Java 类在做 encrypt()decrypt()时没有太多的复杂性来达到高安全标准?

487231 次浏览

下面是一些你可以阅读 Java 支持的链接

加密/解密数据流。

此示例演示如何 加密(使用对称加密) 算法,如 AES,河豚,RC2, 大量的数据 数据以块的形式传递给 加密方法: EncryptBytes, EncryptString,EncryptBytesENC,或 EncryptStringENC (方法名称 指示输入的类型(字符串或 字节数组)和返回类型 (编码字符串或字节数组) FirstChunk 和 LastChunk 属性 用于指示块是否 中的第一个、中间的或最后一个 流加密。默认情况下, FirstChunk 和 LastChunk 都一样 True 表示数据传递了 就是全部金额。

JCERefGuide

Java 加密示例

我建议使用一些广泛使用的标准对称密码,如 DES3DESAES。虽然这不是最安全的算法,但有很多实现,您只需要将密钥交给任何应该解密条形码中的信息的人。Javax.crypto. Cipher是您在这里想要使用的。

假设要加密的字节位于

byte[] input;

接下来,需要密钥和 初始向量字节

byte[] keyBytes;
byte[] ivBytes;

现在您可以初始化您选择的算法的密码:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

加密是这样的:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

像这样解密:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

警告

不要把这个当做某种安全措施。

本文中的加密机制是一次性密码本,这意味着攻击者可以使用2条加密消息轻松地恢复密钥。XOR 2加密信息,你就能拿到钥匙。就这么简单!

穆萨指出


我正在使用 Sun 的 Base64Encoder/Decder,它可以在 Sun 的 JRE 中找到,以避免在 lib 中出现另一个 JAR。从使用 OpenJDK 或其他 JRE 的角度来看,这是危险的。除此之外,还有其他原因我应该考虑使用 Apache commons 库与编码器/解码器?

public class EncryptUtils {
public static final String DEFAULT_ENCODING = "UTF-8";
static BASE64Encoder enc = new BASE64Encoder();
static BASE64Decoder dec = new BASE64Decoder();


public static String base64encode(String text) {
try {
return enc.encode(text.getBytes(DEFAULT_ENCODING));
} catch (UnsupportedEncodingException e) {
return null;
}
}//base64encode


public static String base64decode(String text) {
try {
return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
} catch (IOException e) {
return null;
}
}//base64decode


public static void main(String[] args) {
String txt = "some text to be encrypted";
String key = "key phrase used for XOR-ing";
System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));


String encoded = base64encode(txt);
System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
}


public static String xorMessage(String message, String key) {
try {
if (message == null || key == null) return null;


char[] keys = key.toCharArray();
char[] mesg = message.toCharArray();


int ml = mesg.length;
int kl = keys.length;
char[] newmsg = new char[ml];


for (int i = 0; i < ml; i++) {
newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
}//for i


return new String(newmsg);
} catch (Exception e) {
return null;
}
}//xorMessage
}//class

感谢我使用你的代码创建了这个类,也许有人发现它是用户满的

对象加密器

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;


import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;




public class ObjectCrypter {


private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;




public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
// wrap key data in Key/IV specs to pass to cipher




ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
try {
DESKeySpec dkey = new  DESKeySpec(keyBytes);
key = new SecretKeySpec(dkey.getKey(), "DES");
deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
byte[] input = convertToByteArray(obj);
enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);


return enCipher.doFinal(input);








//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;




}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);


return convertFromByteArray(deCipher.doFinal(encrypted));


}






private Object convertFromByteArray(byte[] byteObject) throws IOException,
ClassNotFoundException {
ByteArrayInputStream bais;


ObjectInputStream in;
bais = new ByteArrayInputStream(byteObject);
in = new ObjectInputStream(bais);
Object o = in.readObject();
in.close();
return o;


}






private byte[] convertToByteArray(Object complexObject) throws IOException {
ByteArrayOutputStream baos;


ObjectOutputStream out;


baos = new ByteArrayOutputStream();


out = new ObjectOutputStream(baos);


out.writeObject(complexObject);


out.close();


return baos.toByteArray();


}




}

这样吧:

private static byte[] xor(final byte[] input, final byte[] secret) {
final byte[] output = new byte[input.length];
if (secret.length == 0) {
throw new IllegalArgumentException("empty security key");
}
int spos = 0;
for (int pos = 0; pos < input.length; ++pos) {
output[pos] = (byte) (input[pos] ^ secret[spos]);
++spos;
if (spos >= secret.length) {
spos = 0;
}
}
return output;
}

对我来说效果很好,而且相当紧凑。

public static String encryptParams(String myTextInput) {


String myKey = "40674244454045cb9a70040a30e1c007";
String myVector = "@1B2c3D4e5F6g7H8";


String encData = "";


try{
JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
System.out.println(encData);
}catch(NoSuchAlgorithmException ex){
ex.printStackTrace();
}catch(NoSuchPaddingException ex){
ex.printStackTrace();
}catch(InvalidKeyException ex){
ex.printStackTrace();
}catch(InvalidAlgorithmParameterException ex){
ex.printStackTrace();
}catch(IllegalBlockSizeException ex){
ex.printStackTrace();
}catch(BadPaddingException ex){
ex.printStackTrace();
}catch(UnsupportedEncodingException ex){
ex.printStackTrace();
}


return encData;
}

我会考虑使用类似 https://www.bouncycastle.org/的东西。它是一个预先构建的库,允许您使用许多不同的密码对任何内容进行加密 我理解您只是想保护信息不被窥探,但是如果您真的想保护信息,那么使用 Base64实际上并不能保护您。

下面是我在 met64.com 中的 Spring Singleton 实现。如果您想为每个调用创建一个也可以工作的 ciper 实例,那么您可以删除“ synized”调用,但是要注意,“ crypher”不是线程安全的。

import java.security.Key;


import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;


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


@Component
@Scope("singleton")
public class Encryptor {


@Value("${aeskey}")
private String keyStr;


private Key aesKey = null;
private Cipher cipher = null;


synchronized private void init() throws Exception {
if (keyStr == null || keyStr.length() != 16) {
throw new Exception("bad aes key configured");
}
if (aesKey == null) {
aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
cipher = Cipher.getInstance("AES");
}
}


synchronized public String encrypt(String text) throws Exception {
init();
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
return toHexString(cipher.doFinal(text.getBytes()));
}


synchronized public String decrypt(String text) throws Exception {
init();
cipher.init(Cipher.DECRYPT_MODE, aesKey);
return new String(cipher.doFinal(toByteArray(text)));
}


public static String toHexString(byte[] array) {
return DatatypeConverter.printHexBinary(array);
}


public static byte[] toByteArray(String s) {
return DatatypeConverter.parseHexBinary(s);
}


/*
* DO NOT DELETE
*
* Use this commented code if you don't like using DatatypeConverter dependency
*/
// public static String toHexStringOld(byte[] bytes) {
// StringBuilder sb = new StringBuilder();
// for (byte b : bytes) {
// sb.append(String.format("%02X", b));
// }
// return sb.toString();
// }
//
// public static byte[] toByteArrayOld(String s) {
// int len = s.length();
// byte[] data = new byte[len / 2];
// for (int i = 0; i < len; i += 2) {
// data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
// 1), 16));
// }
// return data;
// }
}

您可能需要考虑使用一些自动化工具来生成加密/解密代码,例如 https://www.stringencrypt.com/java-encryption/

它可以为字符串或文件加密每次生成不同的加密和解密代码。

当涉及到不使用 RSA、 AES 等的快速字符串加密时,它非常方便。

结果样本:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
"\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
"\u1E06\u26AE\u2EDC";


for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
qUJQG = szTest.charAt(iatwS);
qUJQG ++;
qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
qUJQG -= iatwS;
qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
qUJQG ^= iatwS;
qUJQG -= iatwS;
qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
qUJQG ^= 0xFFFF;
qUJQG ^= 0xB6EC;
qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
qUJQG --;
qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
qUJQG ++;
qUJQG ^= 0xFFFF;
qUJQG += iatwS;
szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}


System.out.println(szTest);

我们公司一直在用。

你可以使用 Jasypt

使用 Jasypt,加密和检查密码可以非常简单..。

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

加密:

String myEncryptedText = textEncryptor.encrypt(myText);

解密:

String plainText = textEncryptor.decrypt(myEncryptedText);

格拉德尔:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

特点:

Jasypt 为您提供了简单的单向(摘要)和双向加密技术。

打开 API 以便与任何 JCE 提供程序一起使用,而不仅仅是默认的 JavaVM 提供程序。Jasypt 可以很容易地用于众所周知的提供商,如 Bouncy Castle。了解更多。

您的用户密码的安全性更高。了解更多信息。

支持二进制加密。Jasypt 允许对二进制文件(字节数组)进行摘要和加密。需要时加密对象或文件(例如通过网络发送)。

支持数字加密。除了文本和二进制文件之外,它还允许对数值进行摘要和加密(BigInteger 和 BigDecimal,对 Hibernate 持久性进行加密时支持其他数值类型)。了解更多。

完全线程安全。

支持加密器/摘要池,以便在多处理器/多核系统中实现高性能。

包括一个轻量级(“精简”)版本的库,以便在移动平台等大小限制环境中更好地管理。

为加密新手提供简单、无配置的加密工具,并为高级用户提供高度可配置的标准加密工具。

Hibernate 3和4可选集成,用于以加密方式保存映射实体的字段。字段的加密是在 Hibernate 映射文件中定义的,它对于应用程序的其余部分仍然是透明的(对于敏感的个人数据、具有许多已读用户的数据库非常有用... ...)。加密文本,二进制文件,数字,布尔值,日期... 了解更多。

可以无缝集成到 Spring 应用程序中,具有针对 Spring 2、 Spring 3.0和 Spring 3.1的特定集成特性。Jasypt 中的所有消化器和加密器都设计成可以从 Spring 轻松使用(实例化、依赖注入...)。而且,由于它们是线程安全的,因此可以在 Spring 这样的面向单例的环境中使用它们,而不必担心同步问题。了解更多内容: Spring 2、 Spring 3.0、 Spring 3.1。

Spring Security (以前的 Acegi Security)可选集成,用于执行密码加密和安全框架的匹配任务,通过使用更安全的密码加密机制提高用户密码的安全性,并为您提供更高级别的配置和控制。了解更多。

提供加密应用程序的全部或部分配置文件的高级功能,包括数据库密码等敏感信息。无缝地将加密配置集成到普通的、基于 Spring 的和/或启用 Hibernate 的应用程序中。了解更多。

提供易于使用的 CLI (命令行接口)工具,允许开发人员初始化其加密数据,并在维护任务或脚本中包括加密/解密/摘要操作。了解更多。

集成到 ApacheWicket 中,以便在您的安全应用程序中对 URL 进行更健壮的加密。

全面的指南和 javadoc 文档,使开发人员能够更好地理解他们对自己的数据究竟做了什么。

强大的字符集支持,旨在充分加密和摘要文本,无论原始字符集是。完全支持日语,韩语,阿拉伯语... 没有编码或平台问题。

非常高级的配置能力: 开发人员可以实现一些技巧,比如指示“加密器”请求远程 HTTPS 服务器输入用于加密的密码。它可以满足您的安全需求。

这是第一页,显示通过谷歌和安全 所有实现中的漏洞都让我感到害怕,所以我 发布此文件以添加关于其他加密的信息 已经从原来的职位的 7年。我举行了一个 硕士学位在 花了很多时间学习计算机工程 密码学,所以我决定把互联网变成一个 更安全的地方。

另外,请注意,对于给定的对象,许多实现可能是安全的 但是为什么使用这些并且可能意外地创建一个 错误? 使用最强大的工具,除非你有一个 总的来说,我强烈建议使用图书馆和 尽可能远离细节。

更新4/5/18: 我重写了一些部分,使它们更容易理解,并将推荐的库从 Jasypt改为 谷歌的新图书馆,我建议从现有设置中完全删除 Jasypt

前言

下面我将概述安全对称加密的基础知识,并指出当人们使用标准 Java 库自己实现加密时,我在网上看到的常见错误。如果你只是想跳过所有的细节运行到 谷歌的新图书馆导入到您的项目和使用 AES-GCM 模式的所有加密,你应该是安全的。

现在,如果你想了解关于如何在 java 中加密的细节,请继续阅读:)

块密码

首先,您需要选择一个对称密钥块密码。分组密码是一种用于创建伪随机性的计算机函数/程序。伪随机性是假的随机性,除了量子计算机以外,没有任何计算机能够区分它和真正的随机性。分组密码就像密码学的构建块,当与不同的模式或方案一起使用时,我们可以创建加密。

现在关于分组密码算法可用今天,确保使用 从来没有,我重复 从来没有使用 DES,我甚至会说永远不要使用 3DES。即使斯诺登被美国国家安全局(NSA)释放后,唯一能够证实他与伪随机系统真正接近的块密码器是 AES 256。也存在 AES 128; 区别在于 AES 256工作在256位的块中,而 AES 128工作在128个块中。总而言之,AES 128被认为是安全的,尽管已经发现了一些弱点,但是256是最可靠的。

有趣的是 DES在最初成立的时候就被国家安全局破坏了而且实际上保密了好几年。虽然有些人仍然声称 3DES是安全的,但是已经有相当多的研究论文发现和分析了 3DES的弱点。

加密模式

当您使用一个分组密码并使用一个特定的方案时,就会创建加密,以便将随机性与一个密钥结合起来,从而创建一个只要您知道密钥就可逆的东西。这被称为加密模式。

下面是一个加密模式和最简单的 ECB 模式的例子,这样你就可以直观地理解正在发生的事情:

ECB Mode

网上最常见的加密模式如下:

欧洲中央银行

在上述模式之外,还存在着其他模式,研究者们一直致力于寻找新的模式来改进现存的问题。

现在让我们继续讨论实现和安全性。从来没有使用 ECB,这不利于隐藏著名的 Linux 企鹅所显示的重复数据。Linux Penguin Example

在 Java 中实现时,请注意,如果使用以下代码,则默认设置 ECB 模式:

Cipher cipher = Cipher.getInstance("AES");

... ... 危险这是一个漏洞! 不幸的是,这在 StackOverflow 和在线教程和示例中随处可见。

注射和静脉注射

为了回应欧洲央行模式发现的问题,欧洲央行创建了也被称为 IV 的模式。我们的想法是,我们生成一个新的随机变量,并将其附加到每个加密,以便当您加密两个相同的消息,他们出来的不同。这背后的美妙之处在于,静脉注射或立即注射是公共知识。这意味着攻击者可以访问这些信息,但是只要他们没有你的密钥,他们就不能利用这些信息做任何事情。

我将看到的常见问题是,人们将 IV 设置为静态值,就像他们代码中的固定值一样。这就是静脉注射的陷阱当你重复一个的时候你实际上破坏了整个加密的安全性。

产生随机 IV

SecureRandom randomSecureRandom = new SecureRandom();
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

注意: SHA1坏了,但是我找不到如何在这个用例中正确地实现 SHA256,所以如果有人想尝试一下并更新它的话,那就太棒了!此外,SHA1攻击仍然是非常规的,因为它可能需要几年的时间才能破解一个巨大的集群。点击这里查看详细信息。

实施《条例草案》

CTR 模式不需要填充。

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

CBC 实施情况

如果您选择实现 CBC 模式,请按以下方式使用 PKCS7Paddings:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

CBC 和 CTR 漏洞及为什么应该使用 GCM

尽管 CBC 和 CTR 等其他模式是安全的,但它们会遇到这样的问题: 攻击者可以翻转加密的数据,在解密时更改其值。假设你加密了一条虚构的银行信息“ Sell 100”,你加密的信息看起来像这样“ eu23ng”,攻击者把它改成“ eu53ng”,当你解密你的信息的时候,它读作“ Sell 900”。

为了避免这种情况,大多数互联网使用 GCM,每次你看到 HTTPS,他们可能正在使用 GCM。GCM 使用散列对加密的消息进行签名,并检查是否使用此签名对消息进行了更改。

我会避免实施 GCM,因为它的复杂性。你最好使用 谷歌新图书馆,因为在这里再次如果你不小心重复一个静脉注射,你就损害了关键的情况下与 GCM,这是最终的安全漏洞。新的研究人员正致力于 IV 重复抵抗加密模式,即使你重复 IV 的关键是没有危险,但这尚未成为主流。

现在,如果你想实现 GCM,这里有一个 链接到一个良好的 GCM 实施。然而,我不能保证安全或如果它正确实现,但它得到了基础下来。也注意与 GCM 有没有填充。

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

密钥 VS 密码

另一个非常重要的注意事项是,当涉及到密码学时,密钥和密码是不一样的。密码学中的密钥需要具有一定的熵和随机性才能被认为是安全的。这就是为什么您需要确保使用适当的加密库来为您生成密钥。

所以这里实际上有两个实现,第一个是使用 用于随机密钥生成的 StackOverflow 线程上的代码。这个解决方案使用一个安全的随机数生成器从头创建一个密钥,您可以使用。

另一个不太安全的选项是使用用户输入,如密码。我们讨论的问题是密码没有足够的熵,所以我们必须使用 PBKDF2,一种获取密码并加强它的算法。这是 我喜欢的 StackOverflow 实现。然而 Google Tink 库已经内置了所有这些,你应该好好利用它。

Android 开发者

这里需要指出的一点是,你的 android 代码是可逆向工程的,大多数情况下 Java 代码也是可逆向工程的。这意味着如果在代码中以纯文本形式存储密码。黑客可以很容易地恢复它。通常,对于这些类型的加密,您希望使用非对称加密等。这超出了本文的讨论范围,所以我将避免深入讨论。

一个 2013年的有趣读物: 指出88% 的 Crypto 安卓系统实现是不正确的。

最后的想法

我再次建议不要直接实现加密的 java 库,而是使用 谷歌叮当,这样可以省去你的麻烦,因为他们真的很好地实现了所有的算法。即便如此,也要确保检查 Tink github 上出现的问题,漏洞到处都是。

如果你有任何问题或反馈,请随时发表评论! 安全性总是在变化,你需要尽最大努力跟上它:)

String s1="arshad";
char[] s2=s1.toCharArray();
int s3= s2.length;


System.out.println(s3);
int i=0;


// for(int j=0;j<s3;j++)
// System.out.println(s2[j]);


for(i=0;i<((s3)/2);i++) {


char z,f=10;
z=(char) (s2[i] * f);
s2[i]=s2[(s3-1)-i];
s2[(s3-1)-i]=z;


String b=new String(s2);


print(b);  }

这里有一个简单的解决方案,只有 java.*javax.crypto.*依赖于字节加密,提供 保密协议正直。对于千字节的短消息,它应该是 在选择的明文攻击下无法区分

它在 GCM模式下使用 AES,不使用任何填充,PBKDF2派生了一个128位密钥,并进行了大量迭代,并从提供的密码中获得了一个静态 salt。这样可以确保强制输入密码是困难的,并将熵分布在整个密钥上。

生成一个随机初始化向量(IV) ,并将其预置到密文中。此外,静态字节 0x01被预先作为第一个字节作为“版本”。

整个消息进入由 AES/GCM生成的讯息鑑别码(MAC)。

在这里,零外部依赖加密类提供 保密协议正直:

package ch.n1b.tcrypt.utils;


import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;


import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;


/**
* This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
* It provides confidentiality and integrity of the plaintext.
*
* @author Thomas Richner
* @created 2018-12-07
*/
public class AesGcmCryptor {


// https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
private static final byte VERSION_BYTE = 0x01;
private static final int VERSION_BYTE_LENGTH = 1;
private static final int AES_KEY_BITS_LENGTH = 128;




// fixed AES-GCM constants
private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
private static final int GCM_IV_BYTES_LENGTH = 12;
private static final int GCM_TAG_BYTES_LENGTH = 16;


// can be tweaked, more iterations = more compute intensive to brute-force password
private static final int PBKDF2_ITERATIONS = 1024;


// protects against rainbow tables
private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");


public String encryptString(char[] password, String plaintext) throws CryptoException {


byte[] encrypted = null;
try {
encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
| InvalidKeySpecException e) {
throw new CryptoException(e);
}
return byteArrayToHexString(encrypted);
}


public String decryptString(char[] password, String ciphertext)
throws CryptoException {


byte[] ct = hexStringToByteArray(ciphertext);
byte[] plaintext = null;
try {
plaintext = decrypt(password, ct);
} catch (AEADBadTagException e) {
throw new CryptoException(e);
} catch ( //
NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
| InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
| BadPaddingException e) {
throw new CryptoException(e);
}
return new String(plaintext, StandardCharsets.UTF_8);
}


/**
* Decrypts an AES-GCM encrypted ciphertext and is
* the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
*
* @param password   passphrase for decryption
* @param ciphertext encrypted bytes
* @return plaintext bytes
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws IllegalArgumentException           if the length or format of the ciphertext is bad
* @throws CryptoException
*/
public byte[] decrypt(char[] password, byte[] ciphertext)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {


// input validation
if (ciphertext == null) {
throw new IllegalArgumentException("ciphertext cannot be null");
}


if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
throw new IllegalArgumentException("ciphertext too short");
}


// the version must match, we don't decrypt other versions
if (ciphertext[0] != VERSION_BYTE) {
throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
}


// input seems legit, lets decrypt and check integrity


// derive key from password
SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);


// init cipher
Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
ciphertext,
VERSION_BYTE_LENGTH,
GCM_IV_BYTES_LENGTH
);
cipher.init(Cipher.DECRYPT_MODE, key, params);


final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;


// add version and IV to MAC
cipher.updateAAD(ciphertext, 0, ciphertextOffset);


// decipher and check MAC
return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
}


/**
* Encrypts a plaintext with a password.
* <p>
* The encryption provides the following security properties:
* Confidentiality + Integrity
* <p>
* This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
* <p>
* The tag is calculated over the version byte, the IV as well as the ciphertext.
* <p>
* Finally the encrypted bytes have the following structure:
* <pre>
*          +-------------------------------------------------------------------+
*          |         |               |                             |           |
*          | version | IV bytes      | ciphertext bytes            |    tag    |
*          |         |               |                             |           |
*          +-------------------------------------------------------------------+
* Length:     1B        12B            len(plaintext) bytes            16B
* </pre>
* Note: There is no padding required for AES-GCM, but this also implies that
* the exact plaintext length is revealed.
*
* @param password  password to use for encryption
* @param plaintext plaintext to encrypt
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeySpecException
*/
public byte[] encrypt(char[] password, byte[] plaintext)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
InvalidKeySpecException {


// initialise random and generate IV (initialisation vector)
SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(iv);


// encrypt
Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);


// add IV to MAC
final byte[] versionBytes = new byte[]{VERSION_BYTE};
cipher.updateAAD(versionBytes);
cipher.updateAAD(iv);


// encrypt and MAC plaintext
byte[] ciphertext = cipher.doFinal(plaintext);


// prepend VERSION and IV to ciphertext
byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
int pos = 0;
System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
pos += VERSION_BYTE_LENGTH;
System.arraycopy(iv, 0, encrypted, pos, iv.length);
pos += iv.length;
System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);


return encrypted;
}


/**
* We derive a fixed length AES key with uniform entropy from a provided
* passphrase. This is done with PBKDF2/HMAC256 with a fixed count
* of iterations and a provided salt.
*
* @param password passphrase to derive key from
* @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
*                 a random constant salt is better than no salt.
* @param keyLen   number of key bits to output
* @return a SecretKey for AES derived from a passphrase
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
throws NoSuchAlgorithmException, InvalidKeySpecException {


if (password == null || salt == null || keyLen <= 0) {
throw new IllegalArgumentException();
}
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
SecretKey pbeKey = factory.generateSecret(spec);


return new SecretKeySpec(pbeKey.getEncoded(), "AES");
}


/**
* Helper to convert hex strings to bytes.
* <p>
* May be used to read bytes from constants.
*/
private static byte[] hexStringToByteArray(String s) {


if (s == null) {
throw new IllegalArgumentException("Provided `null` string.");
}


int len = s.length();
if (len % 2 != 0) {
throw new IllegalArgumentException("Invalid length: " + len);
}


byte[] data = new byte[len / 2];
for (int i = 0; i < len - 1; i += 2) {
byte b = (byte) toHexDigit(s, i);
b <<= 4;
b |= toHexDigit(s, i + 1);
data[i / 2] = b;
}
return data;
}


private static int toHexDigit(String s, int pos) {
int d = Character.digit(s.charAt(pos), 16);
if (d < 0) {
throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
}
return d;
}


private static String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}


public class CryptoException extends Exception {


public CryptoException(Throwable cause) {
super(cause);
}
}
}

这里的整个项目与一个不错的 CLI: https://github.com/trichner/tcrypt

编辑: 现在使用适当的 encryptStringdecryptString

正如许多人已经说过的那样,您应该使用过度使用的标准密码,如 DES 或 AES。

使用 AES在 java 中加密和解密字符串的一个简单示例。

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


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


public class EncryptorDemo {


public static String encrypt(String key, String randomVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(randomVector.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 text: "  + Base64.encodeBase64String(encrypted));
return Base64.encodeBase64String(encrypted);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


public static String decrypt(String key, String randomVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(randomVector.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[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
System.out.println("decrypted text: "  + new String(originalText));
return new String(originalText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


public static void main(String[] args) {
String key = "JavasEncryptDemo"; // 128 bit key
String randomVector = "RandomJavaVector"; // 16 bytes IV
decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));


}
}

2019年12月12日最新情况

与其他一些模式,如 CBC,GCM 模式不需要 IV 是不可预测的。唯一的要求是 IV 对于每个具有给定密钥的调用必须是唯一的。如果对给定的密钥重复一次,安全性就会受到威胁。实现这一点的一个简单方法是使用一个强伪随机数生成器中的随机 IV,如下所示。

使用序列或时间戳作为 IV 也是可能的,但它可能并不像听起来那么简单。例如,如果系统没有正确地跟踪持久存储中已经作为 IV 使用的序列,则在系统重新启动后,调用可能会重复 IV。同样,没有完美的时钟。计算机时钟调整等。

此外,每2 ^ 32次调用后应旋转密钥。有关 IV 要求的进一步详细信息,请参阅本 回答NIST 建议


考虑到以下几点,这是我刚刚在 Java8中编写的加密和解密代码。希望有人会发现这个有用:

  1. 加密算法 : 256位密钥的分组密码 AES 被认为是足够安全的。要加密完整的消息,需要选择一种模式。建议使用经过身份验证的加密(同时提供机密性和完整性)。GCM、 CCM 和 EAX 是最常用的认证加密模式。GCM 通常是首选的,它在 Intel 架构中表现良好,为 GCM 提供专用指令。所有这三种模式都是基于 CTR (计数器)的模式,因此它们不需要填充。因此,他们不容易受到相关的填充攻击

  2. 气相色谱法需要一个初始向量(IV)。静脉注射已经不是秘密了。唯一的要求是它必须是随机的或不可预测的。在 Java 中,SecuredRandom类用于生成加密性强的伪随机数。在 getInstance()方法中可以指定伪随机数生成算法。但是,自从 Java8以来,推荐的方法是使用 getInstanceStrong()方法,它将使用由 Provider配置和提供的最强算法

  3. NIST 推荐96位 IV 用于 GCM,以提高互操作性、效率和设计的简单性

  4. 为了确保额外的安全性,在下面的实现中,在生成每2 ^ 16字节的伪随机字节生成之后重新播种 SecureRandom

  5. 收件人需要知道 IV 才能解密密文。因此,IV 需要与密文一起传输。一些实现将 IV 作为 AD (关联数据)发送,这意味着身份验证标记将在密码文本和 IV 上计算。但是,这并不是必需的。IV 可以简单地用密码文本预先挂起,因为如果 IV 在传输过程中由于故意攻击或网络/文件系统错误而被更改,身份验证标记验证无论如何都会失败

  6. 字符串不应该用来保存清晰的文本消息或键,因为字符串是不可变的,因此我们不能在使用后清除它们。然后,这些未清除的 String 会逗留在内存中,并可能显示在堆转储中。出于同样的原因,调用这些加密或解密方法的客户端应该在不再需要保存消息或密钥的所有变量或数组后清除它们。

  7. 在遵循一般建议的代码中,没有提供程序是硬编码的

  8. 最后,为了通过网络或存储进行传输,密钥或密码文本应该使用 Base64编码进行编码。Base64的详细信息可以在 给你中找到。应该遵循 Java8方法

可以使用以下方法清除字节数组:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

然而,在 Java8中,没有简单的方法来清除 SecretKeyspecSecretKey,因为这两个接口的实现似乎没有实现接口 Destroyable的方法 destroy()。在下面的代码中,编写了一个单独的方法来使用反射清除 SecretKeySpecSecretKey

应该使用下面提到的两种方法之一生成密钥。

请注意,密钥是像密码一样的秘密,但不像密码是为人类使用的,密钥是为加密算法所使用的,因此应该只使用上述方法生成。

package com.sapbasu.javastudy;


import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;


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


public class Crypto {


private static final int AUTH_TAG_SIZE = 128; // bits


// NIST recommendation: "For IVs, it is recommended that implementations
// restrict support to the length of 96 bits, to
// promote interoperability, efficiency, and simplicity of design."
private static final int IV_LEN = 12; // bytes


// number of random number bytes generated before re-seeding
private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);


private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";


private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
.asList(new Integer[] {128, 192, 256}); // bits


private static SecureRandom prng;


// Used to keep track of random number bytes generated by PRNG
// (for the purpose of re-seeding)
private static int bytesGenerated = 0;


public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {


Objects.requireNonNull(input, "Input message cannot be null");
Objects.requireNonNull(key, "key cannot be null");


if (input.length == 0) {
throw new IllegalArgumentException("Length of message cannot be 0");
}


if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
}


Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);


byte[] iv = getIV(IV_LEN);


GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);


cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
byte[] messageCipher = cipher.doFinal(input);


// Prepend the IV with the message cipher
byte[] cipherText = new byte[messageCipher.length + IV_LEN];
System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
messageCipher.length);
return cipherText;
}


public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
Objects.requireNonNull(input, "Input message cannot be null");
Objects.requireNonNull(key, "key cannot be null");


if (input.length == 0) {
throw new IllegalArgumentException("Input array cannot be empty");
}


byte[] iv = new byte[IV_LEN];
System.arraycopy(input, 0, iv, 0, IV_LEN);


byte[] messageCipher = new byte[input.length - IV_LEN];
System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);


GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);


Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);


return cipher.doFinal(messageCipher);
}


public byte[] getIV(int bytesNum) {


if (bytesNum < 1) throw new IllegalArgumentException(
"Number of bytes must be greater than 0");


byte[] iv = new byte[bytesNum];


prng = Optional.ofNullable(prng).orElseGet(() -> {
try {
prng = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Wrong algorithm name", e);
}
return prng;
});


if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
prng.setSeed(prng.generateSeed(bytesNum));
bytesGenerated = 0;
}


prng.nextBytes(iv);
bytesGenerated = bytesGenerated + bytesNum;


return iv;
}


private static void clearSecret(Destroyable key)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, SecurityException {
Field keyField = key.getClass().getDeclaredField("key");
keyField.setAccessible(true);
byte[] encodedKey = (byte[]) keyField.get(key);
Arrays.fill(encodedKey, Byte.MIN_VALUE);
}
}

加密密钥可以主要通过两种方式生成:

  • 没有任何密码

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
    "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
    
  • With password

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations,
    keyLength);
    SecretKeyFactory keyFactory =
    SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
    "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
    

Update Based on Comments

As pointed out by @MaartenBodewes, my answer did not handle any String as is required by the question. Therefore, I'll make an attempt to fill that gap just in case someone stumbles upon this answer and leaves wondering about handling String.

As indicated earlier in the answer, handling sensitive information in a String is, in general, not a good idea because String is immutable and thus we cannot clear it off after use. And as we know, even when a String doesn't have a strong reference, the garbage collector does not immediately rush to remove it off heap. Thus, the String continues to be around in the memory for an unknown window of time even though it is not accessible to the program. The issue with that is, a heap dump during that time frame would reveal the sensitive information. Therefore, it is always better to handle all sensitive information in a byte array or char array and then fill the array with 0s once their purpose is served.

However, with all that knowledge, if we still end up in a situation where the sensitive information to be encrypted is in a String, we first need to convert it into a byte array and invoke the encrypt and decrypt functions introduced above. (The other input key can be generated using the code snippet provided above).

A String can be converted into bytes in the following way:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

从 Java8开始,String在内部使用 UTF-16编码存储在堆中。但是,我们在这里使用了 UTF-8,因为它通常比 UTF-16占用更少的空间,特别是对于 ASCII 字符。

同样,加密的字节数组也可以转换为 String,如下所示:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

这是一个复制/粘贴解决方案。我还建议阅读和投票支持 @ Konstantino 的回答,即使它不提供任何代码。初始向量就像盐一样,不需要保密。我是 GCM 的新手,显然 AAD 是可选的,只在某些情况下使用。把钥匙放在环境变量里。使用类似 留下的东西来生成一个32个字符的密码。这个解决方案是以我的 Ruby 解决方案为模型的。

    public static String encrypt(String s) {
try {
byte[] input = s.getBytes("UTF-8");
String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
if (keyString == null || keyString.length() == 0) {
Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
return null;
}
byte[] keyBytes = keyString.getBytes("UTF-8");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
// generate IV
SecureRandom secureRandom = SecureRandom.getInstanceStrong();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] ivBytes = new byte[cipher.getBlockSize()];
secureRandom.nextBytes(ivBytes);
GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
// generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
// encrypt
byte[] encrypted = cipher.doFinal(input);
byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
return encryptedString;
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
return null;
}
}


public static String decrypt(String s) {
if (s == null || s.length() == 0) return "";
try {
byte[] encrypted = Base64.getDecoder().decode(s);
String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
if (keyString == null || keyString.length() == 0) {
Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
return null;
}
byte[] keyBytes = keyString.getBytes("UTF-8");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] ivBytes = new byte[cipher.getBlockSize()];
System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
String decryptedString = new String(decrypted, "UTF-8");
return decryptedString;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException |
InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
return null;
}
}

这里有一个例子:

    String s = "This is a test.";
String enc = Utils.encrypt(s);
System.out.println(enc);
// fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
String dec = Utils.decrypt(enc);
System.out.println(dec);
// This is a test.