Java 256 位 AES 基于密码的加密

2022-08-31 04:36:01

我需要实现256位AES加密,但我在网上找到的所有示例都使用“密钥生成器”来生成256位密钥,但我想使用自己的密钥。如何创建自己的密钥?我尝试将其填充到256位,但后来我收到一个错误,说密钥太长。我确实安装了无限管辖权补丁,所以这不是问题:)

即。密钥生成器看起来像这样...

// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available

// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

代码取自此处

编辑

我实际上将密码填充为256字节,而不是位,这太长了。以下是我现在使用的一些代码,我对此有更多的经验。

byte[] key = null; // TODO
byte[] input = null; // TODO
byte[] output = null;
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(input)

您需要自己做的“待办事项”位:-)


答案 1

与带外接收者共享 (a ) 和 (由 a 选择的 —8 个字节是一个很好的盐 , 不需要保密)。然后从此信息中派生出一个好的密钥:passwordchar[]saltbyte[]SecureRandom

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

幻数(可以在某个地方定义为常量)65536 和 256 分别是关键派生迭代计数和密钥大小。

密钥派生函数被迭代为需要大量的计算工作,这可以防止攻击者快速尝试许多不同的密码。迭代计数可以根据可用的计算资源进行更改。

密钥大小可以减少到128位,这仍然被认为是“强”加密,但如果发现削弱AES的攻击,它不会提供太多的安全裕度。

与适当的区块链模式一起使用时,可以使用相同的派生密钥来加密许多消息。在密码块链接 (CBC) 中,为每个消息生成一个随机初始化向量 (IV),即使纯文本相同,也会生成不同的密码文本。CBC可能不是您可用的最安全的模式(请参阅下面的AEAD);还有许多其他具有不同安全属性的模式,但它们都使用类似的随机输入。在任何情况下,每个加密操作的输出都是密文初始化向量:

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes(StandardCharsets.UTF_8));

存储 和 .在解密时,将以完全相同的方式重新生成,使用具有相同 salt 和迭代参数的密码。使用此密钥初始化密码以及与消息一起存储的初始化向量:ciphertextivSecretKey

/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
System.out.println(plaintext);

Java 7包括对AEAD密码模式的API支持,OpenJDK和Oracle发行版中包含的“SunJCE”提供程序从Java 8开始实现这些。强烈建议使用其中一种模式代替CBC;它将保护数据的完整性及其隐私。


带有消息“非法密钥大小或默认参数”表示加密强度有限;无限强度管辖权策略文件不在正确的位置。在 JDK 中,它们应放在java.security.InvalidKeyException${jdk}/jre/lib/security

根据问题描述,听起来策略文件未正确安装。系统可以很容易地拥有多个Java运行时;仔细检查以确保使用正确的位置。


答案 2

考虑使用Spring安全加密模块

Spring Security Crypto模块支持对称加密,密钥生成和密码编码。该代码作为核心模块的一部分分发,但不依赖于任何其他Spring Security(或Spring)代码。

它为加密提供了一个简单的抽象,并且似乎与这里需要的相匹配,

“标准”加密方法是使用PKCS #5的PBKDF2(基于密码的密钥派生函数#2)的256位AES。此方法需要 Java 6。用于生成密钥的密码应保存在安全的地方,不得共享。盐用于防止在加密数据泄露时对密钥的字典攻击。还应用了 16 字节的随机初始化向量,因此每条加密消息都是唯一的。

看看内部结构,就会发现一个类似于埃里克森答案的结构

如问题中所述,这还需要 Java 加密扩展 (JCE) 无限强度管辖权策略(否则,您将遇到 InvalidKeyException: Illegal Key Size)。它可以下载Java 6Java 7Java 8

用法示例

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class CryptoExample {
    public static void main(String[] args) {
        final String password = "I AM SHERLOCKED";  
        final String salt = KeyGenerators.string().generateKey();
        
        TextEncryptor encryptor = Encryptors.text(password, salt);      
        System.out.println("Salt: \"" + salt + "\"");
        
        String textToEncrypt = "*royal secrets*";
        System.out.println("Original text: \"" + textToEncrypt + "\"");
        
        String encryptedText = encryptor.encrypt(textToEncrypt);
        System.out.println("Encrypted text: \"" + encryptedText + "\"");
        
        // Could reuse encryptor but wanted to show reconstructing TextEncryptor
        TextEncryptor decryptor = Encryptors.text(password, salt);
        String decryptedText = decryptor.decrypt(encryptedText);
        System.out.println("Decrypted text: \"" + decryptedText + "\"");
        
        if(textToEncrypt.equals(decryptedText)) {
            System.out.println("Success: decrypted text matches");
        } else {
            System.out.println("Failed: decrypted text does not match");
        }       
    }
}

和样本输出,

Salt: "feacbc02a3a697b0"
Original text: "*royal secrets*"
Encrypted text: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Decrypted text: "*royal secrets*"
Success: decrypted text matches

推荐