我需要加密的字符串会显示在2D条码中(PDF-417),所以当有人知道扫描的想法时,它就不会可读。
其他需求:
它必须足够简单,以摆脱被监视的人,并且必须易于对其他有兴趣获取该数据的公司解密。他们打电话给我们,我们告诉他们标准,或者给他们一些简单的密钥,然后可以将其用于解密。
那些公司可能会使用不同的技术,因此最好坚持不依赖于某些特殊平台或技术的某些标准。
你有什么建议?有一些Java类做encrypt()和decrypt()没有太多的并发症,实现高安全标准?
encrypt()
decrypt()
与CBC等其他模式不同,GCM模式不需要IV是不可预测的。唯一的要求是,对于具有给定密钥的每次调用,IV必须是唯一的。如果对于给定的密钥重复一次,则可能会损害安全性。一种简单的实现方法是使用来自强伪随机数生成器的随机IV,如下所示。
也可以将序列或时间戳记用作IV,但听起来可能不那么琐碎。例如,如果系统未正确跟踪持久存储中已用作IV的序列,则在系统重新引导后,调用可能会重复IV。同样,也没有完美的时钟。电脑时钟重新调整等。
此外,每2 ^ 32次调用后应旋转一次键。有关IV要求的更多详细信息,请参阅此答案和NIST建议。
考虑到以下几点,这是我刚刚在Java 8中编写的加密和解密代码。希望有人会发现这个有用:
字节数组可以使用以下方法清除:
Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);
但是,从Java 8开始,没有容易清除的方法,SecretKeyspec并且SecretKey由于这两个接口的实现似乎尚未实现destroy()该接口的方法Destroyable。在下面的代码中,编写了一个单独的方法来清除SecretKeySpec和SecretKey使用反射。
SecretKeyspec
SecretKey
destroy()
Destroyable
SecretKeySpec
密钥应使用以下两种方法之一生成。
请注意,密钥是像密码一样的秘密,但是与供人使用的密码不同,密钥是供加密算法使用的,因此只能使用上述方式生成。
package com.sapbasu.javastudy; import java.lang.reflect.Field; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Crypto { private static final int AUTH_TAG_SIZE = 128; // bits // NIST recommendation: "For IVs, it is recommended that implementations // restrict support to the length of 96 bits, to // promote interoperability, efficiency, and simplicity of design." private static final int IV_LEN = 12; // bytes // number of random number bytes generated before re-seeding private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16); private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; private static final List<Integer> ALLOWED_KEY_SIZES = Arrays .asList(new Integer[] {128, 192, 256}); // bits private static SecureRandom prng; // Used to keep track of random number bytes generated by PRNG // (for the purpose of re-seeding) private static int bytesGenerated = 0; public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception { Objects.requireNonNull(input, "Input message cannot be null"); Objects.requireNonNull(key, "key cannot be null"); if (input.length == 0) { throw new IllegalArgumentException("Length of message cannot be 0"); } if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) { throw new IllegalArgumentException("Size of key must be 128, 192 or 256"); } Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); byte[] iv = getIV(IV_LEN); GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv); cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec); byte[] messageCipher = cipher.doFinal(input); // Prepend the IV with the message cipher byte[] cipherText = new byte[messageCipher.length + IV_LEN]; System.arraycopy(iv, 0, cipherText, 0, IV_LEN); System.arraycopy(messageCipher, 0, cipherText, IV_LEN, messageCipher.length); return cipherText; } public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception { Objects.requireNonNull(input, "Input message cannot be null"); Objects.requireNonNull(key, "key cannot be null"); if (input.length == 0) { throw new IllegalArgumentException("Input array cannot be empty"); } byte[] iv = new byte[IV_LEN]; System.arraycopy(input, 0, iv, 0, IV_LEN); byte[] messageCipher = new byte[input.length - IV_LEN]; System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN); GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec); return cipher.doFinal(messageCipher); } public byte[] getIV(int bytesNum) { if (bytesNum < 1) throw new IllegalArgumentException( "Number of bytes must be greater than 0"); byte[] iv = new byte[bytesNum]; prng = Optional.ofNullable(prng).orElseGet(() -> { try { prng = SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Wrong algorithm name", e); } return prng; }); if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) { prng.setSeed(prng.generateSeed(bytesNum)); bytesGenerated = 0; } prng.nextBytes(iv); bytesGenerated = bytesGenerated + bytesNum; return iv; } private static void clearSecret(Destroyable key) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { Field keyField = key.getClass().getDeclaredField("key"); keyField.setAccessible(true); byte[] encodedKey = (byte[]) keyField.get(key); Arrays.fill(encodedKey, Byte.MIN_VALUE); } }
加密密钥主要可以通过两种方式生成:
没有任何密码
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong()); SecretKey secretKey = keyGen.generateKey(); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES"); Crypto.clearSecret(secretKey); // After encryption or decryption with key Crypto.clearSecret(secretKeySpec);
带密码
SecureRandom random = SecureRandom.getInstanceStrong(); byte[] salt = new byte[32]; random.nextBytes(salt); PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, keyLength); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); SecretKey secretKey = keyFactory.generateSecret(keySpec); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES"); Crypto.clearSecret(secretKey); // After encryption or decryption with key Crypto.clearSecret(secretKeySpec);