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

时间:2017-10-27 18:41:01

标签: java encryption secret-key

我正在尝试制作加密解密应用。我有两个类 - 一个具有生成密钥,加密和解密的功能,另一个用于JavaFX GUI。在GUI类中,我有4个textareas:第一个用于加密文本,第二个用于加密文本,第三个用于密钥(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

这是我的第一堂课:

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();
        }
    });

2 个答案:

答案 0 :(得分:2)

你的一个问题是:

String encryptedText = new String(encryptedBytes, "UTF8");

通常,密文中的许多字节序列不是有效的UTF-8编码字符。当您尝试创建String时,此格式错误的序列将替换为“替换字符”,然后来自密文的信息将无法挽回地丢失。当您将String转换回字节并尝试解密时,损坏的密文会引发错误。

如果您需要将密文表示为字符串,请使用base-64编码,就像使用密钥一样。

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

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

答案 1 :(得分:2)

有两个主要问题:

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

  2. Direct 3DES(DESede)是一次加密8个字节的分组密码。要加密多个块,有一些方法已定义如何正确执行。这是电话Block cipher mode

  3. 要获得正确的加密,您需要处理更多的事情

    从密码创建密钥

    假设你想要使用DESede(3des)​​。 密钥必须具有固定大小 - 16或24字节。要从密码正确生成密钥,您应使用PBKDF 。有些人对"必须使用"很敏感,但是忽略这一步骤主要是使用用户输入的密码来破坏加密安全性。

    对于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(不一定按此顺序排列,但您需要所有这些才能进行正确加密)。

    要解密输出,您需要将输出拆分为salt,ciphertext和tag。

    我在StackOverflow的例子中经常看到零向量(静态盐或静态盐),但在许多情况下,它可能导致破解密码重写密钥或明文。

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

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