Java tutorial
package com._37coins; /* JSPWiki - a JSP-based WikiWiki clone. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.Random; import org.apache.commons.codec.binary.Base64; /** * Hashes and verifies salted SHA-1 passwords, which are compliant with RFC * 2307. */ public final class CryptoUtils { private static final String SSHA = "{SSHA}"; public static final Random RANDOM = new SecureRandom(); public static final int DEFAULT_SALT_SIZE = 8; private static final Object HELP = "--help"; private static final Object HASH = "--hash"; private static final Object VERIFY = "--verify"; /** * Private constructor to prevent direct instantiation. */ private CryptoUtils() { } /** * <p> * Convenience method for hashing and verifying salted SHA-1 passwords from * the command line. This method requires <code>commons-codec-1.3.jar</code> * (or a newer version) to be on the classpath. Command line arguments are * as follows: * </p> * <ul> * <li><code>--hash <var>password</var></code> - hashes * <var>password</var></code> and prints a password digest that looks like * this: <blockquote><code>{SSHA}yfT8SRT/WoOuNuA6KbJeF10OznZmb28=</code> * </blockquote></li> * <li><code>--verify <var>password</var> <var>digest</var></code> - * verifies <var>password</var> by extracting the salt from * <var>digest</var> (which is identical to what is printed by * <code>--hash</code>) and re-computing the digest again using the password * and salt. If the password supplied is the same as the one used to create * the original digest, <code>true</code> will be printed; otherwise * <code>false</code></li> * </ul> * <p> * For example, one way to use this utility is to change to JSPWiki's * <code>build</code> directory and type the following command: * </p> * <blockquote> * <code>java -cp JSPWiki.jar:../lib/commons-codec-1.3.jar com.ecyrd.jspwiki.util.CryptoUtil --hash mynewpassword</code> * </blockquote> * * @param args * arguments for this method as described above * @throws Exception * Catches nothing; throws everything up. */ public static void main(final String[] args) throws Exception { // Print help if the user requested it, or if no arguments if (args.length == 0 || (args.length == 1 && HELP.equals(args[0]))) { System.out.println("Usage: CryptUtil [options] "); System.out.println(" --hash password create hash for password"); System.out.println(" --verify password digest verify password for digest"); System.exit(0); } // User wants to hash the password if (HASH.equals(args[0])) { if (args.length < 2) { throw new IllegalArgumentException("Error: --hash requires a 'password' argument."); } final String password = args[1].trim(); System.out.println(CryptoUtils.getSaltedPassword(password.getBytes("UTF-8"))); } // User wants to verify an existing password else if (VERIFY.equals(args[0])) { if (args.length < 3) { throw new IllegalArgumentException("Error: --hash requires 'password' and 'digest' arguments."); } final String password = args[1].trim(); final String digest = args[2].trim(); System.out.println(CryptoUtils.verifySaltedPassword(password.getBytes("UTF-8"), digest)); } else { System.out.println("Wrong usage. Try --help."); } } /** * <p> * Creates an RFC 2307-compliant salted, hashed password with the SHA1 * MessageDigest algorithm. After the password is digested, the first 20 * bytes of the digest will be the actual password hash; the remaining bytes * will be a randomly generated salt of length {@link #DEFAULT_SALT_SIZE}, * for example: <blockquote> * <code>{SSHA}3cGWem65NCEkF5Ew5AEk45ak8LHUWAwPVXAyyw==</code></blockquote> * </p> * <p> * In layman's terms, the formula is * <code>digest( secret + salt ) + salt</code>. The resulting digest is * Base64-encoded. * </p> * <p> * Note that successive invocations of this method with the same password * will result in different hashes! (This, of course, is exactly the point.) * </p> * * @param password * the password to be digested * @return the Base64-encoded password hash, prepended by * <code>{SSHA}</code>. * @throws NoSuchAlgorithmException * If your JVM is completely b0rked and does not have SHA. */ public static String getSaltedPassword(byte[] password) throws NoSuchAlgorithmException { byte[] salt = new byte[DEFAULT_SALT_SIZE]; RANDOM.nextBytes(salt); return getSaltedPassword(password, salt); } /** * <p> * Helper method that creates an RFC 2307-compliant salted, hashed password * with the SHA1 MessageDigest algorithm. After the password is digested, * the first 20 bytes of the digest will be the actual password hash; the * remaining bytes will be the salt. Thus, supplying a password * <code>testing123</code> and a random salt <code>foo</code> produces the * hash: * </p> * <blockquote><code>{SSHA}yfT8SRT/WoOuNuA6KbJeF10OznZmb28=</code> * </blockquote> * <p> * In layman's terms, the formula is * <code>digest( secret + salt ) + salt</code>. The resulting digest is * Base64-encoded. * </p> * * @param password * the password to be digested * @param salt * the random salt * @return the Base64-encoded password hash, prepended by * <code>{SSHA}</code>. * @throws NoSuchAlgorithmException * If your JVM is totally b0rked and does not have SHA1. */ public static String getSaltedPassword(byte[] password, byte[] salt) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance("SHA"); digest.update(password); byte[] hash = digest.digest(salt); // Create an array with the hash plus the salt byte[] all = new byte[hash.length + salt.length]; for (int i = 0; i < hash.length; i++) { all[i] = hash[i]; } for (int i = 0; i < salt.length; i++) { all[hash.length + i] = salt[i]; } byte[] base64 = Base64.encodeBase64(all); return SSHA + new String(base64); } /** * Compares a password to a given entry and returns true, if it matches. * * @param password * The password in bytes. * @param entry * The password entry, typically starting with {SSHA}. * @return True, if the password matches. * @throws NoSuchAlgorithmException * If there is no SHA available. * @throws UnsupportedEncodingException * If no UTF-8 encoding is available */ public static boolean verifySaltedPassword(byte[] password, String entry) throws NoSuchAlgorithmException, UnsupportedEncodingException { // First, extract everything after {SSHA} and decode from Base64 if (!entry.startsWith(SSHA)) { throw new IllegalArgumentException("Hash not prefixed by {SSHA}; is it really a salted hash?"); } byte[] challenge = Base64.decodeBase64(entry.substring(6).getBytes("UTF-8")); // Extract the password hash and salt byte[] passwordHash = extractPasswordHash(challenge); byte[] salt = extractSalt(challenge); // Re-create the hash using the password and the extracted salt MessageDigest digest = MessageDigest.getInstance("SHA"); digest.update(password); byte[] hash = digest.digest(salt); // See if our extracted hash matches what we just re-created return Arrays.equals(passwordHash, hash); } /** * Helper method that extracts the hashed password fragment from a supplied * salted SHA digest by taking all of the characters before position 20. * * @param digest * the salted digest, which is assumed to have been previously * decoded from Base64. * @return the password hash * @throws IllegalArgumentException * if the length of the supplied digest is less than or equal to * 20 bytes */ public static byte[] extractPasswordHash(byte[] digest) throws IllegalArgumentException { if (digest.length < 20) { throw new IllegalArgumentException( "Hash was less than 20 characters; could not extract password hash!"); } // Extract the password hash byte[] hash = new byte[20]; for (int i = 0; i < 20; i++) { hash[i] = digest[i]; } return hash; } /** * Helper method that extracts the salt from supplied salted digest by * taking all of the characters at position 20 and higher. * * @param digest * the salted digest, which is assumed to have been previously * decoded from Base64. * @return the salt * @throws IllegalArgumentException * if the length of the supplied digest is less than or equal to * 20 bytes */ public static byte[] extractSalt(byte[] digest) throws IllegalArgumentException { if (digest.length <= 20) { throw new IllegalArgumentException("Hash was less than 21 characters; we found no salt!"); } // Extract the salt byte[] salt = new byte[digest.length - 20]; for (int i = 20; i < digest.length; i++) { salt[i - 20] = digest[i]; } return salt; } }