Java 如何从字符串正确地重新创建SecretKey

Java 如何从字符串正确地重新创建SecretKey,java,encryption,secret-key,Java,Encryption,Secret Key,我正在尝试制作一个加密解密应用程序。我有两个类——一个是生成密钥的函数,加密和解密,另一个是JavaFXGUI。在GUI类中,我有4个文本区域:第一个用于编写要加密的文本,第二个用于加密文本,第三个用于密钥字符串encodedKey=Base64.getEncoder.encodeToStringklucz.getEncoded;第四个是解密文本 问题是,我无法解密文本。我正试图像这样重新创建SecretKey: String encodedKey = textAreaKey.getText()

我正在尝试制作一个加密解密应用程序。我有两个类——一个是生成密钥的函数,加密和解密,另一个是JavaFXGUI。在GUI类中,我有4个文本区域:第一个用于编写要加密的文本,第二个用于加密文本,第三个用于密钥字符串encodedKey=Base64.getEncoder.encodeToStringklucz.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:当使用填充密码解密时,输入长度必须是8的倍数

这是我的第一节课:

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


你的问题之一是:

String encryptedText = new String(encryptedBytes, "UTF8");
通常,密文中的许多字节序列都不是有效的UTF-8编码字符。当您尝试创建字符串时,这种格式错误的序列将被替换为替换字符,然后密码文本中的信息将无法挽回地丢失。当您将字符串转换回字节并尝试对其解密时,损坏的密码文本将引发错误

如果需要将密码文本表示为字符串,请使用base-64编码,就像对密钥所做的那样

另一个主要问题是没有指定完整的转换。您应该明确指定密码的模式和填充,如DESede/ECB/PKCS5Padding


正确的模式取决于您的作业。ECB通常不安全,但更安全的模式会增加一点复杂性,这可能超出您的任务范围。研究您的说明,必要时与老师澄清要求。

有两个主要问题:

您不应该使用用户输入的密码作为密钥,因为它们之间存在差异。密钥必须具有特定的大小,具体取决于3des的16或24字节密码

Direct 3DES DESede是一种一次加密8个字节的分组密码。要加密多个块,有一些方法定义了如何正确地进行加密。这是电话

对于正确的加密,您需要处理更多的事情

从密码创建密钥

假设您想使用DESede 3des。密钥必须具有固定大小-16或24字节。要从密码正确生成密钥,您应该使用。有些人对“必须使用”很敏感,但是忽略这一步确实会损害主要使用用户输入密码的加密安全性

对于3DE,您可以使用:

        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 |标记,不一定按此顺序,但您需要所有这些来进行正确的加密

要解密输出,需要将输出拆分为salt、密文和标记

我在StackOverflow的示例中经常看到零向量静态salt或iv,但在许多情况下,它可能会导致密钥或明文的破译密码

块链模式加密比单个块更长的输入时需要初始化向量iv,我们也可以使用密钥中的salt 在我们的例子中,当具有相同大小的8字节时。对于真正安全的解决方案,密码应该更长


标记是一个身份验证标记,以确保没有人使用密文进行操作。您可以使用明文或密文的HMAC。重要的是,您应该为HMAC使用与加密不同的密钥。但是-我相信在你的情况下,即使没有hmac标签,你的家庭作业也可以

我使用了你发布的代码,一字不差,但无法复制。所以问题一定出在你没有发布的代码中。1.为什么要使用3DES DESede,因为它不应该用于需要3DES的新工作、学校项目?2.对于168位密钥com.sun.crypto.provider,3DES密钥为24字节。DESedeKey@4f964d80不是3DES键。虽然3DES密钥为24字节,但一些实现将接受较短的密钥并复制部分以创建24字节密钥。3.为什么要用Base64编码第三个字段,不是文本吗?4.没有提到填充,最好显式指定所有参数。5.密码。加密模式的值是多少?1。是的,这是一个学校项目2。这部分内容是在互联网上的某个地方找到的,因为我不知道如何提供正确的3DES密钥。我应该用什么来代替这个?3.首先,通过加密,我将SecretKey转换成字符串,这样我就可以在textAreaKey中显示它。然后,通过解密,我将文本从textAreaKey转换回问题4中添加的SecretKey代码。我真的不知道你这是什么意思,我在这方面是个新手。5.当我现在试着
Cipher.ENCRYPT\u MODE的tem.out.printlnValue:+Cipher.ENCRYPT\u MODE;它只提供了1个新的StringencryptedBytes,UTF8:这毫无意义。你不能把任意字节当作UTF8编码的字符串。如果希望加密的字节作为可打印文本,请执行已用于密钥的操作:使用base64对其进行编码。请注意,您最好使用StandardCharsets.UTF_8,而不是UTF8。因此,我将其替换为字符串encryptedText=Base64.getEncoder.EncodeToStringEntryPtedBytes;。但是这个DESede/ECB/PKCS5P怎么样?我应该用它来替换每一个DESede吗?当我尝试时,得到了java.security.nosuchagorithmexception:DESede/ECB/PKCS5Padding KeyGenerator不可用。他真的不在乎,我已经得到了这个作业的分数,但只有在我不重新启动应用程序的情况下,它才被解密,我想为自己改进这一点,并尝试用JavaFX而不是swingHmm来实现这一点,现在,当我在generateKey中将其设置为DESede,并在encrypt和decrypt中添加DESede/ECB/pkcs5p时,我没有得到这样的算法异常,加密工作,但我仍然通过解密得到错误:java.security.InvalidKeyException:错误的算法:DESede或TripleDESrequired@zygmunt我得看看你现在的密码。创建SecretKeySpec进行解密时,您是否仍在指定DESede?否,我将其更改为DESede/ECB/PKCS5Padding,我已最终更新了问题中的代码!谢谢你的帮助,最重要的是感谢你的耐心。我明天会仔细看一看,但恐怕这对我来说有点太复杂了me@zygmunt加密很难正确地进行。人们忽视了细节,这将导致实现失败。创建安全产品时,请确保您了解自己在做什么:/@zygmount我是出于其他原因创建的,您可以检查密码对称加密。下面介绍如何从密码创建加密密钥
        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");