summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorMarcello Stanisci <stanisci.m@gmail.com>2020-03-30 13:18:06 +0200
committerMarcello Stanisci <stanisci.m@gmail.com>2020-03-30 13:18:06 +0200
commit89a529c95caf2050803d314c8f70e8dec09f2c47 (patch)
tree32501fe1fb358810902ce96596a5a27da467b1a5 /util
parent13a3d0b5cae312c5022cac49eb8d16bd7b570d2e (diff)
downloadlibeufin-89a529c95caf2050803d314c8f70e8dec09f2c47.tar.gz
libeufin-89a529c95caf2050803d314c8f70e8dec09f2c47.tar.bz2
libeufin-89a529c95caf2050803d314c8f70e8dec09f2c47.zip
Use Florian's Crockford base32 encoder.
Diffstat (limited to 'util')
-rw-r--r--util/src/main/kotlin/CrockfordBase32.java655
-rw-r--r--util/src/main/kotlin/Encoding.kt134
-rw-r--r--util/src/test/kotlin/CryptoUtilTest.kt9
3 files changed, 138 insertions, 660 deletions
diff --git a/util/src/main/kotlin/CrockfordBase32.java b/util/src/main/kotlin/CrockfordBase32.java
deleted file mode 100644
index ba5ec8ff..00000000
--- a/util/src/main/kotlin/CrockfordBase32.java
+++ /dev/null
@@ -1,655 +0,0 @@
-import java.nio.charset.Charset;
-
-/**
- * <p>Provides Base32 encoding and decoding as defined by <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a>.
- * However it uses a custom alphabet first coined by Douglas Crockford. Only addition to the alphabet is that 'u' and
- * 'U' characters decode as if they were 'V' to improve mistakes by human input.<p/>
- * <p>
- * This class operates directly on byte streams, and not character streams.
- * </p>
- *
- * @version $Id: Base32.java 1382498 2012-09-09 13:41:55Z sebb $
- * @see <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a>
- * @see <a href="http://www.crockford.com/wrmg/base32.html">Douglas Crockford's Base32 Encoding</a>
- * @since 1.5
- */
-public class CrockfordBase32 {
-
- /**
- * Mask used to extract 8 bits, used in decoding bytes
- */
- protected static final int MASK_8BITS = 0xff;
- private static final Charset UTF8 = Charset.forName("UTF-8");
- private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
- /**
- * Defines the default buffer size - currently {@value}
- * - must be large enough for at least one encoded block+separator
- */
- private static final int DEFAULT_BUFFER_SIZE = 8192;
- /**
- * Mask used to extract 5 bits, used when encoding Base32 bytes
- */
- private static final int MASK_5BITS = 0x1f;
- /**
- * BASE32 characters are 5 bits in length.
- * They are formed by taking a block of five octets to form a 40-bit string,
- * which is converted into eight BASE32 characters.
- */
- private static final int BITS_PER_ENCODED_BYTE = 5;
- private static final int BYTES_PER_ENCODED_BLOCK = 8;
- private static final int BYTES_PER_UNENCODED_BLOCK = 5;
- private static final byte PAD = '=';
- /**
- * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet"
- * equivalents as specified in Table 3 of RFC 2045.
- */
- private static final byte[] ENCODE_TABLE = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
- 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'
- };
- /**
- * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
- * <code>decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length;</code>
- */
- private final int decodeSize;
- /**
- * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
- * <code>encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length;</code>
- */
- private final int encodeSize;
- /**
- * Wheather this encoder should use a padding character at the end of encoded Strings.
- */
- private final boolean usePaddingCharacter;
- /**
- * Buffer for streaming.
- */
- protected byte[] buffer;
- /**
- * Position where next character should be written in the buffer.
- */
- protected int pos;
- /**
- * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
- * and must be thrown away.
- */
- protected boolean eof;
- /**
- * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding.
- * This variable helps track that.
- */
- protected int modulus;
- /**
- * Place holder for the bytes we're dealing with for our based logic.
- * Bitwise operations store and extract the encoding or decoding from this variable.
- */
- private long bitWorkArea;
-
- public CrockfordBase32() {
- this(false);
- }
-
- /**
- * Creates a Base32 codec used for decoding and encoding.
- * <p>
- * When encoding the line length is 0 (no chunking).
- * </p>
- */
- public CrockfordBase32(boolean usePaddingCharacter) {
- this.usePaddingCharacter = usePaddingCharacter;
- this.encodeSize = BYTES_PER_ENCODED_BLOCK;
- this.decodeSize = this.encodeSize - 1;
- }
-
- private static byte decode(byte octet) {
- switch (octet) {
- case '0':
- case 'O':
- case 'o':
- return 0;
-
- case '1':
- case 'I':
- case 'i':
- case 'L':
- case 'l':
- return 1;
-
- case '2':
- return 2;
- case '3':
- return 3;
- case '4':
- return 4;
- case '5':
- return 5;
- case '6':
- return 6;
- case '7':
- return 7;
- case '8':
- return 8;
- case '9':
- return 9;
-
- case 'A':
- case 'a':
- return 10;
-
- case 'B':
- case 'b':
- return 11;
-
- case 'C':
- case 'c':
- return 12;
-
- case 'D':
- case 'd':
- return 13;
-
- case 'E':
- case 'e':
- return 14;
-
- case 'F':
- case 'f':
- return 15;
-
- case 'G':
- case 'g':
- return 16;
-
- case 'H':
- case 'h':
- return 17;
-
- case 'J':
- case 'j':
- return 18;
-
- case 'K':
- case 'k':
- return 19;
-
- case 'M':
- case 'm':
- return 20;
-
- case 'N':
- case 'n':
- return 21;
-
- case 'P':
- case 'p':
- return 22;
-
- case 'Q':
- case 'q':
- return 23;
-
- case 'R':
- case 'r':
- return 24;
-
- case 'S':
- case 's':
- return 25;
-
- case 'T':
- case 't':
- return 26;
-
- case 'U':
- case 'u':
- case 'V':
- case 'v':
- return 27;
-
- case 'W':
- case 'w':
- return 28;
-
- case 'X':
- case 'x':
- return 29;
-
- case 'Y':
- case 'y':
- return 30;
-
- case 'Z':
- case 'z':
- return 31;
-
- default:
- return -1;
- }
- }
-
- /**
- * Checks if a byte value is whitespace or not.
- * Whitespace is taken to mean: space, tab, CR, LF
- *
- * @param byteToCheck the byte to check
- * @return true if byte is whitespace, false otherwise
- */
- protected static boolean isWhiteSpace(byte byteToCheck) {
- switch (byteToCheck) {
- case ' ':
- case '\n':
- case '\r':
- case '\t':
- return true;
- default:
- return false;
- }
- }
-
- /**
- * Tests a given String to see if it contains only valid characters within the alphabet.
- * The method treats whitespace and PAD as valid.
- *
- * @param base32 String to test
- * @return <code>true</code> if all characters in the String are valid characters in the alphabet or if
- * the String is empty; <code>false</code>, otherwise
- * @see #isInAlphabet(byte[], boolean)
- */
- public static boolean isInAlphabet(String base32) {
- return isInAlphabet(base32.getBytes(UTF8), true);
- }
-
- /**
- * Tests a given byte array to see if it contains only valid characters within the alphabet.
- * The method optionally treats whitespace and pad as valid.
- *
- * @param arrayOctet byte array to test
- * @param allowWSPad if <code>true</code>, then whitespace and PAD are also allowed
- * @return <code>true</code> if all bytes are valid characters in the alphabet or if the byte array is empty;
- * <code>false</code>, otherwise
- */
- public static boolean isInAlphabet(byte[] arrayOctet, boolean allowWSPad) {
- for (int i = 0; i < arrayOctet.length; i++) {
- if (!isInAlphabet(arrayOctet[i]) &&
- (!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns whether or not the <code>octet</code> is in the Base32 alphabet.
- *
- * @param octet The value to test
- * @return <code>true</code> if the value is defined in the the Base32 alphabet <code>false</code> otherwise.
- */
- public static boolean isInAlphabet(byte octet) {
- return decode(octet) != -1;
- }
-
- /**
- * Returns the amount of buffered data available for reading.
- *
- * @return The amount of buffered data available for reading.
- */
- int available() { // package protected for access from I/O streams
- return buffer != null ? pos : 0;
- }
-
- /**
- * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
- */
- private void resizeBuffer() {
- if (buffer == null) {
- buffer = new byte[DEFAULT_BUFFER_SIZE];
- pos = 0;
- } else {
- byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
- System.arraycopy(buffer, 0, b, 0, buffer.length);
- buffer = b;
- }
- }
-
- /**
- * Ensure that the buffer has room for <code>size</code> bytes
- *
- * @param size minimum spare space required
- */
- protected void ensureBufferSize(int size) {
- if ((buffer == null) || (buffer.length < pos + size)) {
- resizeBuffer();
- }
- }
-
- /**
- * Extracts buffered data into the provided byte[] array, starting at position bPos,
- * up to a maximum of bAvail bytes. Returns how many bytes were actually extracted.
- *
- * @param b byte[] array to extract the buffered data into.
- * @return The number of bytes successfully extracted into the provided byte[] array.
- */
- int readResults(byte[] b) { // package protected for access from I/O streams
- if (buffer != null) {
- int len = available();
- System.arraycopy(buffer, 0, b, 0, len);
- buffer = null; // so hasData() will return false, and this method can return -1
- return len;
- }
- return eof ? -1 : 0;
- }
-
- /**
- * Resets this object to its initial newly constructed state.
- */
- private void reset() {
- buffer = null;
- pos = 0;
- modulus = 0;
- eof = false;
- }
-
- /**
- * Encodes a String containing characters in the Base32 alphabet.
- *
- * @param pArray A String containing Base32 character data
- * @return A String containing only Base32 character data
- */
- public String encodeToString(String pArray) {
- return encodeToString(pArray.getBytes(UTF8));
- }
-
- /**
- * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
- *
- * @param pArray a byte array containing binary data
- * @return A String containing only Base32 character data
- */
- public String encodeToString(byte[] pArray) {
- return new String(encode(pArray), UTF8);
- }
-
- /**
- * Encodes a String containing characters in the Base32 alphabet.
- *
- * @param pArray A String containing Base32 character data
- * @return A UTF-8 decoded String
- */
- public String decodeToString(String pArray) {
- return decodeToString(pArray.getBytes(UTF8));
- }
-
- /**
- * Decodes a byte[] containing binary data, into a String containing UTF-8 decoded String.
- *
- * @param pArray a byte array containing binary data
- * @return A UTF-8 decoded String
- */
- public String decodeToString(byte[] pArray) {
- return new String(decode(pArray), UTF8);
- }
-
- /**
- * Decodes a String containing characters in the Base-N alphabet.
- *
- * @param pArray A String containing Base-N character data
- * @return a byte array containing binary data
- */
- public byte[] decode(String pArray) {
- return decode(pArray.getBytes(UTF8));
- }
-
- /**
- * Encodes a String containing characters in the Base32 alphabet.
- *
- * @param pArray A String containing Base-N character data
- * @return a byte array containing binary data
- */
- public byte[] encode(String pArray) {
- return encode(pArray.getBytes(UTF8));
- }
-
- /**
- * Decodes a byte[] containing characters in the Base-N alphabet.
- *
- * @param pArray A byte array containing Base-N character data
- * @return a byte array containing binary data
- */
- public byte[] decode(byte[] pArray) {
- reset();
- if (pArray == null || pArray.length == 0) {
- return pArray;
- }
- decode(pArray, 0, pArray.length);
- decode(pArray, 0, -1); // Notify decoder of EOF.
- byte[] result = new byte[pos];
- readResults(result);
- return result;
- }
-
- // The static final fields above are used for the original static byte[] methods on Base32.
- // The private member fields below are used with the new streaming approach, which requires
- // some state be preserved between calls of encode() and decode().
-
- /**
- * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
- *
- * @param pArray a byte array containing binary data
- * @return A byte array containing only the basen alphabetic character data
- */
- public byte[] encode(byte[] pArray) {
- reset();
- if (pArray == null || pArray.length == 0) {
- return pArray;
- }
- encode(pArray, 0, pArray.length);
- encode(pArray, 0, -1); // Notify encoder of EOF.
- byte[] buf = new byte[pos];
- readResults(buf);
- return buf;
- }
-
- /**
- * Calculates the amount of space needed to encode the supplied array.
- *
- * @param pArray byte[] array which will later be encoded
- * @return amount of space needed to encoded the supplied array.
- * Returns a long since a max-len array will require > Integer.MAX_VALUE
- */
- public long getEncodedLength(byte[] pArray) {
- // Calculate non-chunked size - rounded up to allow for padding
- // cast to long is needed to avoid possibility of overflow
- long len = ((pArray.length + BYTES_PER_UNENCODED_BLOCK - 1) / BYTES_PER_UNENCODED_BLOCK) * (long) BYTES_PER_ENCODED_BLOCK;
- return len;
- }
-
- /**
- * <p>
- * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
- * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
- * call is not necessary when decoding, but it doesn't hurt, either.
- * </p>
- * <p>
- * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
- * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
- * garbage-out philosophy: it will not check the provided data for validity.
- * </p>
- *
- * @param in byte[] array of ascii data to Base32 decode.
- * @param inPos Position to start reading data from.
- * @param inAvail Amount of bytes available from input for encoding.
- * <p/>
- * Output is written to {@link #buffer} as 8-bit octets, using {@link #pos} as the buffer position
- */
- void decode(byte[] in, int inPos, int inAvail) { // package protected for access from I/O streams
- if (eof) {
- return;
- }
- if (inAvail < 0) {
- eof = true;
- }
- for (int i = 0; i < inAvail; i++) {
- byte b = in[inPos++];
- if (b == PAD) {
- // We're done.
- eof = true;
- break;
- } else {
- ensureBufferSize(decodeSize);
- if (isInAlphabet(b)) {
- int result = decode(b);
- modulus = (modulus + 1) % BYTES_PER_ENCODED_BLOCK;
- bitWorkArea = (bitWorkArea << BITS_PER_ENCODED_BYTE) + result; // collect decoded bytes
- if (modulus == 0) { // we can output the 5 bytes
- buffer[pos++] = (byte) ((bitWorkArea >> 32) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 24) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
- buffer[pos++] = (byte) (bitWorkArea & MASK_8BITS);
- }
- }
- }
- }
-
- // Two forms of EOF as far as Base32 decoder is concerned: actual
- // EOF (-1) and first time '=' character is encountered in stream.
- // This approach makes the '=' padding characters completely optional.
- if (eof && modulus >= 2) { // if modulus < 2, nothing to do
- ensureBufferSize(decodeSize);
-
- // we ignore partial bytes, i.e. only multiples of 8 count
- switch (modulus) {
- case 2: // 10 bits, drop 2 and output one byte
- buffer[pos++] = (byte) ((bitWorkArea >> 2) & MASK_8BITS);
- break;
- case 3: // 15 bits, drop 7 and output 1 byte
- buffer[pos++] = (byte) ((bitWorkArea >> 7) & MASK_8BITS);
- break;
- case 4: // 20 bits = 2*8 + 4
- bitWorkArea = bitWorkArea >> 4; // drop 4 bits
- buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
- break;
- case 5: // 25bits = 3*8 + 1
- bitWorkArea = bitWorkArea >> 1;
- buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
- break;
- case 6: // 30bits = 3*8 + 6
- bitWorkArea = bitWorkArea >> 6;
- buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
- break;
- case 7: // 35 = 4*8 +3
- bitWorkArea = bitWorkArea >> 3;
- buffer[pos++] = (byte) ((bitWorkArea >> 24) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
- buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
- break;
- }
- }
- }
-
- /**
- * <p>
- * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
- * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last
- * remaining bytes (if not multiple of 5).
- * </p>
- *
- * @param in byte[] array of binary data to Base32 encode.
- * @param inPos Position to start reading data from.
- * @param inAvail Amount of bytes available from input for encoding.
- */
- void encode(byte[] in, int inPos, int inAvail) { // package protected for access from I/O streams
- if (eof) {
- return;
- }
- // inAvail < 0 is how we're informed of EOF in the underlying data we're
- // encoding.
- if (inAvail < 0) {
- eof = true;
- if (0 == modulus) {
- return; // no leftovers to process
- }
- ensureBufferSize(encodeSize);
- int savedPos = pos;
- switch (modulus) { // % 5
- case 1: // Only 1 octet; take top 5 bits then remainder
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 2) & MASK_5BITS]; // 5-3=2
- if (usePaddingCharacter) {
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- }
- break;
-
- case 2: // 2 octets = 16 bits to use
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4
- if (usePaddingCharacter) {
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- }
- break;
- case 3: // 3 octets = 24 bits to use
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1
- if (usePaddingCharacter) {
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- buffer[pos++] = PAD;
- }
- break;
- case 4: // 4 octets = 32 bits to use
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3
- if (usePaddingCharacter) {
- buffer[pos++] = PAD;
- }
- break;
- }
- } else {
- for (int i = 0; i < inAvail; i++) {
- ensureBufferSize(encodeSize);
- modulus = (modulus + 1) % BYTES_PER_UNENCODED_BLOCK;
- int b = in[inPos++];
- if (b < 0) {
- b += 256;
- }
- bitWorkArea = (bitWorkArea << 8) + b; // BITS_PER_BYTE
- if (0 == modulus) { // we have enough bytes to create our output
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 35) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 30) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 25) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 20) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 15) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 10) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 5) & MASK_5BITS];
- buffer[pos++] = ENCODE_TABLE[(int) bitWorkArea & MASK_5BITS];
- }
- }
- }
- }
-
-} \ No newline at end of file
diff --git a/util/src/main/kotlin/Encoding.kt b/util/src/main/kotlin/Encoding.kt
new file mode 100644
index 00000000..25a59bef
--- /dev/null
+++ b/util/src/main/kotlin/Encoding.kt
@@ -0,0 +1,134 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.crypto
+
+import java.io.ByteArrayOutputStream
+
+class EncodingException : Exception("Invalid encoding")
+
+
+object Base32Crockford {
+
+ private fun ByteArray.getIntAt(index: Int): Int {
+ val x = this[index].toInt()
+ return if (x >= 0) x else (x + 256)
+ }
+
+ private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
+
+ fun encode(data: ByteArray): String {
+ val sb = StringBuilder()
+ val size = data.size
+ var bitBuf = 0
+ var numBits = 0
+ var pos = 0
+ while (pos < size || numBits > 0) {
+ if (pos < size && numBits < 5) {
+ val d = data.getIntAt(pos++)
+ bitBuf = (bitBuf shl 8) or d
+ numBits += 8
+ }
+ if (numBits < 5) {
+ // zero-padding
+ bitBuf = bitBuf shl (5 - numBits)
+ numBits = 5
+ }
+ val v = bitBuf.ushr(numBits - 5) and 31
+ sb.append(encTable[v])
+ numBits -= 5
+ }
+ return sb.toString()
+ }
+
+ fun decode(encoded: String, out: ByteArrayOutputStream) {
+ val size = encoded.length
+ var bitpos = 0
+ var bitbuf = 0
+ var readPosition = 0
+
+ while (readPosition < size || bitpos > 0) {
+ //println("at position $readPosition with bitpos $bitpos")
+ if (readPosition < size) {
+ val v = getValue(encoded[readPosition++])
+ bitbuf = (bitbuf shl 5) or v
+ bitpos += 5
+ }
+ while (bitpos >= 8) {
+ val d = (bitbuf ushr (bitpos - 8)) and 0xFF
+ out.write(d)
+ bitpos -= 8
+ }
+ if (readPosition == size && bitpos > 0) {
+ bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF
+ bitpos = if (bitbuf == 0) 0 else 8
+ }
+ }
+ }
+
+ fun decode(encoded: String): ByteArray {
+ val out = ByteArrayOutputStream()
+ decode(encoded, out)
+ return out.toByteArray()
+ }
+
+ private fun getValue(chr: Char): Int {
+ var a = chr
+ when (a) {
+ 'O', 'o' -> a = '0'
+ 'i', 'I', 'l', 'L' -> a = '1'
+ 'u', 'U' -> a = 'V'
+ }
+ if (a in '0'..'9')
+ return a - '0'
+ if (a in 'a'..'z')
+ a = Character.toUpperCase(a)
+ var dec = 0
+ if (a in 'A'..'Z') {
+ if ('I' < a) dec++
+ if ('L' < a) dec++
+ if ('O' < a) dec++
+ if ('U' < a) dec++
+ return a - 'A' + 10 - dec
+ }
+ throw EncodingException()
+ }
+
+ /**
+ * Compute the length of the resulting string when encoding data of the given size
+ * in bytes.
+ *
+ * @param dataSize size of the data to encode in bytes
+ * @return size of the string that would result from encoding
+ */
+ @Suppress("unused")
+ fun calculateEncodedStringLength(dataSize: Int): Int {
+ return (dataSize * 8 + 4) / 5
+ }
+
+ /**
+ * Compute the length of the resulting data in bytes when decoding a (valid) string of the
+ * given size.
+ *
+ * @param stringSize size of the string to decode
+ * @return size of the resulting data in bytes
+ */
+ @Suppress("unused")
+ fun calculateDecodedDataLength(stringSize: Int): Int {
+ return stringSize * 5 / 8
+ }
+}
+
diff --git a/util/src/test/kotlin/CryptoUtilTest.kt b/util/src/test/kotlin/CryptoUtilTest.kt
index 2ef9f823..735db865 100644
--- a/util/src/test/kotlin/CryptoUtilTest.kt
+++ b/util/src/test/kotlin/CryptoUtilTest.kt
@@ -17,6 +17,7 @@
* <http://www.gnu.org/licenses/>
*/
+import net.taler.wallet.crypto.Base32Crockford
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
@@ -30,6 +31,7 @@ import java.security.spec.X509EncodedKeySpec
import javax.crypto.EncryptedPrivateKeyInfo
import kotlin.test.assertEquals
import kotlin.test.assertTrue
+
class CryptoUtilTest {
@Test
@@ -144,14 +146,13 @@ class CryptoUtilTest {
@Test
fun importEdDSAPublicKeyTest() {
- val decoder = CrockfordBase32()
val givenEnc = "XZH3P6NF9DSG3BH0C082X38N2RVK1RV2H24KF76028QBKDM24BCG"
// import a public key
val spki = SubjectPublicKeyInfo(
AlgorithmIdentifier(
EdECObjectIdentifiers.id_Ed25519
),
- decoder.decode(givenEnc.toByteArray(Charsets.UTF_8))
+ Base32Crockford.decode(givenEnc)
)
val ks: KeySpec = X509EncodedKeySpec(spki.encoded)
val kpg = KeyFactory.getInstance(
@@ -164,10 +165,8 @@ class CryptoUtilTest {
@Test
// from Crockford32 encoding to binary.
fun base32ToBytesTest() {
- val decoder = CrockfordBase32()
- // blob
val blob = "blob".toByteArray(Charsets.UTF_8)
- assert(decoder.decode("C9P6YRG".toByteArray(Charsets.UTF_8)).toString(Charsets.UTF_8) == "blob")
+ assert(Base32Crockford.decode("C9P6YRG").toString(Charsets.UTF_8) == "blob")
}
}