package net.torvald.terrarum.utils import kotlin.experimental.xor /** * Old-school passworld system using Base32 * * Created by minjaesong on 2017-05-02. */ object PasswordBase32 { private val stringSet = "YBNDRFG8EJKMCPQXOT+VWIS2A345H769=" private val substituteSet = hashMapOf( Pair('0', 'O'), Pair('1', 'I'), Pair('Z', '2') ) /* 0 x 1 6 2 4 3 3 4 1 */ val padLen = arrayOf(0, 6, 4, 3, 1) private val nullPw = byteArrayOf(0.toByte()) private fun encodeToLetters(byteArray: ByteArray, password: ByteArray): IntArray { val out = ArrayList() fun get(i: Int) = byteArray[i] xor (password[i % password.size]) /* 5 Bytes -> 8 Letters 0000 0000 | 1111 1111 | 2222 2222 | 3333 3333 | 4444 4444 AAAA ABBB | BBCC CCCD | DDDD EEEE | EFFF FFGG | GGGH HHHH */ // non-pads (0..byteArray.lastIndex - 5 step 5).forEach { /* A */ out.add(get(it).toInt().and(0xF8).ushr(3)) /* B */ out.add(get(it).toInt().and(7).shl(2) or get(it+1).toInt().and(0xC0).ushr(6)) /* C */ out.add(get(it+1).toInt().and(0x3E).ushr(1)) /* D */ out.add(get(it+1).toInt().and(1).shl(4) or get(it+2).toInt().and(0xF0).ushr(4)) /* E */ out.add(get(it+2).toInt().and(0xF).shl(1) or get(it+3).toInt().and(0x80).ushr(7)) /* F */ out.add(get(it+3).toInt().and(0x7C).ushr(2)) /* G */ out.add(get(it+3).toInt().and(3).shl(3) or get(it+4).toInt().and(0xE0).ushr(5)) /* H */ out.add(get(it+4).toInt().and(0x1F)) } // pads val residue = byteArray.size % 5 if (residue != 0){ val it = (byteArray.size / 5) * 5 // dark magic of integer division, let's hope the compiler won't "optimise" this... when (residue) { 1 -> { /* A */ out.add(get(it).toInt().and(0xF8).ushr(3)) /* B */ out.add(get(it).toInt().and(7).shl(2)) } 2 -> { /* A */ out.add(get(it).toInt().and(0xF8).ushr(3)) /* B */ out.add(get(it).toInt().and(7).shl(2) or get(it+1).toInt().and(0xC0).ushr(6)) /* C */ out.add(get(it+1).toInt().and(0x3E).ushr(1)) /* D */ out.add(get(it+1).toInt().and(1).shl(4)) } 3 -> { /* A */ out.add(get(it).toInt().and(0xF8).ushr(3)) /* B */ out.add(get(it).toInt().and(7).shl(2) or get(it+1).toInt().and(0xC0).ushr(6)) /* C */ out.add(get(it+1).toInt().and(0x3E).ushr(1)) /* D */ out.add(get(it+1).toInt().and(1).shl(4) or get(it+2).toInt().and(0xF0).ushr(4)) /* E */ out.add(get(it+2).toInt().and(0xF).shl(1)) } 4 -> { /* A */ out.add(get(it).toInt().and(0xF8).ushr(3)) /* B */ out.add(get(it).toInt().and(7).shl(2) or get(it+1).toInt().and(0xC0).ushr(6)) /* C */ out.add(get(it+1).toInt().and(0x3E).ushr(1)) /* D */ out.add(get(it+1).toInt().and(1).shl(4) or get(it+2).toInt().and(0xF0).ushr(4)) /* E */ out.add(get(it+2).toInt().and(0xF).shl(1) or get(it+3).toInt().and(0x80).ushr(7)) /* F */ out.add(get(it+3).toInt().and(0x7C).ushr(2)) /* G */ out.add(get(it+3).toInt().and(3).shl(3)) } } // append padding kotlin.repeat(padLen[residue], { out.add(32) }) } return out.toIntArray() } /** * * @param bytes size of multiple of five (5, 10, 15, 20, ...) is highly recommended to prevent * lengthy padding * @param password to encode resulting string using XOR Cipher to prevent unexperienced kids * from doing naughty things. Longer, the better. */ fun encode(bytes: ByteArray, password: ByteArray = nullPw): String { val plaintext = encodeToLetters(bytes, password) val sb = StringBuilder() plaintext.forEach { sb.append(stringSet[it]) } return sb.toString() } /** * @param input password input from the user. Will be automatically converted to uppercase and * will correct common mistakes. * @param outByteLength expected length of your decoded password. It is always a good idea to * suspect user inputs and sanitise them. */ fun decode(input: String, outByteLength: Int, password: ByteArray = nullPw): ByteArray { val buffer = ByteArray(outByteLength) var appendCount = 0 var input = input.toUpperCase() substituteSet.forEach { from, to -> input = input.replace(from, to) } fun append(byte: Int) { buffer[appendCount] = byte.toByte() xor (password[appendCount % password.size]) appendCount++ } fun sbyteOf(i: Int) = stringSet.indexOf(input[i]).and(0x1F) try { /* 8 Letters -> 5 Bytes 0000 0000 | 1111 1111 | 2222 2222 | 3333 3333 | 4444 4444 AAAA ABBB | BBCC CCCD | DDDD EEEE | EFFF FFGG | GGGH HHHH */ (0..input.lastIndex.plus(8) step 8).forEach { /* 0 */ append(sbyteOf(it+0).shl(3) or sbyteOf(it+1).ushr(2)) /* 1 */ append(sbyteOf(it+1).shl(6) or sbyteOf(it+2).shl(1) or sbyteOf(it+3).ushr(4)) /* 2 */ append(sbyteOf(it+3).shl(4) or sbyteOf(it+4).ushr(1)) /* 3 */ append(sbyteOf(it+4).shl(7) or sbyteOf(it+5).shl(2) or sbyteOf(it+6).ushr(3)) /* 4 */ append(sbyteOf(it+6).shl(5) or sbyteOf(it+7)) } } catch (endOfStream: ArrayIndexOutOfBoundsException) { } return buffer } }