Шифрование Java Blowfish с CBC - PullRequest
1 голос
/ 19 апреля 2019

Я пытаюсь представить, что делает этот сайт https://codebeautify.org/encrypt-decrypt, используя Blowfish и CBC

Я не уверен, каков фактический термин, но метод шифрования, который я 'Хотелось бы, чтобы получалась непоследовательная зашифрованная строка, несмотря на использование того же содержимого и ключа,

Например, если я шифрую Hello с помощью ключа key123, дважды первый результат может показать abcde, ивторой должен показать что-то еще, например fghij.Но дешифрование abcde и fghij с помощью key123 должно вернуть одинаковое Hello.

enter image description here

Также могу ли я знать, что это за типкодировки они используют для получения окончательного результата?Например, hex / base64, потому что я пробовал оба варианта, но, похоже, он не дает похожего результата.

Вот что я использую:

Класс Crypto:

public static String enc(String content, String key) {
    String encCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
        Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        String secret = content;
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
        byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));

        System.out.println("-- Encrypted -----------");
        encCon = DatatypeConverter.printBase64Binary(encoding);
        System.out.println("-- encCon : " + encCon);
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
    }

    return encCon;
}

public static String dec(String content, String key) {
    String decCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
        Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        // Decode Base64
        byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);

        // Decrypt
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
        byte[] message = cipher.doFinal(ciphertext);

        System.out.println("-- Decrypted -----------");
        decCon = new String(message, "UTF-8");
        System.out.println("-- decCon : " + decCon);
    } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
    }

    return decCon;
}

Класс вызова (например, Main.java)

// This is what I get from codebeautify site, encrypting Hello with key123
// However, I'm getting javax.crypto.BadPaddingException: Given final block not properly padded
Crypto.dec("08GCpwyZc+qGNuxSvXAD2A==", "key123"); 

// Below 2 lines works fine, the only problem is the result isn't randomized
String encContent = Crypto.enc("Hello", "key123");
Crypto.dec(encContent, "key123");

Ответы [ 2 ]

2 голосов
/ 19 апреля 2019

ОБНОВЛЕНИЕ 2019-04-21 09:49 вечера UTC

После того, как @MaartenBodewes и @MarkJeronimus указали на некоторые моменты, я обновляю ответ, чтобы сделать его более правильным. Но поскольку этот вопрос касается реализации, а не повышения ее безопасности, этой и старой версии должно быть достаточно, чтобы хотя бы дать немного понимания. Опять же, более безопасное решение может быть достигнуто путем изменения приведенного ниже кода.

1007 * Changelog *

  • Вывод ключа
  • Обработка исключений с их деталями
  • Использование одного экземпляра SecureRandom для каждых данных (iv [8 байт] и соли [32 байт])
  • Проверка на нулевое значение и пустоту для шифрования открытого текста и дешифрования зашифрованного текста
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;

public class Crypto {
    private static final char[] tempKey = new char[] {'T', 'E', 'M', 'P', '_', 'G', 'E', 'N', '_', 'K', 'E', 'Y'};
    private static final SecureRandom secureRandomForSalt = new SecureRandom();
    private static final SecureRandom secureRandomForIV = new SecureRandom();

    private static byte[] generateSalt() throws RuntimeException {
        try{
            byte[] saltBytes = new byte[32];

            secureRandomForSalt.nextBytes(saltBytes);

            return saltBytes;
        }
        catch(Exception ex){
            ex.printStackTrace();
            throw new RuntimeException("An error occurred in salt generation part. Reason: " + ex.getMessage());
        }
    }

    public static String enc(String content) throws RuntimeException {
        String encClassMethodNameForLogging = Crypto.class.getName() + ".enc" + " || ";

        byte[] salt;
        byte[] encodedTmpSecretKey;
        SecretKeySpec keySpec;
        Cipher cipher;
        byte[] iv;
        IvParameterSpec ivParameterSpec;
        String finalEncResult;

        if(content == null || content.trim().length() == 0) {
            throw new RuntimeException("To be encrypted text is null or empty");
        }

        System.out.println("-- Encrypting -----------");

        try {
            salt = generateSalt();
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in salt generation part. Reason: " + ex.getMessage());
        }

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
            SecretKey tmpSecretKey = factory.generateSecret(spec);

            encodedTmpSecretKey = tmpSecretKey.getEncoded();
            System.out.println("-- Secret Key Derivation in Encryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (InvalidKeySpecException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
        }

        try {
            keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
            cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (NoSuchPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular padding mechanism is requested but is not available in the environment");
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
        }

        try {
            iv = new byte[cipher.getBlockSize()];
            secureRandomForIV.nextBytes(iv);
            ivParameterSpec = new IvParameterSpec(iv);
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
        }

        try {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
            byte[] encoding = cipher.doFinal(content.getBytes("UTF-8"));

            String encCon = DatatypeConverter.printBase64Binary(encoding);
            String ivStr = DatatypeConverter.printBase64Binary(iv);
            String saltStr = DatatypeConverter.printBase64Binary(salt);

            System.out.println("-- encCon : " + encCon);
            System.out.println("-- iv : " + ivStr);
            System.out.println("-- salt : " + saltStr);

            finalEncResult = encCon + ":" + ivStr + ":" + saltStr;
            System.out.println("-- finalEncRes : " + finalEncResult + "\n");
        }
        catch (InvalidKeyException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
        }
        catch (InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
        }
        catch (IllegalBlockSizeException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
        }
        catch (BadPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
        }
        catch (UnsupportedEncodingException ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
        }
        catch (Exception ex){
            ex.printStackTrace();
            throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage());
        }

        return finalEncResult;
    }

    public static String dec(String encContent) throws RuntimeException {
        String decClassMethodNameForLogging = Crypto.class.getName() + ".dec" + " || ";

        String decCon;
        byte[] salt;
        byte[] encodedTmpSecretKey;
        SecretKeySpec keySpec;
        Cipher cipher;
        byte[] iv;

        if(encContent == null || encContent.trim().length() == 0) {
            throw new RuntimeException("To be decrypted text is null or empty");
        }

        System.out.println("-- Decrypting -----------");

        try {
            salt = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.lastIndexOf(":") + 1));
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in salt retrieving part. Reason: " + ex.getMessage());
        }

        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
            SecretKey tmpSecretKey = factory.generateSecret(spec);

            encodedTmpSecretKey = tmpSecretKey.getEncoded();
            System.out.println("-- Secret Key Gathering in Decryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (InvalidKeySpecException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
        }

        try {
            keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
            cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
        }
        catch (NoSuchAlgorithmException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
        }
        catch (NoSuchPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation : The particular padding mechanism requested is not available in the environment");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
        }

        try {
            iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1, encContent.lastIndexOf(":")));
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
        }

        try {
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
            byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(encContent.substring(0, encContent.indexOf(":"))));

            decCon = new String(decoding, "UTF-8");
            System.out.println("-- decCon : " + decCon + "\n");
        }
        catch (InvalidKeyException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
        }
        catch (InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
        }
        catch (IllegalBlockSizeException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
        }
        catch (BadPaddingException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
        }
        catch (UnsupportedEncodingException ex){
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage());
        }

        return decCon;
    }

    public static void main(String args[]) {
        System.out.println("-- Original -------------");
        String plainText = "hello world";
        System.out.println("-- origWord : " + plainText + "\n");

        String e = Crypto.enc(plainText);
        String d = Crypto.dec(e);

        System.out.println("-- Results --------------");
        System.out.println("-- PlainText: " + plainText);
        System.out.println("-- EncryptedText: " + e);
        System.out.println("-- DecryptedText: " + d);
    }
}

Кроме того, исполняемая версия приведена ниже;

https://www.jdoodle.com/a/19HT


ОРИГИНАЛЬНЫЙ ОТВЕТ

Я вижу, что письменные комментарии соответствуют вашим потребностям, но я хочу поделиться приведенным ниже решением как для примера, так и для дальнейшего использования;

** Использование рандомизированного IV (размер блока шифра дан для размера IV, но статический размер байта также может быть определен как, например, '16 байтов')

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;

public class Crypto {
    public static String enc(String content, String key) {
        String encCon = "";
        String ivStr = "";

        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
            Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

            byte[] iv = new byte[cipher.getBlockSize()];
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(iv);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

            String secret = content;
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
            byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));

            System.out.println("-- Encrypted -----------");
            encCon = DatatypeConverter.printBase64Binary(encoding);
            ivStr = DatatypeConverter.printBase64Binary(iv);
            System.out.println("-- encCon : " + encCon);
            System.out.println("-- iv : " + ivStr);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return encCon + ":" + ivStr;
    }

    public static String dec(String encContent, String key) {
        String decCon = "";

        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
            Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

            byte[] iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1));

            String secret = encContent.substring(0, encContent.indexOf(":"));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
            byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(secret));

            System.out.println("-- Decrypted -----------");
            decCon = new String(decoding, "UTF-8");
            System.out.println("-- decCon : " + decCon);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return decCon;
    }

    public static void main(String args[]) {
        String e = Crypto.enc("hello world", "key123");
        String d = Crypto.dec(e, "key123");
    }
}

Примечание: Конечно, может быть достигнуто более безопасное решение. Приведенное выше решение дано только для того, чтобы дать немного понимания.

0 голосов
/ 19 апреля 2019

Единственный способ отобразить разные выходы на один и тот же вход - добавить дополнительные данные к своему входу и убрать его из расшифрованного вывода. Использование PKCS5Padding недостаточно, поскольку это не случайно, и в худшем случае добавляет только 1 байт. Использование IV бесполезно, так как оно должно быть известно во время расшифровки.

Самый простой способ - добавить определенное количество байтов (например, равное размеру блока) случайных данных при шифровании и игнорировать эти байты при расшифровке. Название этих случайных данных - «nonce» из числа, использованного один раз. (Не путать с тесно связанной «солью» - числом, которое вы сохраняете для дальнейшего использования).

Кстати, я не сделал это, чтобы соответствовать веб-сайту. Я не знаю, что, как веб-сайт шифрует, так как он отправляет все входные значения на сервер и показывает ответ. Разговор о безопасном ...

private static final SecureRandom SECURE_RANDOM = new SecureRandom();

public static String enc(String content, String key) {
    String encCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
        Cipher        cipher  = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        byte[] nonce = new byte[cipher.getBlockSize()];
        SECURE_RANDOM.nextBytes(nonce);

        // Construct plaintext = nonce + secret
        byte[] secret    = content.getBytes(StandardCharsets.UTF_8);
        byte[] plaintext = new byte[nonce.length + secret.length];
        System.arraycopy(nonce, 0, plaintext, 0, nonce.length);
        System.arraycopy(secret, 0, plaintext, nonce.length, secret.length);

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
        byte[] encoding = cipher.doFinal(plaintext);

        encCon = DatatypeConverter.printBase64Binary(encoding);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return encCon;
}

public static String dec(String content, String key) {
    String decCon = "";

    try {
        String IV = "12345678";

        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
        Cipher        cipher  = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");

        // Decode Base64
        byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);

        // Decrypt
        cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
        byte[] message = cipher.doFinal(ciphertext);

        decCon = new String(message,
                            cipher.getBlockSize(),
                            message.length - cipher.getBlockSize(),
                            StandardCharsets.UTF_8);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return decCon;
}

Ps. Знаете ли вы, что хранить секрет в строке - плохая идея? Строки являются окончательными, поэтому содержимое не может быть стерто. Массивы байтов могут быть стерты (для краткости в этом примере это не сделано). Знаете ли вы, что вы можете просто создать любую программу для Windows, которая может использовать полный объем памяти любой другой программы для Windows?

...