View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  /*
20   * The apr_md5_encode() routine in the APR project's apr_md5.c file uses much
21   * code obtained from the FreeBSD 3.0 MD5 crypt() function, which is licenced
22   * as follows:
23   * ----------------------------------------------------------------------------
24   * "THE BEER-WARE LICENSE" (Revision 42):
25   * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
26   * can do whatever you want with this stuff. If we meet some day, and you think
27   * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
28   * ----------------------------------------------------------------------------
29   */
30  package org.apache.shiro.codec;
31  
32  import java.io.IOException;
33  
34  /**
35   * Codec for <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">Unix Crypt</a>-style encoding.  While similar to
36   * Base64, it is not compatible with Base64.
37   * <p/>
38   * This implementation is based on encoding algorithms found in the Apache Portable Runtime library's
39   * <a href="http://svn.apache.org/viewvc/apr/apr/trunk/crypto/apr_md5.c?revision=HEAD&view=markup">apr_md5.c</a>
40   * implementation for its {@code crypt}-style support.  The APR team in turn received inspiration for its encoding
41   * implementation based on FreeBSD 3.0's {@code /usr/src/lib/libcrypt/crypt.c} implementation.  The
42   * accompanying license headers have been retained at the top of this source file.
43   * <p/>
44   * This file and all that it contains is ASL 2.0 compatible.
45   *
46   * @since 1.2
47   */
48  public class H64 {
49  
50      private static final char[] itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
51  
52      private static short toShort(byte b) {
53          return (short) (b & 0xff);
54      }
55  
56      private static int toInt(byte[] bytes, int offset, int numBytes) {
57          if (numBytes < 1 || numBytes > 4) {
58              throw new IllegalArgumentException("numBytes must be between 1 and 4.");
59          }
60          int val = toShort(bytes[offset]); //1st byte
61          for (int i = 1; i < numBytes; i++) { //any remaining bytes:
62              short s = toShort(bytes[offset + i]);
63              switch (i) {
64                  case 1: val |= s << 8; break;
65                  case 2: val |= s << 16; break;
66                  case 3: val |= s << 24; break;
67              }
68          }
69          return val;
70      }
71  
72      /**
73       * Appends the specified character into the buffer, rethrowing any encountered
74       * {@link IOException} as an {@link IllegalStateException} (since this method is used for internal
75       * implementation needs and we only ever use StringBuilders, we should never encounter an IOException).
76       *
77       * @param buf the buffer to append to
78       * @param c   the character to append.
79       */
80      private static void append(Appendable buf, char c) {
81          try {
82              buf.append(c);
83          } catch (IOException e) {
84              throw new IllegalStateException("Unable to append character to internal buffer.", e);
85          }
86      }
87  
88      /**
89       * Encodes the specified integer to {@code numChars} H64-compatible characters and appends them into {@code buf}.
90       *
91       * @param value    the integer to encode to H64-compatible characters
92       * @param buf      the output buffer
93       * @param numChars the number of characters the value should be converted to.  3, 2 or 1.
94       */
95      private static void encodeAndAppend(int value, Appendable buf, int numChars) {
96          for (int i = 0; i < numChars; i++) {
97              append(buf, itoa64[value & 0x3f]);
98              value >>= 6;
99          }
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 }