潇湘沐
更新时间 2019 年 4 月 21 日晚上 9:49 UTC在@MaartenBodewes 和@MarkJeronimus 指出了一些需要考虑的事情之后,我正在更新答案以使其更正确。但是因为这个问题是关于实现的,而不是关于使它更安全的,所以这个和旧版本应该足以至少提供一些洞察力。同样,通过修改以下代码可以实现更安全的解决方案。变更日志密钥派生处理异常及其详细信息对每个数据使用单个 SecureRandom 实例(iv[8 字节] 和 salt[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
绝地无双
将不同的输出映射回同一输入的唯一方法是向输入添加额外的数据,并将其从解密的输出中剥离。使用 PKCS5Padding 是不够的,因为这不是随机的,在最坏的情况下,只添加 1 个字节。使用 IV 没有用,因为它需要在解密时知道。最简单的方法是在加密时添加一定数量的字节(例如等于块大小)的随机数据,而在解密时忽略这些字节。此随机数据的名称是 Number Used Once 中的“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;}附言。您知道将秘密存储在字符串中是个坏主意吗?字符串是最终的,因此内容不能被删除。字节数组可以被擦除(为简洁起见,本例中没有这样做)。您是否还知道您可以制作任何可以查看任何其他 Windows 程序的完整内存占用的 Windows 程序?