Skip to content

Commit c5fd5fb

Browse files
committed
[Authentication] Refactor into CryptoUtils
- Also a CryptoData class to convert salt, iv and data to Base64
1 parent bba045b commit c5fd5fb

File tree

3 files changed

+309
-66
lines changed

3 files changed

+309
-66
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* This program and the accompanying materials
3+
* are made available under the terms of the License
4+
* which accompanies this distribution in the file LICENSE.txt
5+
*/
6+
package org.archicontribs.modelrepository.authentication;
7+
8+
import java.util.Base64;
9+
10+
/**
11+
* Represents encrypted data, salt and inialization vector
12+
* These can be encoded to and decoded from a Base64 String
13+
*
14+
* @author Phillip Beauvoir
15+
*/
16+
public class CryptoData {
17+
// Separates salt and iv from the data. These must not be valid Base64 characters
18+
private static final char SALT_SEPARATOR = ',';
19+
private static final char IV_SEPARATOR = ';';
20+
21+
private final byte[] salt;
22+
private final byte[] iv;
23+
private final byte[] encryptedData;
24+
25+
/**
26+
* Take salt, iv and encryptedData
27+
* @param salt optional salt
28+
* @param iv optional iv
29+
* @param encryptedData
30+
*/
31+
public CryptoData(byte[] salt, byte[] iv, byte[] encryptedData) {
32+
this.salt = salt;
33+
this.iv = iv;
34+
this.encryptedData = encryptedData;
35+
}
36+
37+
/**
38+
* Take Base64 encoded string and get salt, iv and data bytes
39+
* salt, iv areoptional
40+
* @param encodedString
41+
*/
42+
public CryptoData(String encodedString) {
43+
int saltPos = encodedString.indexOf(SALT_SEPARATOR);
44+
int ivPos = encodedString.indexOf(IV_SEPARATOR);
45+
46+
if(saltPos != -1) {
47+
salt = Base64.getDecoder().decode(encodedString.substring(0, saltPos));
48+
}
49+
else { // no salt
50+
salt = null;
51+
}
52+
53+
if(ivPos != -1) {
54+
iv = Base64.getDecoder().decode(encodedString.substring(saltPos + 1, ivPos));
55+
}
56+
else { // No iv
57+
iv = null;
58+
ivPos = saltPos;
59+
}
60+
61+
encryptedData = Base64.getDecoder().decode(encodedString.substring(ivPos + 1));
62+
}
63+
64+
public byte[] getSalt() {
65+
return salt;
66+
}
67+
68+
public byte[] getIV() {
69+
return iv;
70+
}
71+
72+
public byte[] getEncryptedData() {
73+
return encryptedData;
74+
}
75+
76+
public String getBase64String() {
77+
// Encode to Base64 and concatanate to one string
78+
StringBuilder encryptedText = new StringBuilder();
79+
80+
// salt
81+
if(salt != null) {
82+
encryptedText.append(Base64.getEncoder().encodeToString(salt));
83+
encryptedText.append(SALT_SEPARATOR);
84+
}
85+
86+
// iv (non-AES algorithms don't use it)
87+
if(iv != null) {
88+
encryptedText.append(Base64.getEncoder().encodeToString(iv));
89+
encryptedText.append(IV_SEPARATOR);
90+
}
91+
92+
// data
93+
encryptedText.append(Base64.getEncoder().encodeToString(encryptedData));
94+
95+
return encryptedText.toString();
96+
}
97+
}

org.archicontribs.modelrepository/src/org/archicontribs/modelrepository/authentication/CryptoUtils.java

+182
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,25 @@
88
import java.nio.ByteBuffer;
99
import java.nio.CharBuffer;
1010
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;
1117
import java.util.Arrays;
1218
import java.util.Base64;
1319

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+
1430
/**
1531
* CryptoUtils
1632
*
@@ -19,6 +35,44 @@
1935
@SuppressWarnings("nls")
2036
public class CryptoUtils {
2137

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+
2276
/**
2377
* Convert char array to byte array without using intermediate String
2478
* chars are converted to UTF-8
@@ -53,5 +107,133 @@ public static char[] encodeCharsToBase64(char[] chars) {
53107
String encoded = Base64.getEncoder().encodeToString(bytes); // Encode bytes to Base64 String
54108
return encoded.toCharArray(); // Return string chars
55109
}
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+
}
56238

57239
}

0 commit comments

Comments
 (0)