【问题标题】:Encrypting and Decrypting Using Java: Unable to get same output使用 Java 加密和解密:无法获得相同的输出
【发布时间】:2017-02-11 09:37:36
【问题描述】:

我正在尝试学习和测试 java 1.6 加密/解密 API。我想知道我做错了什么以及我在知识方面缺少什么。

在下面的代码中,我创建了两个密码:一个用于加密,另一个用于解密。当我使用这些密码时,我用不同的 SecretKey 初始化它们,但我仍然能够得到相同的值。这是为什么呢?

    String algorithm = "DES";
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);

    byte[] encBytes = "12345678".getBytes("UTF8");
    byte[] decBytes = "56781234".getBytes("UTF8");

    DESKeySpec keySpecEncrypt = new DESKeySpec(encBytes);
    DESKeySpec keySpecDecrypt = new DESKeySpec(decBytes);


    SecretKey keyEncrypt = keyFactory.generateSecret(keySpecEncrypt);
    SecretKey keyDecrypt = keyFactory.generateSecret(keySpecDecrypt);

    Cipher cipherEncrypt = Cipher.getInstance(algorithm);
    Cipher cipherDecrypt = Cipher.getInstance(algorithm);

    String input = "john doe";

    cipherEncrypt.init(Cipher.ENCRYPT_MODE, keyEncrypt);
    byte[] inputBytes = cipherEncrypt.doFinal(input.getBytes());
    System.out.println("inputBytes: " + new String(inputBytes));

    cipherDecrypt.init(Cipher.DECRYPT_MODE, keyDecrypt);
    byte[] outputBytes = cipherDecrypt.doFinal(inputBytes);
    System.out.println("outputBytes: " + new String(outputBytes));

【问题讨论】:

标签: java encryption


【解决方案1】:

欢迎使用加密!如前所述,DES 是对称的,需要与解密相同的密钥进行加密。该密钥必须是您正在使用的密码的正确位数。对于 56 位的 DES。不过,在你走得太远之前,你可能需要考虑以下几点:

  1. 您应该使用更强的加密标准,例如AES。现在可以破解DES 加密。
  2. 如果您想使用字符串作为键,那么您应该针对该键字符串使用像SHA-256 这样的强哈希函数。然后从该哈希输出中获取所需的加密密钥所需的位数,对于 AES,128 位就足够了。您的密钥字符串应该和您一样长。
  3. 最好使用分组密码模式,这种模式不会每次为相同的输入生成相同的输出。请参阅block cipher modes of operation 了解有关 ECB 模式为何不好的信息和可视化。

这是一个在 CBC 模式下使用 128 位 AES 加密和 PKCS #5 填充的工作示例:

import java.security.MessageDigest;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptDecrypt {
    public static void main(String[] args) throws Exception {
        // here are your inputs
        String keyString = "averylongtext!@$@#$#@$#*&(*&}{23432432432dsfsdf";
        String input = "john doe";

        // setup AES cipher in CBC mode with PKCS #5 padding
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        // setup an IV (initialization vector) that should be
        // randomly generated for each input that's encrypted
        byte[] iv = new byte[cipher.getBlockSize()];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);

        // hash keyString with SHA-256 and crop the output to 128-bit for key
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        digest.update(keyString.getBytes());
        byte[] key = new byte[16];
        System.arraycopy(digest.digest(), 0, key, 0, key.length);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");

        // encrypt
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(input.getBytes("UTF-8"));
        System.out.println("encrypted: " + new String(encrypted));

        // include the IV with the encrypted bytes for transport, you'll
        // need the same IV when decrypting (it's safe to send unencrypted)

        // decrypt
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
    }
}

【讨论】:

  • @WhiteFang34 存储我的 keyString 的方法或一般策略是什么?如果有人有这个,并且反编译我的 jar/class 文件(假设它没有被混淆,即使它是),那么他们可以解密我的字符串,对吧?
  • 如果您以任何形式分发对称加密的私钥,您可以通过任何混淆或操纵来安全地隐藏它。这是默默无闻的安全性,有人可以提取密钥。听起来您可能需要的是 RSA 或 DSA 等非对称加密。有了这些,您就有了一个公钥和一个私钥,您可以使用旨在安全地执行此操作的程序生成它们。您可以提供公钥,因此将其包含在您分发的 jar 中是安全的。只有您的私钥才能解密使用该公钥加密的任何输入。
  • @WhiteFang34 我想让你的代码更加模块化,我想修改它并创建两种方法,解密(字符串输入)和加密(字符串输入)。加密方法将是您已经提供的内容的复制/粘贴。但是,我如何修改它以便解密方法起作用?因为它是 IV 字节总是随机的,我解密失败。
  • 关于 IV,您应该将其与加密字节一起发送。这取决于您如何传输它们,但您可以单独发送它,也可以直接在加密字节之前发送。另一端只需以相同的方式处理它,然后将相同的 IV 传递给解密。请注意,如果您要将它们发送到 Web 服务器,那么您需要小心使用 Base64 编码之类的方式对它们进行编码以便传输,或者使用多部分 POST 按原样发送二进制文件。
  • 如果您从桌面 GUI 向服务器发出 Web 请求,您应该考虑使用 HTTPS。它已经使用非对称加密并为您处理所有细节。否则,对于非对称加密(RSA 或 DSA),您必须将公钥嵌入桌面 GUI,然后使用服务器端的私钥对其进行解密。如果您坚持使用对称加密 (AES),则没有安全的选项在桌面 GUI 中部署私钥,除非您只是信任这些最终用户并以安全的方式将其分发给他们。
【解决方案2】:

这是来自 JDK 文档的描述:

DESKeySpec 公共 DESKeySpec(字节 [] 密钥) 抛出 InvalidKeyException 使用 key 中的前 8 个字节作为 DES 密钥的密钥材料创建一个 DESKeySpec 对象。 构成 DES 密钥的字节是介于 key[0] 和 key[7] 之间的字节。

DESKeySpec 仅使用 byte[] 的前 8 个字节作为密钥。因此,在您的示例中使用的实际键是相同的。

【讨论】:

  • 谢谢。当我更改前 8 个字节时,我得到一个 javax.crypto.BadPaddingException。我认为这意味着解密失败?这是我第一次使用加密/解密,我需要知道这个异常是否表示解密失败而不是其他什么。
  • @user373312:你能说明前 8 个字节是如何改变的吗?谢谢。
  • 好的,我想我仍然缺少一些东西。我试图通过更改字符串文字值来更改字节。我尝试了以下方法: byte[] encBytes = "12345678".getBytes("UTF8"); byte[] decBytes = "56781234".getBytes("UTF8");我得到一个 BaddPaddingException 我尝试了以下,即使字节不同,我也成功解密。 byte[] encBytes = "12345678".getBytes("UTF8"); byte[] decBytes = "12345679".getBytes("UTF8");
  • (顺便说一句,我不知道如何格式化我的评论,但我已经编辑了上面的代码以反映我想要说明的内容)。
  • @user373312:在这种情况下使用不同的键时抛出异常可能是设计者的决定。 JDK 文档表明 BaddPaddingException 是故意抛出的。但是,它的描述似乎过于详细...
【解决方案3】:

这是一个使用 56 位 DES 加密的工作示例。

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class CipherHelper {

    // Algorithm used
    private final static String ALGORITHM = "DES";

    /**
     * Encrypt data
     * @param secretKey -   a secret key used for encryption
     * @param data      -   data to encrypt
     * @return  Encrypted data
     * @throws Exception
     */
    public static String cipher(String secretKey, String data) throws Exception {
        // Key has to be of length 8
        if (secretKey == null || secretKey.length() != 8)
            throw new Exception("Invalid key length - 8 bytes key needed!");

        SecretKey key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);

        return toHex(cipher.doFinal(data.getBytes()));
    }

    /**
     * Decrypt data
     * @param secretKey -   a secret key used for decryption
     * @param data      -   data to decrypt
     * @return  Decrypted data
     * @throws Exception
     */
    public static String decipher(String secretKey, String data) throws Exception {
        // Key has to be of length 8
        if (secretKey == null || secretKey.length() != 8)
            throw new Exception("Invalid key length - 8 bytes key needed!");

        SecretKey key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);

        return new String(cipher.doFinal(toByte(data)));
    }

    // Helper methods

    private static byte[] toByte(String hexString) {
        int len = hexString.length()/2;

        byte[] result = new byte[len];

        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
        return result;
    }

    public static String toHex(byte[] stringBytes) {
        StringBuffer result = new StringBuffer(2*stringBytes.length);

        for (int i = 0; i < stringBytes.length; i++) {
            result.append(HEX.charAt((stringBytes[i]>>4)&0x0f)).append(HEX.charAt(stringBytes[i]&0x0f));
        }

        return result.toString();
    }

    private final static String HEX = "0123456789ABCDEF";

    // Helper methods - end

    /**
     * Quick test
     * @param args
     */
    public static void main(String[] args) {
        try {

            String secretKey    = "01234567";
            String data="test";
            String encryptedData = cipher(secretKey, data);

            System.out.println("encryptedData: " + encryptedData);

            String decryptedData = decipher(secretKey, encryptedData);

            System.out.println("decryptedData: " + decryptedData);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

【讨论】:

  • 如描述所述,这是 DES,而不是 128 位 AES。
猜你喜欢
  • 2016-10-15
  • 1970-01-01
  • 2019-11-13
  • 1970-01-01
  • 2017-06-02
  • 2018-09-28
  • 2018-09-28
  • 2019-04-07
  • 2021-10-20
相关资源
最近更新 更多