【问题标题】:Converting a String to byte[] for decryption将 String 转换为 byte[] 以进行解密
【发布时间】:2021-09-14 07:37:08
【问题描述】:

我的代码遇到了问题。

我想加密来自用户的密码并将其保存到 SQL 数据库。 我使用 AES 加密密码的代码:

        String key = "1234567890123456"; // 128 bit key
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        byte[] encrypted = cipher.doFinal(Passwort.getBytes());
        String verschlüsselt = new String(encrypted);

现在我将字符串“verschlüsselt”保存到我的数据库中。

在我的 LoginGUI 中,我从数据库中获取带有密码的字符串:

         String psswd = res.getString("Passwort"); //Getting the Password from Database and saving it into String
         System.out.println(psswd);  

打印输出是:“!?¿[ŸÊm,r~¤u”,这是正确的加密密码。

现在我正在尝试使用以下代码解密密码:

     String key = "1234567890123456"; // 128 bit key
     cipher.init(Cipher.DECRYPT_MODE, aesKey);
     EPasswort = new String(cipher.doFinal(bpsswd)); //Im facing the problem here

我知道我无法解密字符串,我必须将字符串转换为字节 []。 我已经通过多种方式做到了这一点:

          byte[] password = psswd.getBytes();
          System.out.println(password);
          password = psswd.getBytes("UTF-8");
          System.out.println(password);
          EPasswort = new String(cipher.doFinal(password));

但输出始终是解密器无法使用的:

[B@ab33b4
[B@189090b

如何将带有密码 (!?¿[ŸÊm,r~¤u) 的字符串转换为字节[],以便我的解密器可以使用它?

感谢您的帮助。

【问题讨论】:

  • 加密密码,然后匹配db密码。
  • 字节到字符串的转换和返回字节将失败,因为许多“不可打印”字符,如(十六进制)x00 字节。更好的是,您正在使用 Base64 和更高版本的解码功能。其次:对于您的加密,您选择了 ECB 模式,这是一个糟糕的选择,尤其是在加密密码时(有很多更好的选择可以使用,例如 BCrypt)。如果您需要用户提供密码,您应该使用 GCM 模式 对数据进行加密。顺便说一句:最好使用不带变音符号的变量名,例如“verschlüsselt”中的“ü”。问候德国、奥地利或瑞士 :-)
  • I want to encrypt the Password from the user and save it to an SQL Database. 如果是用户认证,不要加密密码,而是使用slow salted hash (bcrypt, scrypt,... pbkdf2)

标签: java arrays string encryption


【解决方案1】:

您正在打印对字节数组的引用,而不是元素。

运行这个进行演示:

public static void main (String [] args) {
    String s = "12345678";
    byte [] b = s.getBytes ();
    System.out.println (b);
    for (int i = 0; i < 8; i++) {
        System.out.println (b [i]);
    }
}

输出:

[B@7a81197d
49
50
51
52
53
54
55
56

【讨论】:

    【解决方案2】:

    您应该加密字符串而不是将其保存为字符串,在此示例中,我使用的是 Base64 编码器和不同的密码,但背后的逻辑是相同的。您应该保存字节或字符串,而不是使用对字节的引用,这里是一个示例:

    @Test
    public void testCrypt () {
        
        final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
        final int TAG_LENGTH_BIT = 128;
        final int IV_LENGTH_BYTE = 12;
        final int SALT_LENGTH_BYTE = 16;
        
        final String password = "password";
    
        final byte[] salt = getRandomNonce(SALT_LENGTH_BYTE);
        final byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
        final SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
        
        String msg = "Test";
        String out = "";
        try {
            out = encrypt(ENCRYPT_ALGO, TAG_LENGTH_BIT, iv, aesKeyFromPassword, msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        byte[] plainText = new byte[0];
        try {
            plainText = decrypt(ENCRYPT_ALGO, TAG_LENGTH_BIT, iv, aesKeyFromPassword, out);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        System.out.println("Decrypted " + out + " in: " + new String(plainText, StandardCharsets.ISO_8859_1));
    }
    

    按照加密方法:

    private String encrypt(final String ENCRYPT_ALGO, final int TAG_LENGTH_BIT, final byte[] iv,
            final SecretKey aesKeyFromPassword, String msg) throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        final byte[] pText = msg.getBytes(StandardCharsets.ISO_8859_1);
        
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
    
        final byte[] cipherText = cipher.doFinal(pText);
    
        String out = Base64.getEncoder().encodeToString(cipherText);
        System.out.println("Encrypted " + msg + " as: " + out);
        return out;
    }
    
    
    private static SecretKey getAESKeyFromPassword(final char[] password, final byte[] salt) {
        final int KEY_LENGTH = 128;
        final int ITERATION_COUNT = 65536;
        
        try {
            final SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            final KeySpec spec = new PBEKeySpec(password, salt, ITERATION_COUNT, KEY_LENGTH);
            return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
        } catch (final Exception e) {
            LOGGER.error(e);
            throw new RedException(e);
        }
    }
    
    private static byte[] getRandomNonce(final int numBytes) {
        final byte[] nonce = new byte[numBytes];
        new SecureRandom().nextBytes(nonce);
        return nonce;
    }
    

    msgCrypted 保存在数据库中后,您应该检索它并处理它以解密,如下所示:

    private byte[] decrypt(final String ENCRYPT_ALGO, final int TAG_LENGTH_BIT, final byte[] iv,
            final SecretKey aesKeyFromPassword, String out) throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        Cipher dCipher = Cipher.getInstance(ENCRYPT_ALGO);
        dCipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
        final byte[] plainText = dCipher.doFinal(Base64.getDecoder().decode(out));
        return plainText;
    }
    

    在控制台中你应该有这样的东西:

    【讨论】:

    • 也许你应该考虑给出一个完整的运行示例。我知道“ENCRYPT_ALGO” - 字符串必须是什么,但初学者找不到解决方案。第二:GCM 模式需要一个随机数(建议大小为 12 字节,此处以“iv”命名),该随机数应该随机生成并发送给接收方(伴随密文)——你应该举例说明如何做到这一点。感谢您在 GCM 模式下选择 AES 而不是不安全的 ECB 模式。
    • 感谢您帮助改进我的答案。我对其进行了编辑以提供完整的示例。
    • 感谢编辑。最后一点:为什么将 PBKDF2 密钥派生限制为 16 字节(对于 AES-128)而不是默认使用 32 字节(对于 AES-256)? :-)
    • 没有特别的原因,我想最好将其保留为默认值。再次感谢您的帮助:)
    猜你喜欢
    • 2017-09-25
    • 2012-01-02
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    • 2011-02-15
    • 2023-03-10
    • 2012-05-18
    • 1970-01-01
    相关资源
    最近更新 更多