1 package org.apache.fulcrum.jce.crypto;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.UnsupportedEncodingException;
23 import java.security.MessageDigest;
24 import java.security.NoSuchAlgorithmException;
25
26 /**
27 * The implementation supplies a default password in the case that
28 * the programmer don't want to have additional hassles. It is easy to
29 * reengineer the password being used but much better than a hard-coded
30 * password in the application.
31 *
32 * The code uses parts from Markus Hahn's Blowfish library found at
33 * http://blowfishj.sourceforge.net/
34 *
35 * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl </a>
36 * @author <a href="mailto:maakus@earthlink.net">Markus Hahn</a>
37 */
38
39 public class PasswordFactory implements PasswordParameters
40 {
41
42 private static PasswordFactory instance;
43
44 String algo;
45
46 int count = PasswordParameters.COUNT;
47
48 public PasswordFactory(String algo) {
49 this.algo = algo;
50 }
51
52 public PasswordFactory(String algo, int count) {
53 this.algo = algo;
54 this.count = count;
55 }
56
57 /**
58 * Factory method to get a default instance
59 * @return an instance of the CryptoStreamFactory
60 */
61 public synchronized static PasswordFactory getInstance()
62 {
63 if( PasswordFactory.instance == null )
64 {
65 PasswordFactory.instance = new PasswordFactory("SHA1");
66 }
67
68 return PasswordFactory.instance;
69 }
70
71 /**
72 * Factory method to get a default instance
73 *
74 * @param algo algorithm
75 * @return an instance of the CryptoStreamFactory
76 */
77 public synchronized static PasswordFactory getInstance(String algo)
78 {
79 if( PasswordFactory.instance == null )
80 {
81 PasswordFactory.instance = new PasswordFactory(algo);
82 }
83
84 return PasswordFactory.instance;
85 }
86
87 /**
88 * Factory method to get a default instance
89 *
90 * @param algo algorithm
91 * @param count the number of MessageDigest iterations
92 * @return an instance of the CryptoStreamFactory
93 */
94 public synchronized static PasswordFactory getInstance(String algo, int count)
95 {
96 if( PasswordFactory.instance == null )
97 {
98 PasswordFactory.instance = new PasswordFactory(algo, count);
99 }
100
101 return PasswordFactory.instance;
102 }
103
104 /**
105 * Create a new password
106 *
107 * @return a default password using "xxxx-xxxx-xxxx-xxxxx"
108 *
109 * @throws NoSuchAlgorithmException the encryption algorithm is not supported
110 * @throws UnsupportedEncodingException the requested encoding is not supported
111 */
112 public char[] create()
113 throws NoSuchAlgorithmException, UnsupportedEncodingException
114 {
115 return create(
116 PasswordParameters.DefaultPassword(),
117 PasswordParameters.Salt(),
118 count
119 );
120 }
121
122 /**
123 * Create a new password using a seed
124 *
125 * @param seed the default password supplied by the caller
126 * @return a password using "xxxx-xxxx-xxxx-xxxxx"
127 *
128 * @throws NoSuchAlgorithmException the encryption algorithm is not supported
129 * @throws UnsupportedEncodingException the requested encoding is not supported
130 */
131 public char[] create( String seed )
132 throws NoSuchAlgorithmException, UnsupportedEncodingException
133 {
134 return create(
135 seed.toCharArray()
136 );
137 }
138
139 /**
140 * @param seed the default password supplied by the caller
141 * @return a password using "xxxx-xxxx-xxxx-xxxxx"
142 * @throws NoSuchAlgorithmException the encryption algorithm is not supported
143 * @throws UnsupportedEncodingException the requested encoding is not supported
144 */
145 public final char[] create( char[] seed )
146 throws NoSuchAlgorithmException, UnsupportedEncodingException
147 {
148 return create(
149 seed,
150 PasswordParameters.Salt(),
151 count
152 );
153 }
154
155 /**
156 * Creates a default password using "xxxx-xxxx-xxxx-xxxxx".
157 *
158 * @param salt the password salt
159 * @param password the default password
160 * @param count number of MessageDigest iterations
161 * @return the default password
162 * @throws NoSuchAlgorithmException the encryption algorithm is not supported
163 * @throws UnsupportedEncodingException the requested encoding is not supported
164 */
165 public char [] create( char[] password, byte[] salt, int count )
166 throws NoSuchAlgorithmException, UnsupportedEncodingException
167 {
168 char [] result = null;
169 MessageDigest sha1 = MessageDigest.getInstance( algo );
170 byte [] passwordMask = new String( password ).getBytes( "UTF-8" );
171 byte [] temp = new byte[salt.length + passwordMask.length];
172 byte [] digest = null;
173
174 StringBuilder stringBuffer = new StringBuilder();
175
176 // combine the password with the salt string into a byte[9
177
178 System.arraycopy( passwordMask, 0, temp, 0, passwordMask.length );
179 System.arraycopy( salt, 0, temp, passwordMask.length, salt.length );
180
181 // create a hash over and over to make it a bit random
182
183 digest = temp;
184
185 for (int i = 0; i < count; i++)
186 {
187 sha1.update( digest );
188 digest = sha1.digest();
189 }
190
191 // build a well-formed password string to be usable
192 // by a human
193
194 long long1 = createLong( digest, 0 );
195 long long2 = createLong( digest, 4 );
196 long long3 = createLong( digest, 8 );
197 long long4 = createLong( digest, 12 );
198
199 stringBuffer.append( Long.toHexString( long1 ).substring( 0, 4 ) );
200 stringBuffer.append( '-' );
201 stringBuffer.append( Long.toHexString( long2 ).substring( 0, 4 ) );
202 stringBuffer.append( '-' );
203 stringBuffer.append( Long.toHexString( long3 ).substring( 0, 4 ) );
204 stringBuffer.append( '-' );
205 stringBuffer.append( Long.toHexString( long4 ).substring( 0, 5 ) );
206
207 // copy the password
208 result = new char[stringBuffer.length()];
209
210 for (int i = 0; i < stringBuffer.length(); i++)
211 {
212 result[i] = stringBuffer.charAt( i );
213 }
214
215 // wipe out the StringBuilder
216 for (int i = 0; i < stringBuffer.length(); i++)
217 {
218 stringBuffer.setCharAt( i, ' ' );
219 }
220
221 return result;
222 }
223
224 /**
225 * Gets bytes from an array into a long.
226 *
227 * @param buf where to get the bytes
228 * @param nOfs index from where to read the data
229 * @return the 64bit integer
230 */
231 private static long createLong(byte [] buf, int nOfs)
232 {
233 return
234 ((long)(( buf[nOfs ] << 24) |
235 ((buf[nOfs + 1] & 0x0ff) << 16) |
236 ((buf[nOfs + 2] & 0x0ff) << 8) |
237 ( buf[nOfs + 3] & 0x0ff )) << 32) |
238 ((long)(( buf[nOfs + 4] << 24) |
239 ((buf[nOfs + 5] & 0x0ff) << 16) |
240 ((buf[nOfs + 6] & 0x0ff) << 8) |
241 ( buf[nOfs + 7] & 0x0ff )) & 0x0ffffffffL);
242 }
243 }