1 package com.atlassian.seraph.util;
2
3 import org.apache.log4j.Logger;
4
5 import javax.crypto.Cipher;
6 import javax.crypto.SecretKey;
7 import javax.crypto.SecretKeyFactory;
8 import javax.crypto.spec.PBEKeySpec;
9 import javax.crypto.spec.PBEParameterSpec;
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.io.InputStreamReader;
13 import java.security.GeneralSecurityException;
14 import java.security.NoSuchAlgorithmException;
15 import java.security.spec.InvalidKeySpecException;
16
17 public class EncryptionUtils
18 {
19 private static final Logger log = Logger.getLogger(EncryptionUtils.class);
20
21
22 private static final byte[] SALT = {
23 (byte)0xc2, (byte)0x74, (byte)0x24, (byte)0x4c,
24 (byte)0x7c, (byte)0xd8, (byte)0xee, (byte)0x99
25 };
26
27 private static final int ITERATIONS = 21;
28
29 private PBEParameterSpec pbeParamSpec = new PBEParameterSpec(SALT, ITERATIONS);
30 private SecretKey pbeKey;
31
32 public String encrypt(String data)
33 {
34 try
35 {
36 Cipher pbeCipher = createCipher(true);
37
38
39 return encode(pbeCipher.doFinal(data.getBytes()));
40 } catch (Exception ex)
41 {
42
43 throw new RuntimeException(ex);
44 }
45 }
46
47 private Cipher createCipher(boolean encrypt) throws GeneralSecurityException
48 {
49 if (pbeKey == null)
50 throw new IllegalStateException("The password has not be set");
51
52
53 Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
54
55
56 pbeCipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), pbeKey, pbeParamSpec);
57 return pbeCipher;
58 }
59
60 public String decrypt(String encData)
61 {
62 try
63 {
64 Cipher pbeCipher = createCipher(false);
65 return new String(pbeCipher.doFinal(decode(encData)));
66 } catch (Exception e)
67 {
68
69 throw new RuntimeException(e);
70 }
71 }
72
73
74
75
76
77
78
79
80
81
82
83 static byte[] decode(String encoded)
84 {
85 int offsetsIndex = encoded.length() / 2;
86 char[] encodedPageNumbers = encoded.substring(0, offsetsIndex).toCharArray();
87 char[] encodedOffsets = encoded.substring(offsetsIndex).toCharArray();
88
89 byte[] bytes = new byte[offsetsIndex];
90 for (int i = 0; i < bytes.length; i++)
91 {
92 int pageNumber = decodePageNumber(encodedPageNumbers[i]);
93 byte offset = decodeOffset(encodedOffsets[i]);
94 if (pageNumber < 0 || pageNumber > 3)
95 {
96 log.debug("Invalid encoded page number '" + encodedPageNumbers[i] + "', decoded as " + pageNumber + ". Skipping byte.");
97 continue;
98 }
99 bytes[i] = (byte) ((pageNumber * 64 - 128) + offset);
100 }
101 return bytes;
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115 static byte decodeOffset(char encodedOffset)
116 {
117 if ('0' <= encodedOffset && encodedOffset <= '9')
118 return (byte) (encodedOffset - '0');
119 else if ('A' <= encodedOffset && encodedOffset <= 'Z')
120 return (byte) ((encodedOffset - 'A') + 10);
121 else if ('a' <= encodedOffset && encodedOffset <= 'z')
122 return (byte) ((encodedOffset - 'a') + 36);
123 else if (encodedOffset == '<')
124 return (byte) (62);
125 else if (encodedOffset == '>')
126 return (byte) (63);
127 else
128 {
129 log.debug("Cannot decode invalid encoded offset '" + encodedOffset + "'. Must be one of [A-Za-z0-9<>]. Returning default value '2'.");
130 return 2;
131 }
132 }
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 static String encode(byte[] plainBytes)
152 {
153 StringBuffer encodedPageNumbers = new StringBuffer(plainBytes.length);
154 StringBuffer encodedOffsets = new StringBuffer(plainBytes.length);
155 for (int i = 0; i < plainBytes.length; i++)
156 {
157 byte aByte = plainBytes[i];
158
159 int pageNumber = ((int) aByte + 128) / 64;
160 encodedPageNumbers.append(encodePageNumber(pageNumber));
161
162 int offset = ((int) aByte + 128) % 64;
163 encodedOffsets.append(encodeOffset(offset));
164 }
165 return encodedPageNumbers.toString() + encodedOffsets.toString();
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179
180 static char encodePageNumber(int pageNumber)
181 {
182 int randomChar = (pageNumber * 6) + (int) (Math.random() * 6);
183 boolean upperCase = (int) (Math.random() * 2) < 1;
184 return (char) (randomChar + (int) (upperCase ? 'A' : 'a'));
185 }
186
187
188
189
190
191
192
193
194 static int decodePageNumber(char encodedPageNumber)
195 {
196 return ((int) Character.toLowerCase(encodedPageNumber) - 'a') / 6;
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210 static char encodeOffset(int offset)
211 {
212 if (offset < 0 || offset > 63)
213 {
214 log.debug("Invalid offset to encode: " + offset + ". Should be between 0 and 63. Returning null character.");
215 return (char) 0;
216 }
217
218 if (offset < 10)
219 return (char) (offset + '0');
220
221 if (offset < 36)
222 return (char) (offset - 10 + 'A');
223
224 if (offset < 62)
225 return (char) (offset - 36 + 'a');
226
227 if (offset < 63)
228 return '<';
229
230 return '>';
231 }
232
233 public void setPassword(String password)
234 {
235 PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
236 try
237 {
238 SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
239 pbeKey = keyFac.generateSecret(pbeKeySpec);
240 } catch (NoSuchAlgorithmException e)
241 {
242 throw new RuntimeException("Encryption algorithm not found", e);
243 } catch (InvalidKeySpecException e)
244 {
245 throw new RuntimeException("Invalid passphrase", e);
246 }
247
248
249 }
250
251 public static void main(String[] args) throws IOException
252 {
253 if (args.length == 0)
254 {
255 System.err.println("Usage: com.atlassian.payment.encrypted.EncryptionUtils [encrypt|decrypt]");
256 System.exit(1);
257 }
258
259 EncryptionUtils utils = new EncryptionUtils();
260 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
261 if ("encrypt".equals(args[0]))
262 {
263 System.out.println("Enter the text to encrypt:");
264 String text = in.readLine();
265 System.out.println("\n"+utils.encrypt(text)+"\n");
266 }
267 else
268 {
269 System.out.println("Paste the encrypted text:");
270 String cdata = in.readLine();
271 System.out.println(utils.decrypt(cdata));
272 }
273 }
274 }