001/* 002 * Copyright 2012-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2012-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util; 037 038 039 040import java.io.IOException; 041import java.text.ParseException; 042 043import static com.unboundid.util.UtilityMessages.*; 044 045 046 047/** 048 * This class provides methods for encoding and decoding data in base32 as 049 * defined in <A HREF="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</A>. It 050 * provides a somewhat compact way of representing binary data using only 051 * printable characters (a subset of ASCII letters and numeric digits selected 052 * to avoid ambiguity, like confusion between the number 1 and the uppercase 053 * letter I, and between the number 0 and the uppercase letter O). It uses a 054 * five-bit encoding mechanism in which every five bytes of raw data is 055 * converted into eight bytes of base32-encoded data. 056 * <BR><BR> 057 * <H2>Example</H2> 058 * The following examples demonstrate the process for base32-encoding raw data, 059 * and for decoding a string containing base32-encoded data back to the raw 060 * data used to create it: 061 * <PRE> 062 * // Base32-encode some raw data: 063 * String base32String = Base32.encode(rawDataBytes); 064 * 065 * // Decode a base32 string back to raw data: 066 * byte[] decodedRawDataBytes; 067 * try 068 * { 069 * decodedRawDataBytes = Base32.decode(base32String); 070 * } 071 * catch (ParseException pe) 072 * { 073 * // The string did not represent a valid base32 encoding. 074 * decodedRawDataBytes = null; 075 * } 076 * </PRE> 077 */ 078@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 079public final class Base32 080{ 081 /** 082 * The set of characters in the base32 alphabet. 083 */ 084 @NotNull private static final char[] BASE32_ALPHABET = 085 ("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567").toCharArray(); 086 087 088 089 /** 090 * Prevent this class from being instantiated. 091 */ 092 private Base32() 093 { 094 // No implementation is required. 095 } 096 097 098 099 /** 100 * Encodes the UTF-8 representation of the provided string in base32 format. 101 * 102 * @param data The raw data to be encoded. It must not be {@code null}. 103 * 104 * @return The base32-encoded representation of the provided data. 105 */ 106 @NotNull() 107 public static String encode(@NotNull final String data) 108 { 109 Validator.ensureNotNull(data); 110 111 return encode(StaticUtils.getBytes(data)); 112 } 113 114 115 116 /** 117 * Encodes the provided data in base32 format. 118 * 119 * @param data The raw data to be encoded. It must not be {@code null}. 120 * 121 * @return The base32-encoded representation of the provided data. 122 */ 123 @NotNull() 124 public static String encode(@NotNull final byte[] data) 125 { 126 Validator.ensureNotNull(data); 127 128 final StringBuilder buffer = new StringBuilder(4*data.length/3+1); 129 encodeInternal(data, 0, data.length, buffer); 130 return buffer.toString(); 131 } 132 133 134 135 /** 136 * Appends a base32-encoded version of the contents of the provided buffer 137 * (using a UTF-8 representation) to the given buffer. 138 * 139 * @param data The raw data to be encoded. It must not be {@code null}. 140 * @param buffer The buffer to which the base32-encoded data is to be 141 * written. 142 */ 143 public static void encode(@NotNull final String data, 144 @NotNull final StringBuilder buffer) 145 { 146 Validator.ensureNotNull(data); 147 148 encode(StaticUtils.getBytes(data), buffer); 149 } 150 151 152 153 /** 154 * Appends a base32-encoded version of the contents of the provided buffer 155 * (using a UTF-8 representation) to the given buffer. 156 * 157 * @param data The raw data to be encoded. It must not be {@code null}. 158 * @param buffer The buffer to which the base32-encoded data is to be 159 * written. 160 */ 161 public static void encode(@NotNull final String data, 162 @NotNull final ByteStringBuffer buffer) 163 { 164 Validator.ensureNotNull(data); 165 166 encode(StaticUtils.getBytes(data), buffer); 167 } 168 169 170 171 /** 172 * Appends a base32-encoded representation of the provided data to the given 173 * buffer. 174 * 175 * @param data The raw data to be encoded. It must not be {@code null}. 176 * @param buffer The buffer to which the base32-encoded data is to be 177 * written. 178 */ 179 public static void encode(@NotNull final byte[] data, 180 @NotNull final StringBuilder buffer) 181 { 182 encodeInternal(data, 0, data.length, buffer); 183 } 184 185 186 187 /** 188 * Appends a base32-encoded representation of the provided data to the given 189 * buffer. 190 * 191 * @param data The array containing the raw data to be encoded. It must 192 * not be {@code null}. 193 * @param off The offset in the array at which the data to encode begins. 194 * @param length The number of bytes to be encoded. 195 * @param buffer The buffer to which the base32-encoded data is to be 196 * written. 197 */ 198 public static void encode(@NotNull final byte[] data, final int off, 199 final int length, 200 @NotNull final StringBuilder buffer) 201 { 202 encodeInternal(data, off, length, buffer); 203 } 204 205 206 207 /** 208 * Appends a base32-encoded representation of the provided data to the given 209 * buffer. 210 * 211 * @param data The raw data to be encoded. It must not be {@code null}. 212 * @param buffer The buffer to which the base32-encoded data is to be 213 * written. 214 */ 215 public static void encode(@NotNull final byte[] data, 216 @NotNull final ByteStringBuffer buffer) 217 { 218 encodeInternal(data, 0, data.length, buffer); 219 } 220 221 222 223 /** 224 * Appends a base32-encoded representation of the provided data to the given 225 * buffer. 226 * 227 * @param data The raw data to be encoded. It must not be {@code null}. 228 * @param off The offset in the array at which the data to encode begins. 229 * @param length The number of bytes to be encoded. 230 * @param buffer The buffer to which the base32-encoded data is to be 231 * written. 232 */ 233 public static void encode(@NotNull final byte[] data, final int off, 234 final int length, 235 @NotNull final ByteStringBuffer buffer) 236 { 237 encodeInternal(data, off, length, buffer); 238 } 239 240 241 242 /** 243 * Appends a base32-encoded representation of the provided data to the given 244 * buffer. 245 * 246 * @param data The raw data to be encoded. It must not be {@code null}. 247 * @param off The offset in the array at which the data to encode begins. 248 * @param length The number of bytes to be encoded. 249 * @param buffer The buffer to which the base32-encoded data is to be 250 * written. 251 */ 252 private static void encodeInternal(@NotNull final byte[] data, final int off, 253 final int length, 254 @NotNull final Appendable buffer) 255 { 256 Validator.ensureNotNull(data); 257 Validator.ensureTrue(data.length >= off); 258 Validator.ensureTrue(data.length >= (off+length)); 259 260 if (length == 0) 261 { 262 return; 263 } 264 265 try 266 { 267 int pos = off; 268 for (int i=0; i < (length / 5); i++) 269 { 270 final long longValue = 271 (((data[pos++] & 0xFFL) << 32) | 272 ((data[pos++] & 0xFFL) << 24) | 273 ((data[pos++] & 0xFFL) << 16) | 274 ((data[pos++] & 0xFFL) << 8) | 275 (data[pos++] & 0xFFL)); 276 277 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 35) & 0x1FL)]); 278 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 30) & 0x1FL)]); 279 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 25) & 0x1FL)]); 280 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 20) & 0x1FL)]); 281 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 15) & 0x1FL)]); 282 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 10) & 0x1FL)]); 283 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 5) & 0x1FL)]); 284 buffer.append(BASE32_ALPHABET[(int) (longValue & 0x1FL)]); 285 } 286 287 switch ((off+length) - pos) 288 { 289 case 1: 290 long longValue = ((data[pos] & 0xFFL) << 32); 291 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 35) & 0x1FL)]); 292 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 30) & 0x1FL)]); 293 buffer.append("======"); 294 return; 295 296 case 2: 297 longValue = (((data[pos++] & 0xFFL) << 32) | 298 ((data[pos] & 0xFFL) << 24)); 299 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 35) & 0x1FL)]); 300 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 30) & 0x1FL)]); 301 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 25) & 0x1FL)]); 302 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 20) & 0x1FL)]); 303 buffer.append("===="); 304 return; 305 306 case 3: 307 longValue = (((data[pos++] & 0xFFL) << 32) | 308 ((data[pos++] & 0xFFL) << 24) | 309 ((data[pos] & 0xFFL) << 16)); 310 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 35) & 0x1FL)]); 311 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 30) & 0x1FL)]); 312 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 25) & 0x1FL)]); 313 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 20) & 0x1FL)]); 314 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 15) & 0x1FL)]); 315 buffer.append("==="); 316 return; 317 318 case 4: 319 longValue = (((data[pos++] & 0xFFL) << 32) | 320 ((data[pos++] & 0xFFL) << 24) | 321 ((data[pos++] & 0xFFL) << 16) | 322 ((data[pos] & 0xFFL) << 8)); 323 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 35) & 0x1FL)]); 324 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 30) & 0x1FL)]); 325 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 25) & 0x1FL)]); 326 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 20) & 0x1FL)]); 327 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 15) & 0x1FL)]); 328 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 10) & 0x1FL)]); 329 buffer.append(BASE32_ALPHABET[(int) ((longValue >> 5) & 0x1FL)]); 330 buffer.append("="); 331 return; 332 } 333 } 334 catch (final IOException ioe) 335 { 336 Debug.debugException(ioe); 337 338 // This should never happen. 339 throw new RuntimeException(ioe.getMessage(), ioe); 340 } 341 } 342 343 344 345 /** 346 * Decodes the contents of the provided base32-encoded string. 347 * 348 * @param data The base32-encoded string to decode. It must not be 349 * {@code null}. 350 * 351 * @return A byte array containing the decoded data. 352 * 353 * @throws ParseException If the contents of the provided string cannot be 354 * parsed as base32-encoded data. 355 */ 356 @NotNull() 357 public static byte[] decode(@NotNull final String data) 358 throws ParseException 359 { 360 Validator.ensureNotNull(data); 361 362 final int length = data.length(); 363 if (length == 0) 364 { 365 return StaticUtils.NO_BYTES; 366 } 367 368 if ((length % 8) != 0) 369 { 370 throw new ParseException(ERR_BASE32_DECODE_INVALID_LENGTH.get(), length); 371 } 372 373 final ByteStringBuffer buffer = new ByteStringBuffer(5 * (length / 8)); 374 375 int stringPos = 0; 376 while (stringPos < length) 377 { 378 long longValue = 0x00; 379 for (int i=0; i < 8; i++) 380 { 381 longValue <<= 5; 382 switch (data.charAt(stringPos++)) 383 { 384 case 'A': 385 case 'a': 386 longValue |= 0x00L; 387 break; 388 case 'B': 389 case 'b': 390 longValue |= 0x01L; 391 break; 392 case 'C': 393 case 'c': 394 longValue |= 0x02L; 395 break; 396 case 'D': 397 case 'd': 398 longValue |= 0x03L; 399 break; 400 case 'E': 401 case 'e': 402 longValue |= 0x04L; 403 break; 404 case 'F': 405 case 'f': 406 longValue |= 0x05L; 407 break; 408 case 'G': 409 case 'g': 410 longValue |= 0x06L; 411 break; 412 case 'H': 413 case 'h': 414 longValue |= 0x07L; 415 break; 416 case 'I': 417 case 'i': 418 longValue |= 0x08L; 419 break; 420 case 'J': 421 case 'j': 422 longValue |= 0x09L; 423 break; 424 case 'K': 425 case 'k': 426 longValue |= 0x0AL; 427 break; 428 case 'L': 429 case 'l': 430 longValue |= 0x0BL; 431 break; 432 case 'M': 433 case 'm': 434 longValue |= 0x0CL; 435 break; 436 case 'N': 437 case 'n': 438 longValue |= 0x0DL; 439 break; 440 case 'O': 441 case 'o': 442 longValue |= 0x0EL; 443 break; 444 case 'P': 445 case 'p': 446 longValue |= 0x0FL; 447 break; 448 case 'Q': 449 case 'q': 450 longValue |= 0x10L; 451 break; 452 case 'R': 453 case 'r': 454 longValue |= 0x11L; 455 break; 456 case 'S': 457 case 's': 458 longValue |= 0x12L; 459 break; 460 case 'T': 461 case 't': 462 longValue |= 0x13L; 463 break; 464 case 'U': 465 case 'u': 466 longValue |= 0x14L; 467 break; 468 case 'V': 469 case 'v': 470 longValue |= 0x15L; 471 break; 472 case 'W': 473 case 'w': 474 longValue |= 0x16L; 475 break; 476 case 'X': 477 case 'x': 478 longValue |= 0x17L; 479 break; 480 case 'Y': 481 case 'y': 482 longValue |= 0x18L; 483 break; 484 case 'Z': 485 case 'z': 486 longValue |= 0x19L; 487 break; 488 case '2': 489 longValue |= 0x1AL; 490 break; 491 case '3': 492 longValue |= 0x1BL; 493 break; 494 case '4': 495 longValue |= 0x1CL; 496 break; 497 case '5': 498 longValue |= 0x1DL; 499 break; 500 case '6': 501 longValue |= 0x1EL; 502 break; 503 case '7': 504 longValue |= 0x1FL; 505 break; 506 507 case '=': 508 switch (length - stringPos) 509 { 510 case 0: 511 // The string ended with a single equal sign, so there are 512 // four bytes left. 513 buffer.append((byte) ((longValue >> 32) & 0xFFL)); 514 buffer.append((byte) ((longValue >> 24) & 0xFFL)); 515 buffer.append((byte) ((longValue >> 16) & 0xFFL)); 516 buffer.append((byte) ((longValue >> 8) & 0xFFL)); 517 return buffer.toByteArray(); 518 519 case 2: 520 // The string ended with three equal signs, so there are three 521 // bytes left. 522 longValue <<= 10; 523 buffer.append((byte) ((longValue >> 32) & 0xFFL)); 524 buffer.append((byte) ((longValue >> 24) & 0xFFL)); 525 buffer.append((byte) ((longValue >> 16) & 0xFFL)); 526 return buffer.toByteArray(); 527 528 case 3: 529 // The string ended with four equal signs, so there are two 530 // bytes left. 531 longValue <<= 15; 532 buffer.append((byte) ((longValue >> 32) & 0xFFL)); 533 buffer.append((byte) ((longValue >> 24) & 0xFFL)); 534 return buffer.toByteArray(); 535 536 case 5: 537 // The string ended with six equal signs, so there is one byte 538 // left. 539 longValue <<= 25; 540 buffer.append((byte) ((longValue >> 32) & 0xFFL)); 541 return buffer.toByteArray(); 542 543 default: 544 throw new ParseException( 545 ERR_BASE32_DECODE_UNEXPECTED_EQUAL.get((stringPos-1)), 546 (stringPos-1)); 547 } 548 549 default: 550 throw new ParseException( 551 ERR_BASE32_DECODE_UNEXPECTED_CHAR.get( 552 data.charAt(stringPos-1)), 553 (stringPos-1)); 554 } 555 } 556 557 buffer.append((byte) ((longValue >> 32) & 0xFFL)); 558 buffer.append((byte) ((longValue >> 24) & 0xFFL)); 559 buffer.append((byte) ((longValue >> 16) & 0xFFL)); 560 buffer.append((byte) ((longValue >> 8) & 0xFFL)); 561 buffer.append((byte) (longValue & 0xFFL)); 562 } 563 564 return buffer.toByteArray(); 565 } 566 567 568 569 /** 570 * Decodes the contents of the provided base32-encoded string to a string 571 * containing the raw data using the UTF-8 encoding. 572 * 573 * @param data The base32-encoded string to decode. It must not be 574 * {@code null}. 575 * 576 * @return A string containing the decoded data. 577 * 578 * @throws ParseException If the contents of the provided string cannot be 579 * parsed as base32-encoded data using the UTF-8 580 * encoding. 581 */ 582 @NotNull() 583 public static String decodeToString(@NotNull final String data) 584 throws ParseException 585 { 586 Validator.ensureNotNull(data); 587 588 final byte[] decodedBytes = decode(data); 589 return StaticUtils.toUTF8String(decodedBytes); 590 } 591}