ant*_*abo 136 java encryption
我需要的是加密将显示在二维条码中的字符串(PDF-417),这样当有人想要扫描它时就不会有任何可读性.
其他需求:
它必须足够简单,以摆脱窥探的人,并容易解密其他有兴趣获取数据的公司.他们打电话给我们,我们告诉他们标准或给他们一些简单的密钥,然后可以用于解密.
可能这些公司可以使用不同的技术,因此坚持一些与某些特殊平台或技术无关的标准会更好.
你有什么建议?是否有一些Java类在执行高安全性标准时没有太多的复杂性来执行encrypt()decrypt()?
Kon*_*kis 122
这是第一个通过Google显示的页面,所有实现中的安全漏洞让我感到畏缩,因此我发布此信息是为了添加有关加密的信息,因为它距离原始帖子已有7年了.我拥有计算机工程硕士学位,并花了很多时间学习和学习密码学,所以我投入了两分钱让互联网变得更加安全.
另外,请注意,对于给定的情况,许多实现可能是安全的,但为什么要使用这些实现并且可能意外地犯了错误?除非您有特殊原因,否则请使用您可用的最强大的工具.总的来说,我强烈建议您使用图书馆并尽可能远离细节.
更新4/5/18:我重写了一些部分以使它们更容易理解并将推荐的库从Jasypt更改为Google的新库Tink,我建议从现有设置中完全删除Jasypt.
前言
我将在下面概述安全对称加密的基础知识,并指出当人们使用标准Java库自己实现加密时我在网上看到的常见错误.如果您想跳过所有详细信息,请将其转移到Google的新库中,将其导入项目并使用AES-GCM模式进行所有加密,您应该是安全的.
现在,如果你想了解如何在java中加密的细节,请阅读:)
分组密码
首先,您需要选择对称密钥块密码.分组密码是用于创建伪随机性的计算机功能/程序.伪随机性是伪随机性,除了量子计算机之外,没有任何计算机能够区分它与真实的随机性.Block Cipher就像加密的构建块,当与不同的模式或方案一起使用时,我们可以创建加密.
现在关于今天可用的分组密码算法,请确保永远不要,我再说不要使用DES,我甚至会说不要使用3DES.即使Snowden的NSA版本能够验证尽可能接近伪随机的唯一Block Cipher是AES 256.还存在AES 128,区别在于AES 256在256位块中工作,而AES 128在128块中工作.总而言之,虽然已经发现了一些弱点,但AES 128被认为是安全的,但256就是它的稳固性.
有趣的事实DES在最初成立时被NSA打破了,并且实际上保密了几年,虽然有些人仍然声称3DES是安全的,但是有相当多的研究论文发现并分析了3DES的弱点.
加密模式
当您使用分组密码并使用特定方案时,会创建加密,以便随机性与密钥组合,以便在您知道密钥时创建可逆的内容.这被称为加密模式.
以下是加密模式和最简单模式(称为ECB)的示例,以便您可以直观地了解发生的情况:
您最常见的在线加密模式如下:
ECB CTR,CBC,GCM
除了列出的模式之外还存在其他模式,研究人员总是致力于新模式以改善现有问题.
现在让我们继续讨论实现和什么是安全的.永远不要使用ECB这对于隐藏重复数据是不好的,如着名的Linux企鹅所示.
在Java中实现时,请注意,如果使用以下代码,则默认设置ECB模式:
Cipher cipher = Cipher.getInstance("AES");
Run Code Online (Sandbox Code Playgroud)
......危险这是一个脆弱性!不幸的是,这在整个StackOverflow和在线教程和示例中都可以看到.
Nonce和IVs
针对ECB模式中发现的问题,已经创建了nounce,也称为IV.我们的想法是生成一个新的随机变量并将其附加到每个加密,这样当您加密两个相同的消息时,它们就会变得不同.这背后的美丽是IV或nonce是公共知识.这意味着攻击者可以访问此权限,但只要他们没有您的密钥,他们就无法利用这些知识做任何事情.
我将看到的常见问题是人们会将IV设置为静态值,就像在代码中使用相同的固定值一样.当你重复一次实际上危及加密的整个安全性时,这是IV的陷阱.
生成随机IV
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
Run Code Online (Sandbox Code Playgroud)
注意: SHA1已经坏了,但我找不到如何正确地将SHA256实现到这个用例中,所以如果有人想对此进行破解并更新它将会很棒!此外,SHA1攻击仍然是非传统的,因为它可能需要几年时间才能破解庞大的群集.在这里查看详细信息.
点击率实施
CTR模式不需要填充.
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
Run Code Online (Sandbox Code Playgroud)
CBC实施
如果您选择使用PKCS7Padding实现CBC模式,请执行以下操作:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Run Code Online (Sandbox Code Playgroud)
CBC和点击率漏洞以及您应该如何使用GCM
虽然其他一些模式(如CBC和CTR)是安全的,但它们遇到的问题是攻击者可以翻转加密数据,在解密时更改其值.因此,假设您加密了一个假想的银行消息"卖100",您的加密消息看起来像这样的"eu23ng"攻击者将一位改为"eu53ng"并且在解密您的消息时突然变为"卖出900".
为避免这种情况,大多数互联网使用GCM,每次看到HTTPS时,他们都可能使用GCM.GCM使用散列对加密消息进行签名,并检查以使用此签名验证消息是否未更改.
由于其复杂性,我会避免实施GCM.你最好使用谷歌新的图书馆Tink,因为如果你不小心重复一个IV,你就会在GCM的情况下妥协你的密钥,这是最终的安全漏洞.新的研究人员正致力于IV重复抗性加密模式,即使你重复IV,钥匙也没有危险,但这尚未成为主流.
现在,如果你想要实现GCM,这里有一个很好的GCM实现的链接.但是,我无法确保安全性,或者它是否正确实施但是它得到了基础.另请注意,GCM没有填充.
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
Run Code Online (Sandbox Code Playgroud)
密钥与密码
另一个非常重要的注意事项是,当涉及到加密时,密钥和密码不是一回事.密码学中的密钥需要具有一定量的熵和随机性才能被认为是安全的.这就是为什么您需要确保使用适当的加密库为您生成密钥的原因.
所以你真的可以在这里做两个实现,第一个是使用这个StackOverflow线程上的代码进行随机密钥生成.此解决方案使用安全随机数生成器从头开始创建您可以使用的密钥.
另一个不太安全的选项是使用用户输入,例如密码.我们讨论的问题是密码没有足够的熵,所以我们必须使用PBKDF2,这是一种获取密码并加强密码的算法.这是我喜欢的StackOverflow实现.但是谷歌Tink库已经内置了所有这些,你应该利用它.
Android开发者
需要指出的一点是要知道你的android代码是反向可操作的,大多数情况下大多数java代码也是如此.这意味着如果您在代码中以纯文本格式存储密码.黑客可以轻松检索它.通常,对于这些类型的加密,您需要使用非对称加密等.这超出了这篇文章的范围,所以我将避免潜入它.
2013年的有趣读物:指出Android中88%的Crypto实现都是不正确的.
最后的想法
我再次建议避免直接实现加密的java库并使用Google Tink,它会让你头疼,因为他们已经真正做好了正确实现所有算法的工作.即使这样,也要确保你查看Tink github上提出的问题,这里和那里都会出现漏洞.
如果您有任何问题或反馈,请随时发表评论!安全性总是在变化,你需要尽力跟上它:)
Voi*_*ter 111
我建议使用一些标准的对称密码,它可以广泛使用,如DES,3DES或AES.虽然这不是最安全的算法,但是有很多实现,您只需要将密钥交给任何应该解密条形码中信息的人.javax.crypto.Cipher是你想要在这里工作的.
我们假设要加密的字节在
byte[] input;
Run Code Online (Sandbox Code Playgroud)
接下来,您将需要密钥和初始化向量字节
byte[] keyBytes;
byte[] ivBytes;
Run Code Online (Sandbox Code Playgroud)
现在,您可以为您选择的算法初始化密码:
// 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");
Run Code Online (Sandbox Code Playgroud)
加密会像这样:
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);
Run Code Online (Sandbox Code Playgroud)
和解密这样:
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);
Run Code Online (Sandbox Code Playgroud)
ant*_*abo 21
警告
不要将其用作某种安全措施.
此帖子中的加密机制是一次性密码,这意味着攻击者可以使用2个加密消息轻松恢复密钥.XOR 2加密消息,你得到了密钥.那很简单!
穆萨指出
我正在使用Sun的Base64Encoder/Decoder,它可以在Sun的JRE中找到,以避免在lib中使用另一个JAR.从使用OpenJDK或其他一些JRE开始,这很危险.除此之外,还有另一个原因我应该考虑使用带有编码器/解码器的Apache commons lib吗?
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
Run Code Online (Sandbox Code Playgroud)
she*_*rif 11
谢谢香港专业教育学院使用你的代码创建这个类可能有人发现它userfull
对象套管
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();
}
}
Run Code Online (Sandbox Code Playgroud)
您可以使用Jasypt
With Jasypt, encrypting and checking a password can be as simple as...
StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);
Run Code Online (Sandbox Code Playgroud)
Encryption:
String myEncryptedText = textEncryptor.encrypt(myText);
Run Code Online (Sandbox Code Playgroud)
Decryption:
String plainText = textEncryptor.decrypt(myEncryptedText);
Run Code Online (Sandbox Code Playgroud)
Gradle:
compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'
Run Code Online (Sandbox Code Playgroud)
Features:
Jasypt provides you with easy unidirectional (digest) and bidirectional encryption techniques.
Open API for use with any JCE provider, and not only the default Java VM one. Jasypt can be easily used with well-known providers like Bouncy Castle. Learn more.
Higher security for your users' passwords. Learn more.
Binary encryption support. Jasypt allows the digest and encryption of binaries (byte arrays). Encrypt your objects or files when needed (for being sent over the net, for example).
Number encryption support. Besides texts and binaries, it allows the digest and encryption of numeric values (BigInteger and BigDecimal, other numeric types are supported when encrypting for Hibernate persistence). Learn more.
Completely thread-safe.
Support for encryptor/digester pooling, in order to achieve high performance in multi-processor/multi-core systems.
Includes a lightweight ("lite") version of the library for better manageability in size-restrictive environments like mobile platforms.
Provides both easy, no-configuration encryption tools for users new to encryption, and also highly configurable standard encryption tools, for power-users.
Hibernate 3 and 4 optional integration for persisting fields of your mapped entities in an encrypted manner. Encryption of fields is defined in the Hibernate mapping files, and it remains transparent for the rest of the application (useful for sensitive personal data, databases with many read-enabled users...). Encrypt texts, binaries, numbers, booleans, dates... Learn more.
Seamlessly integrable into a Spring application, with specific integration features for Spring 2, Spring 3.0 and Spring 3.1. All the digesters and encryptors in jasypt are designed to be easily used (instantiated, dependency-injected...) from Spring. And, because of their being thread-safe, they can be used without synchronization worries in a singleton-oriented environment like Spring. Learn more: Spring 2, Spring 3.0, Spring 3.1.
Spring Security (formerly Acegi Security) optional integration for performing password encryption and matching tasks for the security framework, improving the security of your users' passwords by using safer password encryption mechanisms and providing you with a higher degree of configuration and control. Learn more.
Provides advanced functionality for encrypting all or part of an application's configuration files, including sensitive information like database passwords. Seamlessly integrate encrypted configuration into plain, Spring-based and/or Hibernate-enabled applications. Learn more.
Provides easy to use CLI (Command Line Interface) tools to allow developers initialise their encrypted data and include encryption/decryption/digest operations in maintenance tasks or scripts. Learn more.
Integrates into Apache Wicket, for more robust encryption of URLs in your secure applications.
Comprehensive guides and javadoc documentation, to allow developers to better understand what they are really doing to their data.
强大的字符集支持,旨在充分加密和摘要文本,无论原始字符集是什么。完全支持日语,韩语,阿拉伯语等语言,没有编码或平台问题。
很高级别的配置功能:开发人员可以实施一些技巧,例如,指示“加密器”向远程HTTPS服务器询问要用于加密的密码。它可以满足您的安全需求。
这个怎么样:
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;
}
Run Code Online (Sandbox Code Playgroud)
对我来说很好,而且相当紧凑.
小智 5
这是我从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;
// }
}
Run Code Online (Sandbox Code Playgroud)
在这里,只有一个简单的解决方案java.*,并javax.crypto.*依赖于提供字节加密保密性和完整性。对于千字节级的短消息,在选择性明文攻击下应是不可区分的。
它AES在GCM没有填充的模式下使用,一个 128 位的密钥是通过PBKDF2大量迭代和提供的密码中的静态盐派生出来的。这确保了暴力破解密码很困难,并将熵分布在整个密钥上。
生成随机初始化向量 (IV) 并将其添加到密文中。此外,静态字节0x01作为第一个字节作为“版本”。
整个消息进入由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 {
// 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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里有一个很好的 CLI 的整个项目:https : //github.com/trichner/tcrypt
编辑:现在有适当的encryptString和decryptString
考虑到以下几点,这是我刚刚在Java 8中编写的加密和解密代码。希望有人会发现这个有用:
加密算法:具有256位密钥的分组密码AES被认为足够安全。要加密完整的消息,需要选择一种模式。建议使用经过身份验证的加密(同时提供机密性和完整性)。GCM,CCM和EAX是最常用的经过身份验证的加密模式。GCM通常是首选,它在为GCM提供专用指令的Intel体系结构中表现良好。所有这三种模式都是基于CTR(基于计数器)的模式,因此它们不需要填充。因此,它们不容易受到与填充相关的攻击
GCM需要初始化向量(IV)。IV不是秘密。唯一的要求是它必须是随机的或不可预测的。在Java中,SecuredRandom该类旨在产生密码学上强的伪随机数。可以在该getInstance()方法中指定伪随机数生成算法。但是,从Java 8开始,推荐的方法是使用getInstanceStrong()将使用由Java 配置和提供的最强算法的方法。Provider
NIST建议GCM使用96位IV,以提高互操作性,效率和简化设计
为了确保额外的安全性,在下面的实现中SecureRandom会在产生每2 ^ 16字节的伪随机字节生成后重新播种
接收者需要知道IV才能解密密文。因此,IV需要与密文一起传输。一些实现将IV作为AD(关联数据)发送,这意味着将在密文和IV上计算身份验证标签。但是,这不是必需的。IV可以简单地在前面加上密文,因为如果IV在传输过程中由于有意的攻击或网络/文件系统错误而被更改,那么身份验证标签的验证将始终失败。
字符串不可用于保存明文消息或密钥,因为字符串是不可变的,因此我们无法在使用后清除它们。这些未清除的字符串随后会在内存中徘徊,并可能显示在堆转储中。出于相同的原因,调用这些加密或解密方法的客户端应在不再需要它们时清除包含消息或密钥的所有变量或数组。
遵循一般建议,没有提供者在代码中被硬编码
最后,为了通过网络或存储进行传输,应使用Base64编码对密钥或密文进行编码。Base64的详细信息可以在这里找到。应该遵循Java 8方法
字节数组可以使用以下方法清除:
Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);
Run Code Online (Sandbox Code Playgroud)
但是,从Java 8开始,没有简单的方法可以清除,SecretKeyspec并且SecretKey由于这两个接口的实现似乎尚未实现destroy()该接口的方法Destroyable。在下面的代码中,编写了一个单独的方法来清除SecretKeySpec和SecretKey使用反射。
密钥应使用以下两种方法之一生成。
请注意,密钥是像密码一样的秘密,但是与供人使用的密码不同,密钥是供加密算法使用的,因此只能使用上述方式生成。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
加密密钥主要可以通过两种方式生成:
没有任何密码
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);
Run Code Online (Sandbox Code Playgroud)带密码
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);
Run Code Online (Sandbox Code Playgroud)正如@MaartenBodewes所指出的那样,我的回答没有String按照问题的要求进行处理。因此,我会尽力弥补这一空白,以防万一有人偶然发现了这个答案而对处理感到疑惑String。
如答案前面所述,在a中处理敏感信息String通常不是一个好主意,因为它String是不可变的,因此在使用后我们无法清除它。并且我们知道,即使当a String没有强引用时,垃圾收集器也不会立即急于将其从堆中删除。因此,String即使程序无法访问该持续时间,该持续时间仍在内存中存在一个未知的时间窗口。问题在于,在该时间段内进行堆转储会泄露敏感信息。因此,始终最好处理字节数组或char数组中的所有敏感信息,然后在达到目的后将其填充为0。
但是,尽管有了这些知识,但是如果我们仍然遇到要加密的敏感信息位于的情况String,则首先需要将其转换为字节数组,然后调用上面介绍的encrypt和decrypt函数。(另一个输入键可以使用上面提供的代码段生成)。
String可以通过以下方式将A 转换为字节:
byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);
Run Code Online (Sandbox Code Playgroud)
从Java 8开始,String内部通过UTF-16编码存储在堆中。但是,我们UTF-8这里使用的是因为它通常比占用更少的空间UTF-16,尤其是对于ASCII字符。
同样,加密的字节数组也可以转换为字符串,如下所示:
String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
362095 次 |
| 最近记录: |