|
8 | 8 | import java.nio.ByteBuffer;
|
9 | 9 | import java.nio.CharBuffer;
|
10 | 10 | import java.nio.charset.Charset;
|
| 11 | +import java.security.GeneralSecurityException; |
| 12 | +import java.security.Key; |
| 13 | +import java.security.NoSuchAlgorithmException; |
| 14 | +import java.security.Provider; |
| 15 | +import java.security.SecureRandom; |
| 16 | +import java.security.Security; |
11 | 17 | import java.util.Arrays;
|
12 | 18 | import java.util.Base64;
|
13 | 19 |
|
| 20 | +import javax.crypto.Cipher; |
| 21 | +import javax.crypto.KeyGenerator; |
| 22 | +import javax.crypto.SecretKey; |
| 23 | +import javax.crypto.SecretKeyFactory; |
| 24 | +import javax.crypto.spec.GCMParameterSpec; |
| 25 | +import javax.crypto.spec.IvParameterSpec; |
| 26 | +import javax.crypto.spec.PBEKeySpec; |
| 27 | +import javax.crypto.spec.PBEParameterSpec; |
| 28 | +import javax.crypto.spec.SecretKeySpec; |
| 29 | + |
14 | 30 | /**
|
15 | 31 | * CryptoUtils
|
16 | 32 | *
|
|
19 | 35 | @SuppressWarnings("nls")
|
20 | 36 | public class CryptoUtils {
|
21 | 37 |
|
| 38 | + /** |
| 39 | + * Algorithms for PBE |
| 40 | + */ |
| 41 | + public static String[] PBE_ALGORITHMS = { |
| 42 | + "PBEwithHmacSHA256AndAES_256", |
| 43 | + "PBEWithHmacSHA1AndAES_256", |
| 44 | + "PBEWithHmacSHA512AndAES_128", |
| 45 | + "PBEWithHmacSHA1AndAES_128", |
| 46 | + "PBEWithHmacSHA384AndAES_256", |
| 47 | + "PBEWithMD5AndDES" |
| 48 | + }; |
| 49 | + |
| 50 | + /** |
| 51 | + * Algorithms for PBK |
| 52 | + */ |
| 53 | + public static String[] PBK_ALGORITHMS = { |
| 54 | + "PBKDF2WithHmacSHA1", |
| 55 | + "PBKDF2WithHmacSHA224", |
| 56 | + "PBKDF2WithHmacSHA256", |
| 57 | + "PBKDF2WithHmacSHA384", |
| 58 | + "PBKDF2WithHmacSHA512" |
| 59 | + }; |
| 60 | + |
| 61 | + |
| 62 | + /** |
| 63 | + * Cipher transformations |
| 64 | + */ |
| 65 | + public static final String AES = "AES"; // Weakest, no iv is used |
| 66 | + public static final String AES_CBC = "AES/CBC/PKCS5Padding"; // Better |
| 67 | + public static final String AES_GCM = "AES/GCM/NoPadding"; // Strongest |
| 68 | + |
| 69 | + /** |
| 70 | + * IV lengths |
| 71 | + */ |
| 72 | + public static final int CBC_IV_LENGTH = 16; // IV length for AES_CBC |
| 73 | + public static final int GCM_IV_LENGTH = 12; // IV length for AES_GCM |
| 74 | + |
| 75 | + |
22 | 76 | /**
|
23 | 77 | * Convert char array to byte array without using intermediate String
|
24 | 78 | * chars are converted to UTF-8
|
@@ -53,5 +107,133 @@ public static char[] encodeCharsToBase64(char[] chars) {
|
53 | 107 | String encoded = Base64.getEncoder().encodeToString(bytes); // Encode bytes to Base64 String
|
54 | 108 | return encoded.toCharArray(); // Return string chars
|
55 | 109 | }
|
| 110 | + |
| 111 | + /** |
| 112 | + * Generate random bytes for salt or iv |
| 113 | + */ |
| 114 | + public static byte[] generateRandomBytes(int length) { |
| 115 | + byte[] salt = new byte[length]; |
| 116 | + SecureRandom random = new SecureRandom(); |
| 117 | + random.nextBytes(salt); |
| 118 | + return salt; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Generate a random SecretKey |
| 123 | + */ |
| 124 | + public static SecretKey generateRandomSecretKey() throws NoSuchAlgorithmException { |
| 125 | + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); |
| 126 | + keyGenerator.init(256, SecureRandom.getInstanceStrong()); |
| 127 | + return keyGenerator.generateKey(); |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Generate a SecretKey from a password |
| 132 | + */ |
| 133 | + public static SecretKey generateKeyFromPassword(String algorithm, char[] password) throws GeneralSecurityException { |
| 134 | + // Convert the password bytes to Base64 characters because PBEKey class will not accept non-Ascii characters in a password |
| 135 | + char[] encodedPassword = CryptoUtils.encodeCharsToBase64(password); |
| 136 | + |
| 137 | + PBEKeySpec keySpec = new PBEKeySpec(encodedPassword); |
| 138 | + |
| 139 | + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); |
| 140 | + return keyFactory.generateSecret(keySpec); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Generate a SecretKey from a password using a stronger algorithm with salt and iterations |
| 145 | + * |
| 146 | + * Algorithms supported: |
| 147 | + * |
| 148 | + * PBKDF2WithHmacSHA1 |
| 149 | + * PBKDF2WithHmacSHA224 |
| 150 | + * PBKDF2WithHmacSHA256 |
| 151 | + * PBKDF2WithHmacSHA384 |
| 152 | + * PBKDF2WithHmacSHA512 |
| 153 | + */ |
| 154 | + public static SecretKey generateKeyFromPassword(String algorithm, char[] password, byte[] salt, int iterations) throws Exception { |
| 155 | + // Convert the password bytes to Base64 characters because PBEKey class will not accept non-Ascii characters in a password |
| 156 | + char[] encodedPassword = CryptoUtils.encodeCharsToBase64(password); |
| 157 | + |
| 158 | + PBEKeySpec pbeKeySpec = new PBEKeySpec(encodedPassword, salt, iterations, 256); |
| 159 | + |
| 160 | + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); |
| 161 | + SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec); |
| 162 | + |
| 163 | + return new SecretKeySpec(pbeKey.getEncoded(), "AES"); |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Get and initialise Cipher based on PBE key from generateKeyFromPassword |
| 168 | + * |
| 169 | + * @param key The secret PBE key generated from calling generateKeyFromPassword |
| 170 | + * @param algorithm The PBE transformation algorithm to use |
| 171 | + * @param mode Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE |
| 172 | + * @param salt Salt needed for PBEParameterSpec |
| 173 | + * @param iv Optional IV - some PBE algorithms don't use one |
| 174 | + * @param iterations |
| 175 | + */ |
| 176 | + public static Cipher getPBECipher(Key key, String algorithm, int mode, byte[] salt, byte[] iv, int iterations) throws GeneralSecurityException { |
| 177 | + Cipher cipher = Cipher.getInstance(algorithm); |
| 178 | + |
| 179 | + // PBEParameterSpec with salt and (optional) iv spec |
| 180 | + IvParameterSpec paramSpec = iv != null ? new IvParameterSpec(iv) : null; |
| 181 | + |
| 182 | + // Create parameters from the salt and an arbitrary number of iterations (paramSpec will be null if iv is null) |
| 183 | + PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, iterations, paramSpec); |
| 184 | + |
| 185 | + // Set the cipher mode to decryption or encryption |
| 186 | + cipher.init(mode, key, pbeParamSpec); |
| 187 | + |
| 188 | + return cipher; |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Get and initialise Cipher with a secret key and optional iv |
| 193 | + * param @iv is optional and not used for AES |
| 194 | + * |
| 195 | + * @param key The scret key |
| 196 | + * @param algorithm The transformation algorithm to use |
| 197 | + * @param mode Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE |
| 198 | + * @param iv Optional IV |
| 199 | + */ |
| 200 | + public static Cipher getCipher(Key key, String algorithm, int mode, byte... iv) throws GeneralSecurityException { |
| 201 | + Cipher cipher = Cipher.getInstance(algorithm); |
| 202 | + |
| 203 | + switch(algorithm) { |
| 204 | + case AES: |
| 205 | + cipher.init(mode, key); |
| 206 | + break; |
| 207 | + |
| 208 | + case AES_CBC: |
| 209 | + cipher.init(mode, key, new IvParameterSpec(iv)); |
| 210 | + break; |
| 211 | + |
| 212 | + case AES_GCM: |
| 213 | + cipher.init(mode, key, new GCMParameterSpec(128, iv)); |
| 214 | + break; |
| 215 | + } |
| 216 | + |
| 217 | + return cipher; |
| 218 | + } |
| 219 | + |
| 220 | + /** |
| 221 | + * Detect PBE algorithms which require initialization vector |
| 222 | + */ |
| 223 | + public static boolean usesIV(String algorithm) { |
| 224 | + return algorithm.contains("AES"); |
| 225 | + } |
| 226 | + |
| 227 | + /** |
| 228 | + * Print available algorithms |
| 229 | + */ |
| 230 | + public static void printAlgorithms() { |
| 231 | + for(Provider provider : Security.getProviders()) { |
| 232 | + System.out.println(provider.getName()); |
| 233 | + for(String key : provider.stringPropertyNames()) { |
| 234 | + System.out.println("\t" + key + "\t" + provider.getProperty(key)); |
| 235 | + } |
| 236 | + } |
| 237 | + } |
56 | 238 |
|
57 | 239 | }
|
0 commit comments