View Javadoc
1   /**
2    * Copyright 2010-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util.encrypt.openssl;
17  
18  import static com.google.common.base.Preconditions.checkArgument;
19  import static com.google.common.collect.Lists.newArrayList;
20  import static java.lang.System.arraycopy;
21  import static org.kuali.common.util.Ascii.isDigit;
22  import static org.kuali.common.util.Ascii.isLetter;
23  import static org.kuali.common.util.base.Exceptions.illegalArgument;
24  import static org.kuali.common.util.base.Precondition.checkNotNull;
25  import static org.kuali.common.util.encrypt.openssl.OpenSSLContext.buildOpenSSLContext;
26  
27  import java.security.MessageDigest;
28  import java.security.NoSuchAlgorithmException;
29  import java.security.SecureRandom;
30  import java.util.List;
31  import java.util.Random;
32  
33  import org.kuali.common.util.encrypt.EncryptionContext;
34  
35  import com.google.common.collect.ImmutableList;
36  
37  public class OpenSSL {
38  
39  	private static final Random RANDOM = new SecureRandom();
40  	private static final int DEFAULT_SALT_SIZE = 8;
41  	// When using all 62 alphanumeric characters, 22 is the minimum length required for producing more combinations than what is possible with 128 bits
42  	// In other words, 62^22 is greater than 2^128
43  	private static final int DEFAULT_PASSWORD_LENGTH = 22;
44  	private static final List<Character> DEFAULT_PASSWORD_CHARS = getAlphaNumericCharacters();
45  
46  	/**
47  	 * Uses SecureRandom to generate a random 22 character alphanumeric string
48  	 */
49  	public static String generatePassword() {
50  		return generatePassword(DEFAULT_PASSWORD_LENGTH);
51  	}
52  
53  	/**
54  	 * Uses SecureRandom to generate a random alphanumeric string of the specified length
55  	 */
56  	public static String generatePassword(int length) {
57  		return generatePassword(DEFAULT_PASSWORD_CHARS, length);
58  	}
59  
60  	public static String generatePassword(List<Character> chars, int length) {
61  		return generatePassword(chars, length, RANDOM);
62  	}
63  
64  	public static String generatePassword(List<Character> chars, int length, Random random) {
65  		char[] buffer = new char[length];
66  		int size = chars.size();
67  		for (int i = 0; i < length; i++) {
68  			buffer[i] = chars.get(random.nextInt(size));
69  		}
70  		return new String(buffer);
71  	}
72  
73  	public static OpenSSLEncryptor buildOpenSSLEncryptor(EncryptionContext context) {
74  		OpenSSLContext osc = buildOpenSSLContext(context.getStrength());
75  		return new OpenSSLEncryptor(osc, context.getPassword());
76  	}
77  
78  	public static byte[] combineByteArrays(byte[]... arrays) {
79  		byte[] bytes = allocateByteArray(arrays);
80  		int offset = 0;
81  		for (byte[] array : arrays) {
82  			offset = addByteArray(bytes, array, offset);
83  		}
84  		return bytes;
85  	}
86  
87  	public static int addByteArray(byte[] dst, byte[] src, int offset) {
88  		arraycopy(src, 0, dst, offset, src.length);
89  		return offset + src.length;
90  	}
91  
92  	public static byte[] allocateByteArray(byte[]... arrays) {
93  		int length = 0;
94  		for (byte[] array : arrays) {
95  			length += array.length;
96  		}
97  		return new byte[length];
98  	}
99  
100 	/**
101 	 * Creates an 8 byte salt
102 	 */
103 	public static byte[] createSalt() {
104 		return createSalt(DEFAULT_SALT_SIZE);
105 	}
106 
107 	/**
108 	 * Creates a salt of the indicated length
109 	 */
110 	public static byte[] createSalt(int length) {
111 		byte[] salt = new byte[length];
112 		RANDOM.nextBytes(salt);
113 		return salt;
114 	}
115 
116 	public static final byte[] toByteArray(List<Byte> bytes) {
117 		byte[] array = new byte[bytes.size()];
118 		for (int i = 0; i < array.length; i++) {
119 			array[i] = bytes.get(i);
120 		}
121 		return array;
122 	}
123 
124 	public static ImmutableList<Byte> toByteList(byte[] original) {
125 		return toByteList(original, 0, original.length);
126 	}
127 
128 	public static ImmutableList<Byte> toByteList(byte[] original, int offset, int length) {
129 		List<Byte> list = newArrayList();
130 		for (int i = offset; i < length; i++) {
131 			list.add(original[i]);
132 		}
133 		return ImmutableList.copyOf(list);
134 	}
135 
136 	public static String checkBase64(String text) {
137 		checkNotNull(text, "text");
138 		for (char c : text.toCharArray()) {
139 			checkArgument(isBase64(c), "'%s' is not a base 64 character", c);
140 		}
141 		return text;
142 	}
143 
144 	public static boolean isBase64(char c) {
145 		if (isLetter(c) || isDigit(c)) {
146 			return true;
147 		} else {
148 			return c == '/' || c == '+' || c == '=';
149 		}
150 	}
151 
152 	public static MessageDigest getMessageDigest(String algorithm) {
153 		try {
154 			return MessageDigest.getInstance(algorithm);
155 		} catch (NoSuchAlgorithmException e) {
156 			throw illegalArgument(e);
157 		}
158 
159 	}
160 
161 	public static OpenSSLEncryptedContext buildEncryptedContext(OpenSSLContext context, int initVectorLength, byte[] salt, byte[] data) {
162 		MessageDigest md = getMessageDigest(context.getDigestAlgorithm());
163 		int keyLength = context.getKeySizeBits() / Byte.SIZE;
164 		byte[] key = new byte[keyLength];
165 		int keyIndex = 0;
166 		byte[] initVector = new byte[initVectorLength];
167 		int initVectorIndex = 0;
168 		byte[] md_buf = null;
169 		int nkey = keyLength;
170 		int niv = initVectorLength;
171 		int i = 0;
172 		int addmd = 0;
173 		for (;;) {
174 			md.reset();
175 			if (addmd++ > 0) {
176 				md.update(md_buf);
177 			}
178 			md.update(data);
179 			if (null != salt) {
180 				md.update(salt, 0, 8);
181 			}
182 			md_buf = md.digest();
183 			for (i = 1; i < context.getIterations(); i++) {
184 				md.reset();
185 				md.update(md_buf);
186 				md_buf = md.digest();
187 			}
188 			i = 0;
189 			if (nkey > 0) {
190 				for (;;) {
191 					if (nkey == 0)
192 						break;
193 					if (i == md_buf.length)
194 						break;
195 					key[keyIndex++] = md_buf[i];
196 					nkey--;
197 					i++;
198 				}
199 			}
200 			if (niv > 0 && i != md_buf.length) {
201 				for (;;) {
202 					if (niv == 0)
203 						break;
204 					if (i == md_buf.length)
205 						break;
206 					initVector[initVectorIndex++] = md_buf[i];
207 					niv--;
208 					i++;
209 				}
210 			}
211 			if (nkey == 0 && niv == 0) {
212 				break;
213 			}
214 		}
215 		for (i = 0; i < md_buf.length; i++) {
216 			md_buf[i] = 0;
217 		}
218 
219 		OpenSSLEncryptedContext.Builder builder = OpenSSLEncryptedContext.builder();
220 		builder.withSalt(toByteList(salt));
221 		builder.withKey(toByteList(key));
222 		builder.withInitVector(toByteList(initVector));
223 		return builder.build();
224 	}
225 
226 	protected static List<Character> getAlphaNumericCharacters() {
227 		List<Character> chars = newArrayList();
228 		for (char c = 'A'; c < 'Z'; c++) {
229 			chars.add(c);
230 		}
231 		for (char c = 'a'; c < 'z'; c++) {
232 			chars.add(c);
233 		}
234 		for (char c = '0'; c < '9'; c++) {
235 			chars.add(c);
236 		}
237 		return ImmutableList.copyOf(chars);
238 	}
239 
240 }