|
| 1 | +package org.cufy.bcrypt |
| 2 | + |
| 3 | +private val D_MAP = byteArrayOf( |
| 4 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| 5 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| 6 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, 56, 57, |
| 7 | + 58, 59, 60, 61, 62, 63, -1, -1, -1, -2, -1, -1, -1, 2, 3, 4, 5, 6, 7, |
| 8 | + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, |
| 9 | + 26, 27, -1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, |
| 10 | + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53 |
| 11 | +) |
| 12 | + |
| 13 | +private val E_MAP = charArrayOf( |
| 14 | + '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', |
| 15 | + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', |
| 16 | + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', |
| 17 | + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', |
| 18 | + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', |
| 19 | + '6', '7', '8', '9' |
| 20 | +).map { it.code.toByte() } |
| 21 | + |
| 22 | +internal fun decodeRadix64(input: ByteArray): ByteArray? { |
| 23 | + // Ignore trailing '=' padding and whitespace from the input. |
| 24 | + var limit = input.size |
| 25 | + while (limit > 0) { |
| 26 | + val c = input[limit - 1] |
| 27 | + if (c != '='.code.toByte() && c != '\n'.code.toByte() && c != '\r'.code.toByte() && c != ' '.code.toByte() && c != '\t'.code.toByte()) { |
| 28 | + break |
| 29 | + } |
| 30 | + limit-- |
| 31 | + } |
| 32 | + |
| 33 | + // If the input includes whitespace, this output array will be longer than necessary. |
| 34 | + val out = ByteArray((limit * 6L / 8L).toInt()) |
| 35 | + var outCount = 0 |
| 36 | + var inCount = 0 |
| 37 | + |
| 38 | + var word = 0 |
| 39 | + for (pos in 0..<limit) { |
| 40 | + val c = input[pos] |
| 41 | + |
| 42 | + val bits: Int |
| 43 | + if (c == '.'.code.toByte() || c == '/'.code.toByte() || (c >= 'A'.code.toByte() && c <= 'z'.code.toByte()) || (c >= '0'.code.toByte() && c <= '9'.code.toByte())) { |
| 44 | + bits = D_MAP[c.toInt()].toInt() |
| 45 | + } else if (c == '\n'.code.toByte() || c == '\r'.code.toByte() || c == ' '.code.toByte() || c == '\t'.code.toByte()) { |
| 46 | + continue |
| 47 | + } else { |
| 48 | + throw IllegalArgumentException("invalid character to decode: $c") |
| 49 | + } |
| 50 | + |
| 51 | + // Append this char's 6 bits to the word. |
| 52 | + word = (word shl 6) or bits.toByte().toInt() |
| 53 | + |
| 54 | + // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes. |
| 55 | + inCount++ |
| 56 | + if (inCount % 4 == 0) { |
| 57 | + out[outCount++] = (word shr 16).toByte() |
| 58 | + out[outCount++] = (word shr 8).toByte() |
| 59 | + out[outCount++] = word.toByte() |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + val lastWordChars = inCount % 4 |
| 64 | + if (lastWordChars == 1) { |
| 65 | + // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail. |
| 66 | + return ByteArray(0) |
| 67 | + } else if (lastWordChars == 2) { |
| 68 | + // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits. |
| 69 | + word = word shl 12 |
| 70 | + out[outCount++] = (word shr 16).toByte() |
| 71 | + } else if (lastWordChars == 3) { |
| 72 | + // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits. |
| 73 | + word = word shl 6 |
| 74 | + out[outCount++] = (word shr 16).toByte() |
| 75 | + out[outCount++] = (word shr 8).toByte() |
| 76 | + } |
| 77 | + |
| 78 | + // If we sized our out array perfectly, we're done. |
| 79 | + if (outCount == out.size) return out |
| 80 | + |
| 81 | + // Copy the decoded bytes to a new, right-sized array. |
| 82 | + return out.copyOf(outCount) |
| 83 | +} |
| 84 | + |
| 85 | +internal fun encodeRadix64(input: ByteArray): ByteArray { |
| 86 | + val length = 4 * (input.size / 3) + (if (input.size % 3 == 0) 0 else input.size % 3 + 1) |
| 87 | + val out = ByteArray(length) |
| 88 | + var index = 0 |
| 89 | + val end = input.size - input.size % 3 |
| 90 | + var i = 0 |
| 91 | + while (i < end) { |
| 92 | + out[index++] = E_MAP[(input[i].toInt() and 0xff) shr 2] |
| 93 | + out[index++] = E_MAP[((input[i].toInt() and 0x03) shl 4) or ((input[i + 1].toInt() and 0xff) shr 4)] |
| 94 | + out[index++] = E_MAP[((input[i + 1].toInt() and 0x0f) shl 2) or ((input[i + 2].toInt() and 0xff) shr 6)] |
| 95 | + out[index++] = E_MAP[(input[i + 2].toInt() and 0x3f)] |
| 96 | + i += 3 |
| 97 | + } |
| 98 | + when (input.size % 3) { |
| 99 | + 1 -> { |
| 100 | + out[index++] = E_MAP[(input[end].toInt() and 0xff) shr 2] |
| 101 | + out[index] = E_MAP[(input[end].toInt() and 0x03) shl 4] |
| 102 | + } |
| 103 | + |
| 104 | + 2 -> { |
| 105 | + out[index++] = E_MAP[(input[end].toInt() and 0xff) shr 2] |
| 106 | + out[index++] = E_MAP[((input[end].toInt() and 0x03) shl 4) or ((input[end + 1].toInt() and 0xff) shr 4)] |
| 107 | + out[index] = E_MAP[((input[end + 1].toInt() and 0x0f) shl 2)] |
| 108 | + } |
| 109 | + } |
| 110 | + return out |
| 111 | +} |
0 commit comments