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     */
030    package org.apache.shiro.codec;
031    
032    import 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     */
048    public 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    }