1 |
efrain |
1 |
package com.cesams.twogetskills.library;
|
|
|
2 |
|
|
|
3 |
import java.nio.ByteBuffer;
|
|
|
4 |
import java.security.SecureRandom;
|
|
|
5 |
|
|
|
6 |
import javax.crypto.Cipher;
|
|
|
7 |
import javax.crypto.spec.IvParameterSpec;
|
|
|
8 |
import javax.crypto.spec.SecretKeySpec;
|
|
|
9 |
|
|
|
10 |
/**
|
|
|
11 |
* AesCipher
|
|
|
12 |
* <p>Encode/Decode text by password using AES-128-CBC algorithm</p>
|
|
|
13 |
*/
|
|
|
14 |
public class AesCipher {
|
|
|
15 |
public static final int INIT_VECTOR_LENGTH = 16;
|
|
|
16 |
/**
|
|
|
17 |
* @see <a href="https://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java">how-to-convert-a-byte-array-to-a-hex-string</a>
|
|
|
18 |
*/
|
|
|
19 |
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
|
|
20 |
|
|
|
21 |
/**
|
|
|
22 |
* Encoded/Decoded data
|
|
|
23 |
*/
|
|
|
24 |
protected String data;
|
|
|
25 |
/**
|
|
|
26 |
* Initialization vector value
|
|
|
27 |
*/
|
|
|
28 |
protected String initVector;
|
|
|
29 |
/**
|
|
|
30 |
* Error message if operation failed
|
|
|
31 |
*/
|
|
|
32 |
protected String errorMessage;
|
|
|
33 |
|
|
|
34 |
private AesCipher() {
|
|
|
35 |
super();
|
|
|
36 |
}
|
|
|
37 |
|
|
|
38 |
/**
|
|
|
39 |
* AesCipher constructor.
|
|
|
40 |
*
|
|
|
41 |
* @param initVector Initialization vector value
|
|
|
42 |
* @param data Encoded/Decoded data
|
|
|
43 |
* @param errorMessage Error message if operation failed
|
|
|
44 |
*/
|
|
|
45 |
private AesCipher(String initVector, String data, String errorMessage) {
|
|
|
46 |
super();
|
|
|
47 |
|
|
|
48 |
this.initVector = initVector;
|
|
|
49 |
this.data = data;
|
|
|
50 |
this.errorMessage = errorMessage;
|
|
|
51 |
}
|
|
|
52 |
|
|
|
53 |
/**
|
|
|
54 |
* Encrypt input text by AES-128-CBC algorithm
|
|
|
55 |
*
|
|
|
56 |
* @param secretKey 16/24/32 -characters secret password
|
|
|
57 |
* @param plainText Text for encryption
|
|
|
58 |
* @return Encoded string or NULL if error
|
|
|
59 |
*/
|
|
|
60 |
public static AesCipher encrypt(String secretKey, String plainText) {
|
|
|
61 |
String initVector = null;
|
|
|
62 |
try {
|
|
|
63 |
// Check secret length
|
|
|
64 |
if (!isKeyLengthValid(secretKey)) {
|
|
|
65 |
throw new Exception("Secret key's length must be 128, 192 or 256 bits");
|
|
|
66 |
}
|
|
|
67 |
|
|
|
68 |
// Get random initialization vector
|
|
|
69 |
SecureRandom secureRandom = new SecureRandom();
|
|
|
70 |
byte[] initVectorBytes = new byte[INIT_VECTOR_LENGTH / 2];
|
|
|
71 |
secureRandom.nextBytes(initVectorBytes);
|
|
|
72 |
initVector = bytesToHex(initVectorBytes);
|
|
|
73 |
initVectorBytes = initVector.getBytes("UTF-8");
|
|
|
74 |
|
|
|
75 |
IvParameterSpec ivParameterSpec = new IvParameterSpec(initVectorBytes);
|
|
|
76 |
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES");
|
|
|
77 |
|
|
|
78 |
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
|
79 |
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
|
|
|
80 |
|
|
|
81 |
// Encrypt input text
|
|
|
82 |
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
|
|
|
83 |
|
|
|
84 |
ByteBuffer byteBuffer = ByteBuffer.allocate(initVectorBytes.length + encrypted.length);
|
|
|
85 |
byteBuffer.put(initVectorBytes);
|
|
|
86 |
byteBuffer.put(encrypted);
|
|
|
87 |
|
|
|
88 |
// Result is base64-encoded string: initVector + encrypted result
|
|
|
89 |
// String result = Base64.encodeToString(byteBuffer.array(), Base64.DEFAULT);
|
|
|
90 |
|
|
|
91 |
String result = new String(Base64.encode(byteBuffer.array()));
|
|
|
92 |
|
|
|
93 |
|
|
|
94 |
// Return successful encoded object
|
|
|
95 |
return new AesCipher(initVector, result, null);
|
|
|
96 |
} catch (Throwable t) {
|
|
|
97 |
t.printStackTrace();
|
|
|
98 |
// Operation failed
|
|
|
99 |
return new AesCipher(initVector, null, t.getMessage());
|
|
|
100 |
}
|
|
|
101 |
}
|
|
|
102 |
|
|
|
103 |
/**
|
|
|
104 |
* Decrypt encoded text by AES-128-CBC algorithm
|
|
|
105 |
*
|
|
|
106 |
* @param secretKey 16/24/32 -characters secret password
|
|
|
107 |
* @param cipherText Encrypted text
|
|
|
108 |
* @return Self object instance with data or error message
|
|
|
109 |
*/
|
|
|
110 |
public static AesCipher decrypt(String secretKey, String cipherText) {
|
|
|
111 |
try {
|
|
|
112 |
// Check secret length
|
|
|
113 |
if (!isKeyLengthValid(secretKey)) {
|
|
|
114 |
throw new Exception("Secret key's length must be 128, 192 or 256 bits");
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
// Get raw encoded data
|
|
|
118 |
// byte[] encrypted = Base64.decode(cipherText, Base64.DEFAULT);
|
|
|
119 |
|
|
|
120 |
byte[] encrypted = Base64.decode(cipherText.getBytes());
|
|
|
121 |
|
|
|
122 |
// Slice initialization vector
|
|
|
123 |
IvParameterSpec ivParameterSpec = new IvParameterSpec(encrypted, 0, INIT_VECTOR_LENGTH);
|
|
|
124 |
// Set secret password
|
|
|
125 |
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES");
|
|
|
126 |
|
|
|
127 |
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
|
128 |
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
|
|
|
129 |
|
|
|
130 |
// Trying to get decrypted text
|
|
|
131 |
String result = new String(cipher.doFinal(encrypted, INIT_VECTOR_LENGTH, encrypted.length - INIT_VECTOR_LENGTH));
|
|
|
132 |
|
|
|
133 |
// Return successful decoded object
|
|
|
134 |
return new AesCipher(bytesToHex(ivParameterSpec.getIV()), result, null);
|
|
|
135 |
} catch (Throwable t) {
|
|
|
136 |
t.printStackTrace();
|
|
|
137 |
// Operation failed
|
|
|
138 |
return new AesCipher(null, null, t.getMessage());
|
|
|
139 |
}
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
/**
|
|
|
143 |
* Check that secret password length is valid
|
|
|
144 |
*
|
|
|
145 |
* @param key 16/24/32 -characters secret password
|
|
|
146 |
* @return TRUE if valid, FALSE otherwise
|
|
|
147 |
*/
|
|
|
148 |
public static boolean isKeyLengthValid(String key) {
|
|
|
149 |
return key.length() == 16 || key.length() == 24 || key.length() == 32;
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
/**
|
|
|
153 |
* Convert Bytes to HEX
|
|
|
154 |
*
|
|
|
155 |
* @param bytes Bytes array
|
|
|
156 |
* @return String with bytes values
|
|
|
157 |
*/
|
|
|
158 |
public static String bytesToHex(byte[] bytes) {
|
|
|
159 |
char[] hexChars = new char[bytes.length * 2];
|
|
|
160 |
for (int j = 0; j < bytes.length; j++) {
|
|
|
161 |
int v = bytes[j] & 0xFF;
|
|
|
162 |
hexChars[j * 2] = hexArray[v >>> 4];
|
|
|
163 |
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
|
|
164 |
}
|
|
|
165 |
return new String(hexChars);
|
|
|
166 |
}
|
|
|
167 |
|
|
|
168 |
/**
|
|
|
169 |
* Get encoded/decoded data
|
|
|
170 |
*/
|
|
|
171 |
public String getData() {
|
|
|
172 |
return data;
|
|
|
173 |
}
|
|
|
174 |
|
|
|
175 |
/**
|
|
|
176 |
* Get initialization vector value
|
|
|
177 |
*/
|
|
|
178 |
public String getInitVector() {
|
|
|
179 |
return initVector;
|
|
|
180 |
}
|
|
|
181 |
|
|
|
182 |
/**
|
|
|
183 |
* Get error message
|
|
|
184 |
*/
|
|
|
185 |
public String getErrorMessage() {
|
|
|
186 |
return errorMessage;
|
|
|
187 |
}
|
|
|
188 |
|
|
|
189 |
/**
|
|
|
190 |
* Check that operation failed
|
|
|
191 |
*
|
|
|
192 |
* @return TRUE if failed, FALSE otherwise
|
|
|
193 |
*/
|
|
|
194 |
public boolean hasError() {
|
|
|
195 |
return this.errorMessage != null;
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
/**
|
|
|
199 |
* To string return resulting data
|
|
|
200 |
*
|
|
|
201 |
* @return Encoded/decoded data
|
|
|
202 |
*/
|
|
|
203 |
public String toString() {
|
|
|
204 |
return getData();
|
|
|
205 |
}
|
|
|
206 |
}
|