001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019/*
020 * The apr_md5_encode() routine in the APR project's apr_md5.c file uses much
021 * code obtained from the FreeBSD 3.0 MD5 crypt() function, which is licenced
022 * as follows:
023 * ----------------------------------------------------------------------------
024 * "THE BEER-WARE LICENSE" (Revision 42):
025 * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
026 * can do whatever you want with this stuff. If we meet some day, and you think
027 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
028 * ----------------------------------------------------------------------------
029 */
030package org.apache.shiro.codec;
031
032import java.io.IOException;
033
034/**
035 * Codec for <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">Unix Crypt</a>-style encoding.  While similar to
036 * Base64, it is not compatible with Base64.
037 * <p/>
038 * This implementation is based on encoding algorithms found in the Apache Portable Runtime library's
039 * <a href="http://svn.apache.org/viewvc/apr/apr/trunk/crypto/apr_md5.c?revision=HEAD&view=markup">apr_md5.c</a>
040 * implementation for its {@code crypt}-style support.  The APR team in turn received inspiration for its encoding
041 * implementation based on FreeBSD 3.0's {@code /usr/src/lib/libcrypt/crypt.c} implementation.  The
042 * accompanying license headers have been retained at the top of this source file.
043 * <p/>
044 * This file and all that it contains is ASL 2.0 compatible.
045 *
046 * @since 1.2
047 */
048public class H64 {
049
050    private static final char[] itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
051
052    private static short toShort(byte b) {
053        return (short) (b & 0xff);
054    }
055
056    private static int toInt(byte[] bytes, int offset, int numBytes) {
057        if (numBytes < 1 || numBytes > 4) {
058            throw new IllegalArgumentException("numBytes must be between 1 and 4.");
059        }
060        int val = toShort(bytes[offset]); //1st byte
061        for (int i = 1; i < numBytes; i++) { //any remaining bytes:
062            short s = toShort(bytes[offset + i]);
063            switch (i) {
064                case 1: val |= s << 8; break;
065                case 2: val |= s << 16; break;
066                case 3: val |= s << 24; break;
067            }
068        }
069        return val;
070    }
071
072    /**
073     * Appends the specified character into the buffer, rethrowing any encountered
074     * {@link IOException} as an {@link IllegalStateException} (since this method is used for internal
075     * implementation needs and we only ever use StringBuilders, we should never encounter an IOException).
076     *
077     * @param buf the buffer to append to
078     * @param c   the character to append.
079     */
080    private static void append(Appendable buf, char c) {
081        try {
082            buf.append(c);
083        } catch (IOException e) {
084            throw new IllegalStateException("Unable to append character to internal buffer.", e);
085        }
086    }
087
088    /**
089     * Encodes the specified integer to {@code numChars} H64-compatible characters and appends them into {@code buf}.
090     *
091     * @param value    the integer to encode to H64-compatible characters
092     * @param buf      the output buffer
093     * @param numChars the number of characters the value should be converted to.  3, 2 or 1.
094     */
095    private static void encodeAndAppend(int value, Appendable buf, int numChars) {
096        for (int i = 0; i < numChars; i++) {
097            append(buf, itoa64[value & 0x3f]);
098            value >>= 6;
099        }
100    }
101
102    /**
103     * Encodes the specified bytes to an {@code H64}-encoded String.
104     *
105     * @param bytes
106     * @return
107     */
108    public static String encodeToString(byte[] bytes) {
109        if (bytes == null || bytes.length == 0) return null;
110
111        StringBuilder buf = new StringBuilder();
112
113        int length = bytes.length;
114        int remainder = length % 3;
115        int i = 0; //starting byte
116        int last3ByteIndex = length - remainder; //last byte whose index is a multiple of 3
117
118        for(; i < last3ByteIndex; i += 3) {
119            int twentyFourBit = toInt(bytes, i, 3);
120            encodeAndAppend(twentyFourBit, buf, 4);
121        }
122        if (remainder > 0) {
123            //one or two bytes that we still need to encode:
124            int a = toInt(bytes, i, remainder);
125            encodeAndAppend(a, buf, remainder + 1);
126        }
127        return buf.toString();
128    }
129}