001/**
002 * Copyright 2010-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util.encrypt.openssl;
017
018import static com.google.common.base.Preconditions.checkArgument;
019import static com.google.common.collect.Lists.newArrayList;
020import static java.lang.System.arraycopy;
021import static org.kuali.common.util.Ascii.isDigit;
022import static org.kuali.common.util.Ascii.isLetter;
023import static org.kuali.common.util.base.Exceptions.illegalArgument;
024import static org.kuali.common.util.base.Precondition.checkNotNull;
025import static org.kuali.common.util.encrypt.openssl.OpenSSLContext.buildOpenSSLContext;
026
027import java.security.MessageDigest;
028import java.security.NoSuchAlgorithmException;
029import java.security.SecureRandom;
030import java.util.List;
031import java.util.Random;
032
033import org.kuali.common.util.encrypt.EncryptionContext;
034
035import com.google.common.collect.ImmutableList;
036
037public class OpenSSL {
038
039        private static final Random RANDOM = new SecureRandom();
040        private static final int DEFAULT_SALT_SIZE = 8;
041        // When using all 62 alphanumeric characters, 22 is the minimum length required for producing more combinations than what is possible with 128 bits
042        // In other words, 62^22 is greater than 2^128
043        private static final int DEFAULT_PASSWORD_LENGTH = 22;
044        private static final List<Character> DEFAULT_PASSWORD_CHARS = getAlphaNumericCharacters();
045
046        /**
047         * Uses SecureRandom to generate a random 22 character alphanumeric string
048         */
049        public static String generatePassword() {
050                return generatePassword(DEFAULT_PASSWORD_LENGTH);
051        }
052
053        /**
054         * Uses SecureRandom to generate a random alphanumeric string of the specified length
055         */
056        public static String generatePassword(int length) {
057                return generatePassword(DEFAULT_PASSWORD_CHARS, length);
058        }
059
060        public static String generatePassword(List<Character> chars, int length) {
061                return generatePassword(chars, length, RANDOM);
062        }
063
064        public static String generatePassword(List<Character> chars, int length, Random random) {
065                char[] buffer = new char[length];
066                int size = chars.size();
067                for (int i = 0; i < length; i++) {
068                        buffer[i] = chars.get(random.nextInt(size));
069                }
070                return new String(buffer);
071        }
072
073        public static OpenSSLEncryptor buildOpenSSLEncryptor(EncryptionContext context) {
074                OpenSSLContext osc = buildOpenSSLContext(context.getStrength());
075                return new OpenSSLEncryptor(osc, context.getPassword());
076        }
077
078        public static byte[] combineByteArrays(byte[]... arrays) {
079                byte[] bytes = allocateByteArray(arrays);
080                int offset = 0;
081                for (byte[] array : arrays) {
082                        offset = addByteArray(bytes, array, offset);
083                }
084                return bytes;
085        }
086
087        public static int addByteArray(byte[] dst, byte[] src, int offset) {
088                arraycopy(src, 0, dst, offset, src.length);
089                return offset + src.length;
090        }
091
092        public static byte[] allocateByteArray(byte[]... arrays) {
093                int length = 0;
094                for (byte[] array : arrays) {
095                        length += array.length;
096                }
097                return new byte[length];
098        }
099
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}