/* * Copyright 2006-2011 the original author or authors. * * Licensed 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. */ package org.springframework.security.jwt.codec; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; /** * Functions for Hex, Base64 and Utf8 encoding/decoding * * @author Luke Taylor */ public class Codecs { private static Charset UTF8 = Charset.forName("UTF-8"); /** * Base 64 */ public static byte[] b64Encode(byte[] bytes) { return Base64Codec.encode(bytes); } public static byte[] b64Decode(byte[] bytes) { return Base64Codec.decode(bytes); } // URL-safe versions with no padding chars public static byte[] b64UrlEncode(byte[] bytes) { return Base64.urlEncode(bytes); } public static byte[] b64UrlEncode(CharSequence value) { return b64UrlEncode(utf8Encode(value)); } public static byte[] b64UrlDecode(byte[] bytes) { return Base64.urlDecode(bytes); } public static byte[] b64UrlDecode(CharSequence value) { return b64UrlDecode(utf8Encode(value)); } /** * UTF-8 encoding/decoding. Using a charset rather than `String.getBytes` is less forgiving * and will raise an exception for invalid data. */ public static byte[] utf8Encode(CharSequence string) { try { ByteBuffer bytes = UTF8.newEncoder().encode(CharBuffer.wrap(string)); byte[] bytesCopy = new byte[bytes.limit()]; System.arraycopy(bytes.array(), 0, bytesCopy, 0, bytes.limit()); return bytesCopy; } catch (CharacterCodingException e) { throw new RuntimeException(e); } } public static String utf8Decode(byte[] bytes) { return utf8Decode(ByteBuffer.wrap(bytes)); } public static String utf8Decode(ByteBuffer bytes) { try { return UTF8.newDecoder().decode(bytes).toString(); } catch (CharacterCodingException e) { throw new RuntimeException(e); } } public static char[] hexEncode(byte[] bytes) { return Hex.encode(bytes); } public static byte[] hexDecode(CharSequence s) { return Hex.decode(s); } // Substitute for Scala's Array.concat() public static byte[] concat(byte[]... arrays) { int size = 0; for (byte[] a: arrays) { size += a.length; } byte[] result = new byte[size]; int index = 0; for (byte[] a: arrays) { System.arraycopy(a, 0, result, index, a.length); index += a.length; } return result; } } class Base64 { private static byte EQUALS = (byte)'='; static byte[] encode(byte[] bytes) { return Base64Codec.encode(bytes); } static byte[] decode(byte[] bytes) { return Base64Codec.decode(bytes); } static byte[] urlEncode(byte[] bytes) { byte[] b64Bytes = Base64Codec.encodeBytesToBytes(bytes, 0, bytes.length, Base64Codec.URL_SAFE); int length = b64Bytes.length; while(b64Bytes[length - 1] == EQUALS) { length -= 1; } byte[] result = new byte[length]; System.arraycopy(b64Bytes, 0, result, 0, length); return result; } static byte[] urlDecode(byte[] b64) { // Pad with '=' as necessary before feeding to standard decoder byte[] b64Bytes = null; int lMod4 = b64.length % 4; if (lMod4 == 0) { b64Bytes = b64; } else if (lMod4 == 2) { b64Bytes = pad(b64, 2); } else if (lMod4 == 3) { b64Bytes = pad(b64, 1); } else { throw new IllegalArgumentException("Invalid Base64 string"); } return Base64Codec.decode(b64Bytes, 0, b64Bytes.length, Base64Codec.URL_SAFE); } private static byte[] pad(byte[] bytes, int n) { int l = bytes.length; byte[] padded = new byte[l + n]; System.arraycopy(bytes, 0, padded, 0, l); for (int i = l; i < l + n; i++) { padded[i] = EQUALS; } return padded; } } class Hex { private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; static char[] encode(byte[] bytes) { int nBytes = bytes.length; char[] result = new char[2 * nBytes]; int j = 0; for (int i = 0; i < nBytes; i++) { // Char for top 4 bits result[j] = HEX[(0xF0 & bytes[i]) >>> 4]; // Bottom 4 result[j + 1] = HEX[(0x0F & bytes[i])]; j += 2; } return result; } static byte[] decode(CharSequence s) { int nChars = s.length(); if (nChars % 2 != 0) { throw new IllegalArgumentException("Hex-encoded string must have an even number of characters"); } byte[] result = new byte[nChars / 2]; for (int i = 0; i < nChars; i += 2) { int msb = Character.digit(s.charAt(i), 16); int lsb = Character.digit(s.charAt(i + 1), 16); if (msb <= 0 || lsb <= 0) { throw new IllegalArgumentException("Non-hex character in input: " + s); } result[i / 2] = (byte) ((msb << 4) | lsb); } return result; } }