Created
February 4, 2019 17:05
-
-
Save curioustorvald/db1fa5c4f8a4343c9206a06fd157ca30 to your computer and use it in GitHub Desktop.
Byte-to-Password encoder using Base32 using your plaintext and passphrase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<Int>() | |
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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment