如何从字符串正确地重新创建 SecretKey
How to properly recreate SecretKey from string
我正在尝试制作一个加密解密应用程序。我有两个 classes - 一个具有生成密钥、加密和解密的功能,第二个用于 JavaFX GUI。在 GUI class 我有 4 个文本区域:第一个用于编写要加密的文本,第二个用于加密文本,第三个用于密钥 (String encodedKey = Base64.getEncoder().encodeToString(klucz.getEncoded());
),第四个用于解密文本。
问题是,我无法解密文本。我正在尝试像这样重新创建 SecretKey:
String encodedKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz = new SecretKeySpec(decodedKey, "DESede");
当我加密密钥时如下所示:com.sun.crypto.provider.DESedeKey@4f964d80
当我尝试重新创建它时:javax.crypto.spec.SecretKeySpec@4f964d80
我得到 javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
这是我的第一个 class:
public class Encryption {
public static SecretKey generateKey() throws NoSuchAlgorithmException {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
SecretKey klucz = keygen.generateKey();
return klucz;
}
static byte[] encrypt(byte[] plainTextByte, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, klucz);
byte[] encryptedBytes = cipher.doFinal(plainTextByte);
return encryptedBytes;
}
static byte[] decrypt(byte[] encryptedBytes, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, klucz);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return decryptedBytes;
}
}
编辑
btnEncrypt.setOnAction((ActionEvent event) -> {
try {
String plainText = textAreaToEncrypt.getText();
SecretKey klucz = Encryption.generateKey();
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedBytes = Encryption.encrypt(plainTextByte, klucz);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
textAreaEncryptedText.setText(encryptedText);
byte[] byteKey = klucz.getEncoded();
String stringKey = Base64.getEncoder().encodeToString(byteKey);
textAreaKey.setTextstringKey
} catch (Exception ex) {
ex.printStackTrace();
}
});
btnDecrypt.setOnAction((ActionEvent event) -> {
try {
String stringKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz2 = new SecretKeySpec(decodedKey, "DESede");
String encryptedText = textAreaEncryptedText.getText();
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
byte[] decryptedBytes = Encryption.decrypt(encryptedBytes, klucz2;
String decryptedText = Base64.getEncoder().encodeToString(decryptedBytes);
textAreaDecryptedText.setText(decryptedText);
} catch (Exception ex) {
ex.printStackTrace();
}
});
你的问题之一在这里:
String encryptedText = new String(encryptedBytes, "UTF8");
通常,密文中的许多字节序列不是有效的 UTF-8 编码字符。当您尝试创建 String
时,此格式错误的序列将被替换为 "replacement character",然后来自密文的信息将无法挽回地丢失。当您将 String
转换回字节并尝试对其进行解密时,损坏的密文会引发错误。
如果您需要将密文表示为字符串,请使用 base-64 编码,就像您对密钥所做的那样。
另一个主要问题是您没有指定完整的转换。您应该明确指定密码的 "mode" 和 "padding",例如 "DESede/ECB/PKCS5Padding"。
正确的模式将取决于您的任务。 ECB 通常不安全,但更安全的模式会增加一些复杂性,这可能超出您的任务范围。研究您的说明并在必要时与您的老师明确要求。
主要有两个问题:
您不应该使用用户输入的密码作为密钥(它们之间是有区别的)。密钥必须具有特定大小,具体取决于密码(3des 为 16 或 24 字节)
Direct 3DES (DESede) 是一次加密 8 个字节的分组密码。要加密多个块,有一些方法定义了如何正确地做到这一点。它是调用 Block cipher mode.
为了正确加密,您还需要注意一些事情
从密码创建密钥
假设您要使用 DESede (3des)。 密钥必须具有固定大小 - 16 或 24 字节。要从密码正确生成密钥,您应该使用 PBKDF。有些人对"must use"很敏感,但是忽略这一步确实会损害主要使用用户输入密码的加密安全性。
对于 3DES,您可以使用:
int keySize = 16*8;
int iterations = 800000;
char[] password = "password".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = random.generateSeed(8);
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(spec);
SecretKey desSecret = new SecretKeySpec(pbeSecretKey.getEncoded(), "DESede");
// iv needs to have block size
// we will use the salt for simplification
IvParameterSpec ivParam = new IvParameterSpec(salt);
Cipher cipher = Cipher.getInstance("DESEde/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desSecret, ivParam);
System.out.println("salt: "+Base64.getEncoder().encodeToString(salt));
System.out.println(cipher.getIV().length+" iv: "+Base64.getEncoder().encodeToString(cipher.getIV()));
byte[] ciphertext = cipher.doFinal("plaintext input".getBytes());
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(ciphertext));
如果您可以确保您的密码具有良好的熵(足够长且足够随机),您可以使用简单的散列
MessageDigest dgst = MessageDigest.getInstance("sha-1");
byte[] hash = dgst.digest("some long, complex and random password".getBytes());
byte[] keyBytes = new byte[keySize/8];
System.arraycopy(hash, 0, keyBytes, 0, keySize/8);
SecretKey desSecret = new SecretKeySpec(keyBytes, "DESede");
salt 用于随机化输出,应该使用。
加密的输出应该是 salt | cipthertext | tag
(不一定按此顺序,但您需要所有这些才能正确加密)。
要解密输出,您需要将输出拆分为盐、密文和标签。
我在 Whosebug 的示例中经常看到零向量(静态 salt 或 iv),但在许多情况下,它可能会导致破译的密码泄露密钥或明文。
区块链模式需要初始化向量 iv(加密比单个块更长的输入),我们可以使用盐也来自钥匙
当具有相同的大小时(在我们的例子中为 8 个字节)。对于真正安全的解决方案,密码盐应该更长。
该标签为认证标签,确保密文未被任何人篡改。您可以使用明文或密文的 HMAC。重要的是,您应该为 HMAC 使用与加密不同的密钥。但是 - 我相信在你的情况下,即使没有 hmac 标签,你的家庭作业也可以
我正在尝试制作一个加密解密应用程序。我有两个 classes - 一个具有生成密钥、加密和解密的功能,第二个用于 JavaFX GUI。在 GUI class 我有 4 个文本区域:第一个用于编写要加密的文本,第二个用于加密文本,第三个用于密钥 (String encodedKey = Base64.getEncoder().encodeToString(klucz.getEncoded());
),第四个用于解密文本。
问题是,我无法解密文本。我正在尝试像这样重新创建 SecretKey:
String encodedKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz = new SecretKeySpec(decodedKey, "DESede");
当我加密密钥时如下所示:com.sun.crypto.provider.DESedeKey@4f964d80
当我尝试重新创建它时:javax.crypto.spec.SecretKeySpec@4f964d80
我得到 javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
这是我的第一个 class:
public class Encryption {
public static SecretKey generateKey() throws NoSuchAlgorithmException {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
SecretKey klucz = keygen.generateKey();
return klucz;
}
static byte[] encrypt(byte[] plainTextByte, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, klucz);
byte[] encryptedBytes = cipher.doFinal(plainTextByte);
return encryptedBytes;
}
static byte[] decrypt(byte[] encryptedBytes, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, klucz);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return decryptedBytes;
}
}
编辑
btnEncrypt.setOnAction((ActionEvent event) -> {
try {
String plainText = textAreaToEncrypt.getText();
SecretKey klucz = Encryption.generateKey();
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedBytes = Encryption.encrypt(plainTextByte, klucz);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
textAreaEncryptedText.setText(encryptedText);
byte[] byteKey = klucz.getEncoded();
String stringKey = Base64.getEncoder().encodeToString(byteKey);
textAreaKey.setTextstringKey
} catch (Exception ex) {
ex.printStackTrace();
}
});
btnDecrypt.setOnAction((ActionEvent event) -> {
try {
String stringKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz2 = new SecretKeySpec(decodedKey, "DESede");
String encryptedText = textAreaEncryptedText.getText();
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
byte[] decryptedBytes = Encryption.decrypt(encryptedBytes, klucz2;
String decryptedText = Base64.getEncoder().encodeToString(decryptedBytes);
textAreaDecryptedText.setText(decryptedText);
} catch (Exception ex) {
ex.printStackTrace();
}
});
你的问题之一在这里:
String encryptedText = new String(encryptedBytes, "UTF8");
通常,密文中的许多字节序列不是有效的 UTF-8 编码字符。当您尝试创建 String
时,此格式错误的序列将被替换为 "replacement character",然后来自密文的信息将无法挽回地丢失。当您将 String
转换回字节并尝试对其进行解密时,损坏的密文会引发错误。
如果您需要将密文表示为字符串,请使用 base-64 编码,就像您对密钥所做的那样。
另一个主要问题是您没有指定完整的转换。您应该明确指定密码的 "mode" 和 "padding",例如 "DESede/ECB/PKCS5Padding"。
正确的模式将取决于您的任务。 ECB 通常不安全,但更安全的模式会增加一些复杂性,这可能超出您的任务范围。研究您的说明并在必要时与您的老师明确要求。
主要有两个问题:
您不应该使用用户输入的密码作为密钥(它们之间是有区别的)。密钥必须具有特定大小,具体取决于密码(3des 为 16 或 24 字节)
Direct 3DES (DESede) 是一次加密 8 个字节的分组密码。要加密多个块,有一些方法定义了如何正确地做到这一点。它是调用 Block cipher mode.
为了正确加密,您还需要注意一些事情
从密码创建密钥
假设您要使用 DESede (3des)。 密钥必须具有固定大小 - 16 或 24 字节。要从密码正确生成密钥,您应该使用 PBKDF。有些人对"must use"很敏感,但是忽略这一步确实会损害主要使用用户输入密码的加密安全性。
对于 3DES,您可以使用:
int keySize = 16*8;
int iterations = 800000;
char[] password = "password".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = random.generateSeed(8);
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(spec);
SecretKey desSecret = new SecretKeySpec(pbeSecretKey.getEncoded(), "DESede");
// iv needs to have block size
// we will use the salt for simplification
IvParameterSpec ivParam = new IvParameterSpec(salt);
Cipher cipher = Cipher.getInstance("DESEde/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desSecret, ivParam);
System.out.println("salt: "+Base64.getEncoder().encodeToString(salt));
System.out.println(cipher.getIV().length+" iv: "+Base64.getEncoder().encodeToString(cipher.getIV()));
byte[] ciphertext = cipher.doFinal("plaintext input".getBytes());
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(ciphertext));
如果您可以确保您的密码具有良好的熵(足够长且足够随机),您可以使用简单的散列
MessageDigest dgst = MessageDigest.getInstance("sha-1");
byte[] hash = dgst.digest("some long, complex and random password".getBytes());
byte[] keyBytes = new byte[keySize/8];
System.arraycopy(hash, 0, keyBytes, 0, keySize/8);
SecretKey desSecret = new SecretKeySpec(keyBytes, "DESede");
salt 用于随机化输出,应该使用。
加密的输出应该是 salt | cipthertext | tag
(不一定按此顺序,但您需要所有这些才能正确加密)。
要解密输出,您需要将输出拆分为盐、密文和标签。
我在 Whosebug 的示例中经常看到零向量(静态 salt 或 iv),但在许多情况下,它可能会导致破译的密码泄露密钥或明文。
区块链模式需要初始化向量 iv(加密比单个块更长的输入),我们可以使用盐也来自钥匙 当具有相同的大小时(在我们的例子中为 8 个字节)。对于真正安全的解决方案,密码盐应该更长。
该标签为认证标签,确保密文未被任何人篡改。您可以使用明文或密文的 HMAC。重要的是,您应该为 HMAC 使用与加密不同的密钥。但是 - 我相信在你的情况下,即使没有 hmac 标签,你的家庭作业也可以