diff options
author | Marcello Stanisci <stanisci.m@gmail.com> | 2020-03-30 13:01:30 +0200 |
---|---|---|
committer | Marcello Stanisci <stanisci.m@gmail.com> | 2020-03-30 13:01:30 +0200 |
commit | 13a3d0b5cae312c5022cac49eb8d16bd7b570d2e (patch) | |
tree | c950d4c54deba41f8a2e92b4fd8bd7b035b0ceac /util | |
parent | 45d11337f6ae92c592eb4ad96422a5fa9b2a76f2 (diff) | |
download | libeufin-13a3d0b5cae312c5022cac49eb8d16bd7b570d2e.tar.gz libeufin-13a3d0b5cae312c5022cac49eb8d16bd7b570d2e.tar.bz2 libeufin-13a3d0b5cae312c5022cac49eb8d16bd7b570d2e.zip |
Add base32 decoder.
Thanks to GitHub user 'markov':
https://gist.github.com/markov/5206312
Diffstat (limited to 'util')
-rw-r--r-- | util/build.gradle | 1 | ||||
-rw-r--r-- | util/src/main/kotlin/CrockfordBase32.java | 655 | ||||
-rw-r--r-- | util/src/main/kotlin/strings.kt | 22 | ||||
-rw-r--r-- | util/src/test/kotlin/CryptoUtilTest.kt | 23 |
4 files changed, 672 insertions, 29 deletions
diff --git a/util/build.gradle b/util/build.gradle index 394342ba..8812b1e9 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -41,7 +41,6 @@ dependencies { implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.64' implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20' - implementation("com.msiops.ground:ground-crockford32:0.1") testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.3.50' diff --git a/util/src/main/kotlin/CrockfordBase32.java b/util/src/main/kotlin/CrockfordBase32.java new file mode 100644 index 00000000..ba5ec8ff --- /dev/null +++ b/util/src/main/kotlin/CrockfordBase32.java @@ -0,0 +1,655 @@ +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/strings.kt b/util/src/main/kotlin/strings.kt index 0092e633..d57b53ab 100644 --- a/util/src/main/kotlin/strings.kt +++ b/util/src/main/kotlin/strings.kt @@ -2,8 +2,6 @@ package tech.libeufin.util import org.apache.commons.codec.binary.Base32 import java.math.BigInteger import java.util.* -import com.msiops.ground.crockford32.Crockford32 - fun ByteArray.toHexString() : String { return this.joinToString("") { @@ -43,26 +41,6 @@ fun base64ToBytes(encoding: String): ByteArray { return Base64.getDecoder().decode(encoding) } -/** - * Crockford's base32 decoding. - */ -fun base32ToBytes(encoding: String): ByteArray { - val i: BigInteger = Crockford32.decode(encoding) - if ((i.bitLength() % 4) != 0) { - logger.debug("No extra 4-bits element added (bitLength: ${i.bitLength()})") - return i.toByteArray() - } - val extraByteBlob = i.toByteArray() - return Arrays.copyOfRange(extraByteBlob, 1, extraByteBlob.size) -} - -/** - * Crockford's base32 encoding. - */ -fun bytesToBase32(bytes: ByteArray): String { - return Crockford32.encode(BigInteger(bytes)) -} - fun BigInteger.toUnsignedHexString(): String { val signedValue = this.toByteArray() require(this.signum() > 0) { "number must be positive" } diff --git a/util/src/test/kotlin/CryptoUtilTest.kt b/util/src/test/kotlin/CryptoUtilTest.kt index e3d27ed0..2ef9f823 100644 --- a/util/src/test/kotlin/CryptoUtilTest.kt +++ b/util/src/test/kotlin/CryptoUtilTest.kt @@ -22,10 +22,8 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.junit.Test import tech.libeufin.util.* -import java.math.BigInteger import java.security.KeyFactory import java.security.KeyPairGenerator -import java.security.Security import java.security.interfaces.RSAPrivateCrtKey import java.security.spec.KeySpec import java.security.spec.X509EncodedKeySpec @@ -146,11 +144,14 @@ class CryptoUtilTest { @Test fun importEdDSAPublicKeyTest() { + val decoder = CrockfordBase32() val givenEnc = "XZH3P6NF9DSG3BH0C082X38N2RVK1RV2H24KF76028QBKDM24BCG" // import a public key - val spki = SubjectPublicKeyInfo(AlgorithmIdentifier( - EdECObjectIdentifiers.id_Ed25519), - base32ToBytes(givenEnc) + val spki = SubjectPublicKeyInfo( + AlgorithmIdentifier( + EdECObjectIdentifiers.id_Ed25519 + ), + decoder.decode(givenEnc.toByteArray(Charsets.UTF_8)) ) val ks: KeySpec = X509EncodedKeySpec(spki.encoded) val kpg = KeyFactory.getInstance( @@ -159,4 +160,14 @@ class CryptoUtilTest { ) kpg.generatePublic(ks) } -}
\ No newline at end of file + + @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") + } +} + |