如何在 Java 中加密字符串

2022-08-31 06:47:21

我需要的是加密字符串,该字符串将显示在2D条形码(PDF-417)中,因此当有人获得扫描的想法时,它将无法获得任何可读性。

其他要求:

  • 应该不复杂
  • 它不应由 RSA、PKI 基础结构、密钥对等组成。

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

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

你有什么建议?有没有一些Java类在实现高安全标准方面没有太多的复杂性?encrypt()decrypt()


答案 1

这是通过Google显示的第一个页面,所有实现中的安全漏洞都让我畏缩,所以我发布此内容是为了添加有关其他人加密的信息,因为它已经从原始帖子开始7年了。我拥有计算机工程硕士学位,花了很多时间学习和学习密码学,所以我投入了两分钱,使互联网成为一个更安全的地方。

另外,请注意,对于给定的情况,许多实现可能是安全的,但是为什么要使用这些实现并且可能意外地犯错误呢?使用您拥有的最强大的工具,除非您有特定原因不这样做。总的来说,我强烈建议使用图书馆,如果可以的话,远离细节。

2018年4月5日更新:我重写了一些部分,使它们更容易理解,并将推荐的库从Jasypt更改为Google的新库Tink,我建议从现有设置中完全删除Jasypt

前言

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

现在,如果您想了解有关如何在java中加密的细节,请阅读:)

分组密码

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

现在关于今天可用的块密码算法,确保永远不要,我重复一遍永远不要使用DES,我甚至会说永远不要使用3DES。即使是斯诺登的NSA版本也能够验证是否真正接近伪随机的唯一块密码是AES 256。还存在AES 128;不同之处在于AES 256在256位块中工作,而AES 128在128个块中工作。总而言之,AES 128被认为是安全的,尽管已经发现了一些弱点,但256是可靠的。

有趣的是,DES在最初成立时就被NSA打破了,实际上保守了几年的秘密。虽然有些人仍然声称3DES是安全的,但有相当多的研究论文发现并分析了3DES的弱点。

加密模式

当您采用块密码并使用特定方案时,将创建加密,以便将随机性与密钥相结合,以创建只要您知道密钥即可可逆的内容。这称为加密模式。

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

ECB Mode

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

欧洲央行点击率、加拿大广播公司、通用关税壁垒

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

现在,让我们继续讨论实现以及什么是安全的。永远不要使用ECB,这很难隐藏重复的数据,如著名的Linux企鹅所示。Linux Penguin Example

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

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

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

随机数和 IV

为了响应ECB模式发现的问题,创建了也称为IV的节点。我们的想法是,我们生成一个新的随机变量并将其附加到每个加密中,以便当您加密两个相同的消息时,它们会有所不同。这背后的美妙之处在于,静脉注射或随机数是众所周知的。这意味着攻击者可以访问它,但只要他们没有您的密钥,他们就无法利用这些知识做任何事情。

我将看到的常见问题是,人们会将IV设置为静态值,就像在代码中的相同固定值中一样。这是IV的陷阱,当你重复一个时,你实际上损害了加密的整个安全性。

生成随机 IV

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

注意:SHA1已损坏,但我找不到如何正确将SHA256实现到此用例中,因此,如果有人想对此进行破解并进行更新,那将是很棒的!此外,SHA1攻击仍然是非常规的,因为在一个巨大的集群上可能需要几年的时间才能破解。查看详情 请点击此处。.

点击率的实施

点击率模式不需要填充。

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

全息投资实施

如果选择实现 CBC 模式,请使用 PKCS7Padding 执行此操作,如下所示:

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

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

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

为了避免这种情况,大多数互联网都使用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代码是可逆向工程的,大多数情况下,大多数Java代码也是。这意味着,如果您在代码中以纯文本形式存储密码。黑客可以轻松检索它。通常,对于这些类型的加密,您希望使用非对称加密等。这超出了本文的范围,因此我将避免深入研究它。

2013年的一个有趣的解读:指出Android中88%的Crypto实现都是不正确完成的。

最后的思考

我再次建议避免直接实现加密的java库,并使用Google Tink,这将为您省去头痛,因为他们确实在正确实现所有算法方面做得很好。即使这样,也要确保你检查了Tink github上提出的问题,漏洞在这里和那里弹出。

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


答案 2

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

推荐