From 076f0a443537fad83e6b652d551a19e7c18bdd8b Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Tue, 14 Feb 2017 11:07:56 +0000 Subject: [PATCH] Useful ByteArray and String {en,de}coding extension functions --- .../net/corda/core/crypto/EncodingUtils.kt | 88 +++++++++++++++++++ .../corda/core/crypto/EncodingUtilsTest.kt | 79 +++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt create mode 100644 core/src/test/kotlin/net/corda/core/crypto/EncodingUtilsTest.kt diff --git a/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt new file mode 100644 index 0000000000..d64804b6d9 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt @@ -0,0 +1,88 @@ +package net.corda.core.crypto + +import java.util.* + +/** + * This file includes useful encoding methods and extension functions for the most common encoding/decoding operations. + */ + +/** + * [ByteArray] + */ + +// Base58. +fun ByteArray.toBase58(): String = + Base58.encode(this) + +// Base64. +fun ByteArray.toBase64(): String = + Base64.getEncoder().encodeToString(this) + +// Hex (or Base16). +fun ByteArray.toHex(): String { + val result = StringBuffer() + forEach { + val octet = it.toInt() + val firstIndex = (octet and 0xF0).ushr(4) + val secondIndex = octet and 0x0F + result.append(HEX_ALPHABET[firstIndex]) + result.append(HEX_ALPHABET[secondIndex]) + } + return result.toString() +} + +/** + * [String] + */ + +// Base58-String to the actual real [String] using the UTF-8 character set. +fun String.base58ToRealString() = String(base58ToByteArray()) + +// Base64-String to the actual real [String] using the UTF-8 character set. +fun String.base64ToRealString() = String(base64ToByteArray()) + +// Hex-String to the actual real [String] using the UTF-8 character set. +fun String.hexToRealString() = String(hexToByteArray()) + +// Base58-String to [ByteArray]. +fun String.base58ToByteArray(): ByteArray { + try { + return Base58.decode(this) + } catch (afe: AddressFormatException) { + throw afe + } +} + +// Base64-String to [ByteArray]. +fun String.base64ToByteArray(): ByteArray { + try { + return Base64.getDecoder().decode(this) + } catch (iae: IllegalArgumentException) { + throw iae + } +} + +// Hex-String to [ByteArray]. Accept capital letters only. +fun String.hexToByteArray(): ByteArray { + if (this.length == 0) { + return ByteArray(0) + } else if (this.matches(HEX_REGEX)) { + val result = ByteArray(length / 2) + for (i in 0 until length step 2) { + val firstIndex = HEX_ALPHABET.indexOf(this[i]); + val secondIndex = HEX_ALPHABET.indexOf(this[i + 1]); + + val octet = firstIndex.shl(4).or(secondIndex) + result.set(i.shr(1), octet.toByte()) + } + return result + } else + throw IllegalArgumentException() +} + +/** + * Helper vars. + */ + +private val HEX_REGEX = Regex("-?[0-9A-F]+") // accept capital letters only +private val HEX_ALPHABET = "0123456789ABCDEF".toCharArray() diff --git a/core/src/test/kotlin/net/corda/core/crypto/EncodingUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/EncodingUtilsTest.kt new file mode 100644 index 0000000000..f8651fba60 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/crypto/EncodingUtilsTest.kt @@ -0,0 +1,79 @@ +package net.corda.core.crypto + +import org.junit.Test +import java.math.BigInteger +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +class EncodingUtilsTest { + @Test + fun testEncode() { + // Test Hello World + val testbytes = "Hello World".toByteArray() + assertEquals("JxF12TrwUP45BMd", testbytes.toBase58()) + assertEquals("SGVsbG8gV29ybGQ=", testbytes.toBase64()) + assertEquals("48656C6C6F20576F726C64", testbytes.toHex()) + + // Test empty encode + val emptyByteArray = ByteArray(0) + assertEquals("", emptyByteArray.toBase58()) + assertEquals("", emptyByteArray.toBase64()) + assertEquals("", emptyByteArray.toHex()) + + // Test 7 zero bytes + val sevenZeroByteArray = ByteArray(7) + assertEquals("1111111", sevenZeroByteArray.toBase58()) + assertEquals("AAAAAAAAAA==", sevenZeroByteArray.toBase64()) + assertEquals("00000000000000", sevenZeroByteArray.toHex()) + } + + @Test + fun testDecode() { + val testString = "Hello World" + val testBase58String = "JxF12TrwUP45BMd" + val testBase64String = "SGVsbG8gV29ybGQ=" + val testHexString = "48656C6C6F20576F726C64" + + assertEquals(testString, testBase58String.base58ToRealString()) + assertEquals(testString, testBase64String.base64ToRealString()) + assertEquals(testString, testHexString.hexToRealString()) + + // Test empty Strings + assertEquals("", "".base58ToRealString()) + assertEquals("", "".base64ToRealString()) + assertEquals("", "".hexToRealString()) + + try { + testString.base58ToRealString() + fail() + } catch (e: AddressFormatException) { + // expected + } + + try { + testString.base64ToRealString() + fail() + } catch (e: IllegalArgumentException) { + // expected + } + + try { + testString.hexToRealString() + fail() + } catch (e: IllegalArgumentException) { + // expected + } + + // not allow lowercase + try { + testString.toLowerCase().hexToRealString() + fail() + } catch (e: IllegalArgumentException) { + // expected + } + + } + +} \ No newline at end of file