如何在Java中加密字符串


150

我需要加密的字符串会显示在2D条码中(PDF-417),所以当有人知道扫描的想法时,它就不会可读。

其他需求:

  • 应该不复杂
  • 它不应包含RSA,PKI基础结构,密钥对等。

它必须足够简单,以摆脱被监视的人,并且必须易于对其他有兴趣获取该数据的公司解密。他们打电话给我们,我们告诉他们标准,或者给他们一些简单的密钥,然后可以将其用于解密。

那些公司可能会使用不同的技术,因此最好坚持不依赖于某些特殊平台或技术的某些标准。

你有什么建议?有一些Java类做encrypt()decrypt()没有太多的并发症,实现高安全标准?



警告。下面的许多答案显示了一种在Java上执行任何类型的加密的方法。答案可能无法反映出良好的密码惯例,并且可能无法得到很好的审查;没有复制/粘贴安全性之类的东西。答案至少考虑字符串转换。包含2D条形码的实际问题范围太广,应该要求客户特定的解决方案。
Maarten Bodewes

Answers:


156

这是通过Google显示的第一页,所有实施中的安全漏洞使我感到畏缩,所以我将其发布为添加有关他人加密的信息,因为距原始帖子已有7年了。我拥有计算机工程硕士学位,并花了大量时间学习和学习密码学,因此,我花了两分钱使互联网成为一个更安全的地方。

另外,请注意,在给定的情况下,很多实现可能是安全的,但是为什么要使用这些实现并可能会偶然出错呢?除非有特殊原因,否则请使用最强大的工具。总的来说,我强烈建议您使用图书馆,并尽量避免使用细节。

UPDATE 4/5/18:我重写了一些部分,以使它们更易于理解,并将推荐的库从Jasypt更改Google的新库Tink,我建议从现有设置中完全删除Jasypt

前言

我将在下面概述安全对称加密的基础知识,并指出人们在使用标准Java库自行实现加密时在网上看到的常见错误。如果您只想跳过所有详细信息,请运行到Google的新库Tink中,将其导入到您的项目中,并使用AES-GCM模式进行所有加密,那么您将是安全的。

现在,如果您想学习有关如何在Java中进行加密的详细信息,请继续阅读:)

分组密码

首先,您需要选择一个对称密钥Block Cipher。分组密码是用于创建伪随机性的计算机功能/程序。伪随机性是伪随机性,除Quantum Computer以外,其他任何计算机都无法分辨出它与真实随机性之间的区别。块密码就像是加密的基础,当与不同的模式或方案一起使用时,我们可以创建加密。

现在,关于分组密码算法可利用的今天,一定请对NEVER,我再说一遍从未使用DES,我甚至会说永远使用3DES。甚至斯诺登(Snowden)的NSA发行版也能够验证其真正接近伪随机的唯一块密码是AES 256。还存在AES 128。区别在于AES 256适用于256位块,而AES 128适用于128个块。总而言之,尽管发现了一些弱点,但AES 128被认为是安全的,但是256确实可靠。

有趣的事实DES在最初成立时就被NSA打破了,实际上已经保密了几年。尽管仍然有人认为3DES是安全的,但仍有许多研究论文发现并分析了3DES的弱点。

加密方式

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

这是加密模式和最简单的模式(称为ECB)的示例,以便您可以直观地了解正在发生的情况:

ECB模式

您最常在网上看到的加密模式如下:

欧洲央行CTR,CBC,GCM

除列出的模式外,还有其他模式,研究人员一直在努力寻找新模式以改善现有问题。

现在让我们继续进行实施以及什么是安全的。永远不要使用ECB,这很不利于隐藏重复的数据,如著名的Linux企鹅所示Linux企鹅示例

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

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

危险这是一个漏洞!不幸的是,这可以在StackOverflow上看到,也可以在教程和示例中在线看到。

随机数和IV

针对ECB模式下发现的问题,创建了名词(也称为IV)。这个想法是,我们生成一个新的随机变量,并将其附加到每个加密中,这样,当您加密两个相同的消息时,它们的结果就会不同。其背后的美在于,IV或现时是公共知识。这意味着攻击者可以访问此文件,但是只要他们没有您的密钥,他们就无法使用该知识执行任何操作。

我将看到的常见问题是,人们会将IV设置为与代码中相同的固定值相同的静态值。这是IV重复出现的陷阱,实际上您危及了加密的整个安全性。

生成随机IV

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
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模式,请使用PKCS7Padding来实现,如下所示:

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

CBC和CTR漏洞以及为什么要使用GCM

尽管诸如CBC和CTR之类的其他一些模式是安全的,但它们仍会遇到攻击者可以翻转加密数据,在解密时更改其值的问题。假设您加密了一个虚拟的银行消息“ Sell 100”,加密的消息看起来像是“ eu23ng”,攻击者将一位更改为“ eu53ng”,解密后突然变成了“ Sell 900”。

为了避免这种情况,大多数Internet使用GCM,并且每次您看到HTTPS时,它们可能都在使用GCM。GCM使用散列对加密的邮件进行签名,并使用此签名检查以确认该邮件尚未更改。

由于其复杂性,我将避免实施GCM。您最好使用Google的新库Tink,因为如果您不小心重复一次IV,那么在此情况下,您正在破坏GCM的密钥,这是最终的安全漏洞。新的研究人员正在致力于IV重复抗性加密模式,即使您重复IV,密钥也没有危险,但这尚未成为主流。

现在,如果您确实想实现GCM,下面是一个不错的GCM实现链接。但是,我不能保证安全性,也不能保证其正确实施,但是它会使基础降低。另请注意,对于GCM,没有填充。

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

密钥与密码

另一个非常重要的注意事项是,对于密码学而言,密钥和密码不是同一回事。密码学中的密钥需要具有一定程度的熵和随机性才能被认为是安全的。这就是为什么您需要确保使用正确的密码库为您生成密钥的原因。

因此,您实际上可以在此处实现两种实现,第一种是使用此StackOverflow线程上的代码进行随机密钥生成。该解决方案使用安全的随机数生成器从头开始创建密钥,以供您使用。

另一个不太安全的选项是使用用户输入,例如密码。我们讨论的问题是密码没有足够的熵,因此我们将不得不使用PBKDF2,该算法采用密码并对其进行加强。这是我喜欢StackOverflow实现。但是,Google Tink库内置了所有这些功能,您应该利用它。

Android开发人员

这里要指出的重要一点是,您的android代码是可逆向工程的,大多数情况下大多数Java代码也是可以逆向工程的。这意味着如果您将密码以纯文本格式存储在代码中。黑客可以轻松地将其检索。通常,对于这些类型的加密,您需要使用非对称密码学等等。这超出了本文的范围,因此我将避免深入探讨。

2013年有趣读物:指出Android中88%的Crypto实现未正确完成。

最后的想法

再一次,我建议避免直接为crypto实现Java库,而要使用Google Tink,这将使您免于头痛,因为它们确实很好地实现了所有算法。甚至还要确保您检查了Tink github上出现的问题,漏洞四处弹出。

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


15
这是我见过的最干净的东西。
Seraf '17

1
@SabirKhan可能引起关注,但核心算法仍未中断,因此我不必为此担心。如果您不信任它,也可以查看github.com/google/keyczar,它是由谷歌安全团队开发的。
Konstantino Sparakis

1
@KonstantinoSparakis:如果我没有误解Jasypt的BasicTextEncryptor和StrongTextEncryptor的文档,那么这些类将使用DES3DES进行加密,这正是您告诉读者不要使用的。IMO您应该将给定的代码示例替换为使用Jasypt的StandardPBEStringEncryptor并手动定义要使用的AES算法的代码示例。
xpages-noob '18

1
@ xpages-noob我更新了帖子。我实际上找到了Google Tink,它是最新支持的加密货币库,因此您应该检查一下!
Konstantino Sparakis

2
AES块大小为128位。在AES 256中,密钥大小为256位。同样,AES 192和AES128getInstanceStrong()Cipher
-Saptarshi Basu

110

我建议使用一些广泛使用的标准对称密码,例如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);

9
我可以建议您更新此示例以引用该DESede算法吗?由于这是一个很普遍的问题(和答案),因此鼓励人们使用DES是很可耻的,因为按照当今的标准,该密码是如此微弱。
邓肯·琼斯

javax.crypto.BadPaddingException出了点问题:鉴于最终代码块在抄写时未正确填充
好奇

2
@Duncan确实DES很弱,但是我想AES比DESede(aka TipleDES)更可取:http
//security.stackexchange.com/a/26181/69785

2
应该对此进行更新以具有AES / GCM / NoPadding,DES容易受到暴力攻击,也不建议使用TripleDes
Konstantino Sparakis

1
下面的Konstantino Sparakis的答案比这个答案要好得多。
史蒂夫,

22

警告

不要将此用作某种安全性度量。

这篇文章中的加密机制是一次性垫,这意味着攻击者可以使用2条加密消息轻松恢复密钥。XOR 2加密的消息,您将获得密钥。这么简单!

穆萨指出


我正在使用Sun的JRE中可以找到的Sun的Base64Encoder / Decoder,以避免lib中的另一个JAR。从使用OpenJDK或其他JRE的角度来看,这很危险。除此之外,还有其他原因我应该考虑将Apache commons lib与Encoder / Decoder一起使用吗?

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

1
我还通过sun.misc.BASE64Encoder使用了此解决方案建议,但是当使用相当大的字符串进行编码时,编码器将返回分块字符串(每个字符串76个字符)。然后,我切换到提供非块编码方法的Apache Commons Codec Base64!
basZero 2012年

78
如果使用的加密机制多次使用,则非常危险。这就是为什么它被称为一次性垫。攻击者可以使用2条加密消息轻松恢复密钥。xor 2加密的消息,您就会得到密钥。这么简单!
xtrem 2012年

3
它的想法不是笨拙,只是让人们摆脱尝试阅读PDF-417 2D条形码所写内容的想法。而且无论如何,只有一些索引对任何人都不重要...
ante.sabo 2012年

2
好。只是担心有人将其用作加密机制。
xtrem 2012年

对于Encryption,可以避免编码器(例如BASE64Encoder)受到暴力攻击。
Jagrut Dalwadi '16

13

谢谢香港专业教育学院使用您的代码来制作此类,也许有人觉得它很完整

对象加密器

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

}


}

这里发布了一个相关的问题!
user2023507

难道不是在加密和解密过程中传递differentKeys不应返回文本吗?这似乎没有发生在这里。PS:我正在使用此类的不同对象来执行此测试。
instanceOfObject

6

2019年12月12日更新

与CBC等其他模式不同,GCM模式不需要IV是不可预测的。唯一的要求是,对于具有给定密钥的每次调用,IV必须是唯一的。如果针对给定的密钥重复一次,则可能会损害安全性。一种简单的实现方法是使用来自强伪随机数生成器的随机IV,如下所示。

也可以将序列或时间戳记用作IV,但听起来可能不那么琐碎。例如,如果系统未正确跟踪持久存储中已用作IV的序列,则调用可能会在系统重新引导后重复IV。同样,也没有完美的时钟。电脑时钟重新调整等。

此外,每2 ^ 32次调用后应旋转一次键。有关IV要求的更多详细信息,请参考此答案NIST建议


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

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

  2. GCM需要初始化向量(IV)。IV不是秘密。唯一的要求是它必须是随机的或不可预测的。在Java中,SecuredRandom该类用于产生加密强度高的伪随机数。可以在该getInstance()方法中指定伪随机数生成算法。但是,从Java 8开始,推荐的方法是使用getInstanceStrong()将使用由Java 配置和提供的最强算法的方法。Provider

  3. NIST建议GCM使用96位IV,以提高互操作性,效率和简化设计

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

  5. 接收者需要知道IV才能解密密文。因此,IV需要与密文一起传输。一些实现将IV作为AD(关联数据)发送,这意味着将在密文和IV上计算身份验证标签。但是,这不是必需的。IV可以简单地在前面加上密文,因为如果IV在传输过程中由于故意的攻击或网络/文件系统错误而被更改,那么身份验证标签的验证将始终失败。

  6. 字符串不可用于保存明文消息或密钥,因为字符串是不可变的,因此我们无法在使用后清除它们。这些未清除的字符串随后会在内存中徘徊,并可能显示在堆转储中。出于同样的原因,调用这些加密或解密方法的客户端应在不再需要它们时清除包含消息或密钥的所有变量或数组。

  7. 遵循一般建议,没有提供者在代码中被硬编码

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

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

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

但是,从Java 8开始,没有简单的方法可以清除,SecretKeyspec并且SecretKey由于这两个接口的实现似乎尚未实现destroy()该接口的方法Destroyable。在下面的代码中,编写了一个单独的方法来清除SecretKeySpecandSecretKey使用反射。

密钥应使用以下两种方法之一生成。

请注意,密钥是类似于密码的秘密,但是与供人使用的密码不同,密钥是供加密算法使用的,因此只能使用上述方式生成。

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);
  • 带密码

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

根据评论更新

正如@MaartenBodewes指出的那样,我的回答没有String按照问题的要求进行处理。因此,我会尽力弥补这一空白,以防万一有人偶然发现了这个答案而对处理感到疑惑String

如答案前面所述,在a中处理敏感信息String通常不是一个好主意,因为它String是不可变的,因此我们在使用后无法清除它。并且我们知道,即使当a String没有强引用时,垃圾收集器也不会立即急于将其从堆中删除。就这样String即使程序无法访问持续时间持续时间仍在内存中存在一个未知的时间窗口。问题在于,在该时间段内进行堆转储会泄露敏感信息。因此,始终最好处理字节数组或char数组中的所有敏感信息,然后在达到目的后将其填充为0。

但是,尽管有了这些知识,但是如果我们仍然遇到要加密的敏感信息位于的情况String,我们首先需要将其转换为字节数组,然后调用上面介绍的encryptdecrypt函数。(可以使用上面提供的代码段生成另一个输入键)。

String可以通过以下方式将A 转换为字节:

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

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

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

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

1
尽管我想赞成这个答案,但看起来确实符合当前的加密惯例,但我完全看不到任何字符串处理方式,这使其更像是有关如何使用GCM模式的描述。因此,它无法回答问题
Maarten Bodewes

1
@MaartenBodewes非常感谢您抽出宝贵时间审查和分享反馈。我写这本书的前提是,String使用上面创建的功能对a 进行加密是微不足道的。但是,在阅读您的评论后,我再次了解到它可能并不明显。我一定会进行编辑以添加这些详细信息。
Saptarshi Basu

5

这个怎么样:

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

对我来说效果很好,而且非常紧凑。


如果输入参数secret == null或input == null会发生什么?使用字节而不是字符串是可以的,但是在我的情况下是无关紧要的。唯一重要的是,它必须可以在任何设备上以任何字符编码进行读取和解码...
ante.sabo 2012年

@ ante.sabo显然会抛出NPE。这是与NULL唯一相关的事情。
Miha_x64

只要input.length <= secret.length保留并且secret永远不会重复使用,这就是安全的,称为one-time-pad。在这种情况下,input.length > secret.length这是Vigenère密码的一种变体,被认为非常弱。
trichner

5

您可以使用 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提供程序一起使用,而不仅仅是默认的Java VM。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(命令行界面)工具,以允许开发人员初始化其加密数据,并在维护任务或脚本中包括加密/解密/摘要操作。学到更多。

集成到Apache Wicket中,以便在安全的应用程序中对URL进行更强大的加密。

全面的指南和javadoc文档,使开发人员可以更好地了解他们对数据的实际处理。

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

很高级别的配置功能:开发人员可以实施一些技巧,例如,指示“加密器”向远程HTTPS服务器询问要用于加密的密码。它可以满足您的安全需求。


1
但是Jasypt提供什么安全性呢?我不能从他们的网站上弄清楚。在选择明文攻击下是否无法区分?诚信吗?保密?
trichner '18

4

这是我从meta64.com作为Spring Singleton实现的。如果要为每个调用创建一个密码实例,该实例也将起作用,然后可以删除“同步”调用,但是请注意,“ cipher”不是线程安全的。

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

3
这将以可怕的ECB模式进行加密。您应该至少设置CBC模式或GCM模式
Konstantino Sparakis

感谢Konstantinto的建议,我在Google上进行了搜索,发现了一些使用“ AES / CBC / PKCS5Padding”作为Cipher的Init字符串的代码,而不仅仅是“ AES”,但是我将对其进行更多研究。或者,如果您希望提供实际的修复程序,则其他人可以看到更好的方法。但是,除了CBC的详细信息,我相信我的解决方案是最简单,最安全的,值得优先考虑。

是的,不用担心,加密是一个复杂的话题。可悲的是,此页面上的每个实现都被破坏了,可悲的是,这是使用Google搜索“如何进行Java加密”时弹出的第一页。如果有机会,我将尝试修复所有问题。
Konstantino Sparakis

我的示例与此相同: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto / ... 除了我需要的Cipher.getInstance(“ AES / ECB / PKCS5Padding”); 我的代码假定存在一些属性文件,该文件具有完美的16字节长的加密密钥,但是对于从“用户提供的”密码中加密字符串,oracle页面(上面链接)显示了这样做的方法。

1
因此,ECB的问题在于它极易受到频率分析的影响。有一个著名的Linux企鹅示例,blog.filippo.io / the-ecb- penguin 了解如何加密图像,但仍然可以看出它是企鹅。我继续往下写下关于这个问题的想法:) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis

4

这里有一个简单的解决方案,仅包含字节加密java.*javax.crypto.*依赖性,提供了机密性完整性在选择的明文攻击下,对于千字节级的短消息,它应该是不可区分的

AESGCM没有填充的模式下使用,通过PBKDF2多次迭代和提供的密码中的静态盐衍生了一个128位密钥。这样可以确保强行使用强密码,并在整个密钥上分配熵。

生成一个随机初始化向量(IV),并将其放在密文之前。此外,静态字节0x01作为“版本”作为第一个字节开头。

整个消息进入生成的消息身份验证代码(MAC)AES/GCM

零外部依赖加密类提供了机密性完整性

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 {

    // /crypto/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


这难以置信。谢谢!我从您的代码中学到了很多东西,并且在创建BadVersionException Exception类之后,您的代码在第一次就可以完美地工作了。优秀的!!
Morkus

我喜欢这种尝试。就是说...盐应该是随机的,而不是静态的。迭代也可能不是静态的。GCM已将IV包含在标签的计算中。但是它不包含版本号。您不应该为可移植性指定提供程序,“ SunJCE”将是支持该提供程序的平台上的默认提供程序。此代码不包含任何消息字符串处理,这是此特定问题所必需的。
Maarten Bodewes

好了,我整理了一下,并添加了请求的内容encryptStringdecryptString:)
trichner '18

效果很好;ty的代码。请注意,此代码需要API 19(Kit Kat)或更高版本才能正常运行。
PGMacDesign '19

3

我会考虑使用类似https://www.bouncycastle.org/的东西。它是一个预建的库,可以让您使用许多不同的密码对任何内容进行加密,我知道您只是想防止监听,但是如果您确实想要保护信息,使用Base64并不能真正保护您。


1
仅仅推荐一个带有密码的随机加密库并不能解决这个问题。除此之外,为什么不使用内置密码?
Maarten Bodewes

2

以下是一些链接,您可以阅读Java支持的内容

加密/解密数据流。

此示例演示如何加密(使用对称加密算法,例如AES,Blowfish,RC2、3DES等)大量数据。数据以块的形式传递到加密方法之一:EncryptBytes,EncryptString,EncryptBytesENC或EncryptStringENC。(方法名称指示输入的类型(字符串或字节数组)和返回类型(编码的字符串或字节数组)。FirstChunk和LastChunk属性用于指示数据块是流中的第一个,中间还是最后一个默认情况下,FirstChunk和LastChunk都等于true,这意味着传递的数据是全部数据。

JCERefGuide

Java加密示例


是的,Java支持加密。流的加密也不是要求的。
Maarten Bodewes

2

就像许多人已经说过的那样,您应该使用过度使用的标准密码,例如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!"));

    }
}

CBC不再是安全模式。填充很容易受到填充Oracle攻击的影响。同样,在String中处理键和消息也不安全。它们将在字符串池中徘徊并出现在堆转储中
Saptarshi Basu

2
感谢评论。这是用户要求的Java加密和解密方法的简单示例。这个问题是在9年前提出的,并在此基础上得到了回答。谢谢。
viveknaskar

2
是的,这似乎是引入加密/解密的简单方法。对我来说就像是一种魅力。
Codewrapper

0

这是复制/粘贴解决方案。我也建议阅读@Konstantino的答案并对其投票,即使它没有提供任何代码。初始化向量(IV)就像盐一样-不必保密。我是GCM的新手,显然AAD是可选的,仅在某些情况下使用。在环境变量中设置密钥SECRET_KEY_BASE。使用类似KeePass的东西来生成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.

-4

您可能需要考虑使用一些自动化工具来生成加密/解密代码,例如。 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);

我们一直在公司使用它。


这是默默无闻的安全,并不是真正的安全。
Chloe

这个问题要求的是像AES这样的实际现代加密强度加密,而不仅仅是混淆使字符串更难静态提取。甚至在字符之间似乎都没有保持任何状态,因此容易进行频率分析。(单字母替换密码,除了通过UTF-16代码点而不是拉丁字母。但是,如果在英语ASCII文本上使用它,则只会得到一些唯一的16位字符值,除非我误读了它)
彼得Cordes

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

正式地,它将数据加密为不可读的格式。要解密,请使用相同的代码。并将s [i] * f更改为s [I] / f。
Arshad shaik

这是默默无闻的安全,并不是真正的安全。
Chloe

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

1
是JavaEncryprtionUtil的一部分?如果不是,则应说明库的名称。
费马小学生

4
找不到该课程。感觉答案已经定了。
james.garriss 2015年
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.