001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.security.util;
018:
019: import java.security.NoSuchAlgorithmException;
020: import java.security.spec.InvalidKeySpecException;
021:
022: import javax.crypto.Cipher;
023: import javax.crypto.SecretKey;
024: import javax.crypto.SecretKeyFactory;
025: import javax.crypto.spec.PBEKeySpec;
026: import javax.crypto.spec.PBEParameterSpec;
027:
028: import org.apache.commons.codec.binary.Base64;
029: import org.apache.jetspeed.security.SecurityException;
030:
031: /**
032: * <p>
033: * PBEPasswordTool encodes and decodes user passwords using Password Based encryptionl
034: * </p>
035: *
036: * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
037: * @version $Id$
038: */
039: public class PBEPasswordTool {
040: // PKCS #5 (PBE) algoritm
041: private static final String CIPHER_ALGORITM = "PBEwithMD5andDES";
042: // PKCS #5 iteration count is advised to be at least 1000
043: private static final int PKCS_5_ITERATIONCOUNT = 1111;
044: // pseudo random base salt which will be overlayed with userName.getBytes()
045: private static final byte[] PKCS_5_BASE_SALT = { (byte) 0xA9,
046: (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
047: (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
048:
049: // PBE cipher
050: private SecretKey pbeKey;
051:
052: public PBEPasswordTool(String pbePassword)
053: throws InvalidKeySpecException, NoSuchAlgorithmException {
054: pbeKey = SecretKeyFactory.getInstance(CIPHER_ALGORITM)
055: .generateSecret(
056: new PBEKeySpec(pbePassword.toCharArray()));
057: }
058:
059: /* (non-Javadoc)
060: * @see org.apache.jetspeed.security.spi.CredentialPasswordEncoder#encode(java.lang.String, java.lang.String)
061: * @see org.apache.jetspeed.security.PasswordEncodingService#encode(java.lang.String, java.lang.String)
062: */
063: public String encode(String userName, String clearTextPassword)
064: throws SecurityException {
065: try {
066: // prevent dictionary attacks as well as copying of encoded passwords by using the userName as salt
067: PBEParameterSpec cipherSpec = new PBEParameterSpec(
068: createSalt(userName.getBytes("UTF-8")),
069: PKCS_5_ITERATIONCOUNT);
070:
071: Cipher cipher = Cipher.getInstance(CIPHER_ALGORITM);
072: cipher.init(Cipher.ENCRYPT_MODE, pbeKey, cipherSpec);
073:
074: return new String(Base64.encodeBase64(cipher
075: .doFinal(clearTextPassword.getBytes("UTF-8"))),
076: "UTF-8");
077: } catch (Exception e) {
078: throw new SecurityException(
079: SecurityException.UNEXPECTED
080: .create("PBEPasswordTool", "encode", e
081: .getMessage()), e);
082: }
083: }
084:
085: /* (non-Javadoc)
086: * @see org.apache.jetspeed.security.PasswordEncodingService#decode(java.lang.String, java.lang.String)
087: */
088: public String decode(String userName, String encodedPassword)
089: throws SecurityException {
090: try {
091: // prevent dictionary attacks as well as copying of encoded passwords by using the userName as salt
092: PBEParameterSpec cipherSpec = new PBEParameterSpec(
093: createSalt(userName.getBytes("UTF-8")),
094: PKCS_5_ITERATIONCOUNT);
095:
096: Cipher cipher = Cipher.getInstance(CIPHER_ALGORITM);
097: cipher.init(Cipher.DECRYPT_MODE, pbeKey, cipherSpec);
098:
099: return new String(cipher.doFinal(Base64
100: .decodeBase64(encodedPassword.getBytes("UTF-8"))),
101: "UTF-8");
102: } catch (Exception e) {
103: throw new SecurityException(
104: SecurityException.UNEXPECTED
105: .create("PBEPasswordTool", "decode", e
106: .getMessage()), e);
107: }
108: }
109:
110: /*
111: * Create a PCKS #5 salt using the BASE_PCKS_5_SALT overlayed with the provided secret parameter
112: */
113: private byte[] createSalt(byte[] secret) {
114: byte[] salt = new byte[PKCS_5_BASE_SALT.length];
115: int i = 0;
116: for (; i < salt.length && i < secret.length; i++) {
117: salt[i] = secret[i];
118: }
119: for (; i < salt.length; i++) {
120: salt[i] = PKCS_5_BASE_SALT[i];
121: }
122: return salt;
123: }
124:
125: public static void main(String args[]) throws Exception {
126: if (args.length != 4
127: || (!args[0].equals("encode") && !args[0]
128: .equals("decode"))) {
129: System.err
130: .println("Encode/Decode a user password using Password Based Encryption");
131: System.err
132: .println("Usage: PBEPasswordTool <encode|decode> <encoding-password> <username> <password>");
133: System.err
134: .println(" encode|decode : specify if to encode or decode the provided password");
135: System.err
136: .println(" encoding-password: the password to be used for encoding and decoding");
137: System.err
138: .println(" username : the name of the user to which the provided password belongs");
139: System.err
140: .println(" password : the cleartext password to encode, or the encoded password to decode\n");
141: } else if (args[0].toLowerCase().equals("encode")) {
142: System.out.println("Encoded password: "
143: + new PBEPasswordTool(args[1]).encode(args[2],
144: args[3]));
145: } else {
146: System.out.println("Decoded password: "
147: + new PBEPasswordTool(args[1]).decode(args[2],
148: args[3]));
149: }
150: }
151: }
|