mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 17:18:52 -06:00
Implement basic cryptography functions
This commit is contained in:
2048
uhabits-core/assets/main/bip39/chinese_simplified.txt
Normal file
2048
uhabits-core/assets/main/bip39/chinese_simplified.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/chinese_traditional.txt
Normal file
2048
uhabits-core/assets/main/bip39/chinese_traditional.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/czech.txt
Normal file
2048
uhabits-core/assets/main/bip39/czech.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/en_US.txt
Normal file
2048
uhabits-core/assets/main/bip39/en_US.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/french.txt
Normal file
2048
uhabits-core/assets/main/bip39/french.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/italian.txt
Normal file
2048
uhabits-core/assets/main/bip39/italian.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/japanese.txt
Normal file
2048
uhabits-core/assets/main/bip39/japanese.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/korean.txt
Normal file
2048
uhabits-core/assets/main/bip39/korean.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/portuguese.txt
Normal file
2048
uhabits-core/assets/main/bip39/portuguese.txt
Normal file
File diff suppressed because it is too large
Load Diff
2048
uhabits-core/assets/main/bip39/spanish.txt
Normal file
2048
uhabits-core/assets/main/bip39/spanish.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.crypto
|
||||||
|
|
||||||
|
class Bip39(private val wordlist: List<String>, private val crypto: Crypto) {
|
||||||
|
private fun computeChecksum(entropy: List<Boolean>): List<Boolean> {
|
||||||
|
val sha256 = crypto.sha256()
|
||||||
|
var byte = 0
|
||||||
|
entropy.forEachIndexed { i, bit ->
|
||||||
|
byte = byte shl 1
|
||||||
|
if (bit) byte += 1
|
||||||
|
if (i.rem(8) == 7) {
|
||||||
|
sha256.update(byte.toByte())
|
||||||
|
byte = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sha256.finalize().toBits().subList(0, entropy.size / 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encode(entropy: ByteArray): List<String> {
|
||||||
|
val entropyBits = entropy.toBits()
|
||||||
|
val msg = entropyBits + computeChecksum(entropyBits)
|
||||||
|
var wordIndex = 0
|
||||||
|
val mnemonic = mutableListOf<String>()
|
||||||
|
msg.forEachIndexed { i, bit ->
|
||||||
|
wordIndex = wordIndex shl 1
|
||||||
|
if (bit) wordIndex += 1
|
||||||
|
if (i.rem(11) == 10) {
|
||||||
|
mnemonic.add(wordlist[wordIndex])
|
||||||
|
wordIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mnemonic
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decode(mnemonic: List<String>): ByteArray {
|
||||||
|
val bits = mutableListOf<Boolean>()
|
||||||
|
mnemonic.forEach { word ->
|
||||||
|
val wordBits = mutableListOf<Boolean>()
|
||||||
|
var wordIndex = wordlist.binarySearch(word)
|
||||||
|
if (wordIndex < 0) throw InvalidWordException(word)
|
||||||
|
for (it in 0..10) {
|
||||||
|
wordBits.add(wordIndex.rem(2) == 1)
|
||||||
|
wordIndex = wordIndex shr 1
|
||||||
|
}
|
||||||
|
bits.addAll(wordBits.reversed())
|
||||||
|
}
|
||||||
|
if (bits.size.rem(33) != 0) throw InvalidMnemonicLength()
|
||||||
|
val checksumSize = bits.size / 33
|
||||||
|
val checksum = bits.subList(bits.size - checksumSize, bits.size)
|
||||||
|
val entropy = bits.subList(0, bits.size - checksumSize)
|
||||||
|
if (computeChecksum(entropy) != checksum) throw InvalidChecksumException()
|
||||||
|
return byteArray(entropy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidChecksumException : Exception()
|
||||||
|
class InvalidWordException(word: String) : Exception(word)
|
||||||
|
class InvalidMnemonicLength : Exception()
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.crypto
|
||||||
|
|
||||||
|
class Key(val bytes: ByteArray)
|
||||||
|
|
||||||
|
interface Crypto {
|
||||||
|
fun sha256(): Sha256
|
||||||
|
fun hmacSha256(): HmacSha256
|
||||||
|
fun aesGcm(key: Key): AesGcm
|
||||||
|
fun secureRandomBytes(numBytes: Int): ByteArray
|
||||||
|
|
||||||
|
fun generateKey(): Key {
|
||||||
|
return Key(secureRandomBytes(32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deriveKey(master: Key, subkeyName: String): Key {
|
||||||
|
val mac = hmacSha256()
|
||||||
|
mac.init(master.bytes)
|
||||||
|
mac.update(subkeyName)
|
||||||
|
return Key(mac.finalize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Sha256 {
|
||||||
|
fun update(byte: Byte)
|
||||||
|
fun finalize(): ByteArray
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HmacSha256 {
|
||||||
|
fun init(key: ByteArray)
|
||||||
|
fun update(byte: Byte)
|
||||||
|
fun finalize(): ByteArray
|
||||||
|
|
||||||
|
fun update(msg: String) {
|
||||||
|
for (b in msg.encodeToByteArray()) {
|
||||||
|
update(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AesGcm {
|
||||||
|
fun encrypt(msg: ByteArray, iv: ByteArray): ByteArray
|
||||||
|
fun decrypt(cipherText: ByteArray): ByteArray
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Byte.toBits(): List<Boolean> = (7 downTo 0).map { (toInt() and (1 shl it)) != 0 }
|
||||||
|
fun ByteArray.toBits(): List<Boolean> = flatMap { it.toBits() }
|
||||||
|
fun byteArrayOfInts(vararg b: Int) = b.map { it.toByte() }.toByteArray()
|
||||||
|
fun byteArray(bits: List<Boolean>): ByteArray {
|
||||||
|
var byte = 0
|
||||||
|
val bytes = ByteArray(bits.size / 8)
|
||||||
|
bits.forEachIndexed { i, b ->
|
||||||
|
byte = byte shl 1
|
||||||
|
if (b) byte += 1
|
||||||
|
if (i.rem(8) == 7) {
|
||||||
|
bytes[i / 8] = byte.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.crypto
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
class JavaCrypto : Crypto {
|
||||||
|
override fun sha256() = JavaSha256()
|
||||||
|
override fun hmacSha256() = JavaHmacSha256()
|
||||||
|
override fun aesGcm(key: Key) = JavaAesGcm(key)
|
||||||
|
|
||||||
|
override fun secureRandomBytes(numBytes: Int): ByteArray {
|
||||||
|
val sr = SecureRandom()
|
||||||
|
val bytes = ByteArray(numBytes)
|
||||||
|
sr.nextBytes(bytes)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JavaSha256 : Sha256 {
|
||||||
|
private val md = MessageDigest.getInstance("SHA-256")
|
||||||
|
override fun update(byte: Byte) = md.update(byte)
|
||||||
|
override fun finalize(): ByteArray = md.digest()
|
||||||
|
}
|
||||||
|
|
||||||
|
class JavaHmacSha256 : HmacSha256 {
|
||||||
|
private val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
override fun init(key: ByteArray) = mac.init(SecretKeySpec(key, "HmacSHA256"))
|
||||||
|
override fun update(byte: Byte) = mac.update(byte)
|
||||||
|
override fun finalize(): ByteArray = mac.doFinal()
|
||||||
|
}
|
||||||
|
|
||||||
|
class JavaAesGcm(val key: Key) : AesGcm {
|
||||||
|
override fun encrypt(msg: ByteArray, iv: ByteArray): ByteArray {
|
||||||
|
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key.bytes, "AES"), GCMParameterSpec(128, iv))
|
||||||
|
val encrypted = cipher.doFinal(msg)
|
||||||
|
return ByteBuffer
|
||||||
|
.allocate(iv.size + encrypted.size)
|
||||||
|
.put(iv)
|
||||||
|
.put(encrypted)
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
override fun decrypt(cipherText: ByteArray): ByteArray {
|
||||||
|
val buffer = ByteBuffer.wrap(cipherText)
|
||||||
|
val iv = ByteArray(12)
|
||||||
|
buffer.get(iv)
|
||||||
|
val encrypted = ByteArray(buffer.remaining())
|
||||||
|
buffer.get(encrypted)
|
||||||
|
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key.bytes, "AES"), GCMParameterSpec(128, iv))
|
||||||
|
return cipher.doFinal(encrypted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toHexString(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for (b in this) sb.append(String.format("%02x", b))
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.platform.crypto
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.isoron.platform.io.JavaFileOpener
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
class Bip39Test {
|
||||||
|
|
||||||
|
private lateinit var bip39: Bip39
|
||||||
|
|
||||||
|
private val phrases = listOf(
|
||||||
|
listOf(
|
||||||
|
"gather",
|
||||||
|
"capable",
|
||||||
|
"since",
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
"exit",
|
||||||
|
"churn",
|
||||||
|
"hazard",
|
||||||
|
"garage",
|
||||||
|
"hint",
|
||||||
|
"great",
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
"exile",
|
||||||
|
"blouse",
|
||||||
|
"athlete",
|
||||||
|
"dinner",
|
||||||
|
"chef",
|
||||||
|
"home",
|
||||||
|
"destroy",
|
||||||
|
"disagree",
|
||||||
|
"select",
|
||||||
|
"eight",
|
||||||
|
"slim",
|
||||||
|
"talent",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val entropies = listOf(
|
||||||
|
byteArrayOfInts(0x60, 0x64, 0x3f, 0x24),
|
||||||
|
byteArrayOfInts(0x4f, 0xe5, 0x19, 0xa7, 0xaf, 0xb6, 0xbc, 0xcc),
|
||||||
|
byteArrayOfInts(
|
||||||
|
0x4f,
|
||||||
|
0xa3,
|
||||||
|
0x04,
|
||||||
|
0x38,
|
||||||
|
0x9f,
|
||||||
|
0x22,
|
||||||
|
0x74,
|
||||||
|
0xda,
|
||||||
|
0x0f,
|
||||||
|
0x09,
|
||||||
|
0xf6,
|
||||||
|
0xc3,
|
||||||
|
0x48,
|
||||||
|
0xdf,
|
||||||
|
0x2f,
|
||||||
|
0x6e,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() = runBlocking {
|
||||||
|
bip39 = Bip39(JavaFileOpener().openResourceFile("bip39/en_US.txt").lines(), JavaCrypto())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_encode_decode() {
|
||||||
|
phrases.zip(entropies).forEach { (phrase, entropy) ->
|
||||||
|
assertEquals(phrase, bip39.encode(entropy))
|
||||||
|
assertEquals(entropy.toHexString(), bip39.decode(phrase).toHexString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_decode_invalid_checksum() {
|
||||||
|
assertFailsWith<InvalidChecksumException> {
|
||||||
|
bip39.decode(
|
||||||
|
listOf(
|
||||||
|
"lawn",
|
||||||
|
"dirt",
|
||||||
|
"work",
|
||||||
|
"mountain",
|
||||||
|
"depth",
|
||||||
|
"loyal",
|
||||||
|
"citizen",
|
||||||
|
"theory",
|
||||||
|
"cram",
|
||||||
|
"trip",
|
||||||
|
"boil",
|
||||||
|
"about",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_decode_invalid_word() {
|
||||||
|
assertFailsWith<InvalidWordException> {
|
||||||
|
bip39.decode(listOf("dirt", "bee", "work"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.isoron.platform.crypto
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
|
|
||||||
|
class CryptoTest {
|
||||||
|
private val crypto = JavaCrypto()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_sha256() {
|
||||||
|
val sha256 = crypto.sha256()
|
||||||
|
sha256.update(0x10.toByte())
|
||||||
|
sha256.update(0x20.toByte())
|
||||||
|
sha256.update(0x30.toByte())
|
||||||
|
val digest = sha256.finalize()
|
||||||
|
assertEquals(32, digest.size)
|
||||||
|
assertEquals(0x8e.toByte(), digest[0])
|
||||||
|
assertEquals(0x13.toByte(), digest[1])
|
||||||
|
assertEquals(0x36.toByte(), digest[2])
|
||||||
|
assertEquals(0xb9.toByte(), digest[31])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_hmacsha256() {
|
||||||
|
val hmac = crypto.hmacSha256()
|
||||||
|
hmac.init(byteArrayOfInts(0x01, 0x02, 0x03))
|
||||||
|
hmac.update(0x40.toByte())
|
||||||
|
hmac.update("AB")
|
||||||
|
val checksum = hmac.finalize()
|
||||||
|
assertEquals(32, checksum.size)
|
||||||
|
assertEquals(0x6d.toByte(), checksum[0])
|
||||||
|
assertEquals(0xc9.toByte(), checksum[1])
|
||||||
|
assertEquals(0x05.toByte(), checksum[2])
|
||||||
|
assertEquals(0xa1.toByte(), checksum[31])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aes_gcm() {
|
||||||
|
val msg = byteArrayOfInts(
|
||||||
|
0x2f,
|
||||||
|
0xdc,
|
||||||
|
0xaa,
|
||||||
|
0x41,
|
||||||
|
0xfa,
|
||||||
|
0xb8,
|
||||||
|
0x5e,
|
||||||
|
0xe8,
|
||||||
|
0xa3,
|
||||||
|
0x12,
|
||||||
|
0x69,
|
||||||
|
0x68,
|
||||||
|
0x14,
|
||||||
|
0x31,
|
||||||
|
0xd8,
|
||||||
|
0x59,
|
||||||
|
0x74,
|
||||||
|
0x29,
|
||||||
|
0x2e,
|
||||||
|
0xae,
|
||||||
|
0xed,
|
||||||
|
0x76,
|
||||||
|
0x0a,
|
||||||
|
0x56,
|
||||||
|
0x46,
|
||||||
|
0x90,
|
||||||
|
0xb6,
|
||||||
|
0xcb,
|
||||||
|
0x9f,
|
||||||
|
0x37,
|
||||||
|
0xbe,
|
||||||
|
0xae,
|
||||||
|
)
|
||||||
|
|
||||||
|
val key = Key(
|
||||||
|
byteArrayOfInts(
|
||||||
|
0xed,
|
||||||
|
0xa8,
|
||||||
|
0xc3,
|
||||||
|
0xc6,
|
||||||
|
0x44,
|
||||||
|
0x1e,
|
||||||
|
0xa1,
|
||||||
|
0xd5,
|
||||||
|
0x71,
|
||||||
|
0x8c,
|
||||||
|
0x71,
|
||||||
|
0x45,
|
||||||
|
0xbe,
|
||||||
|
0x2d,
|
||||||
|
0xf7,
|
||||||
|
0xa4,
|
||||||
|
0x81,
|
||||||
|
0x2e,
|
||||||
|
0x0a,
|
||||||
|
0x0b,
|
||||||
|
0xa8,
|
||||||
|
0xe4,
|
||||||
|
0x20,
|
||||||
|
0x49,
|
||||||
|
0x94,
|
||||||
|
0x8a,
|
||||||
|
0x71,
|
||||||
|
0x1a,
|
||||||
|
0x15,
|
||||||
|
0xf5,
|
||||||
|
0x29,
|
||||||
|
0x78,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val iv = byteArrayOfInts(
|
||||||
|
0xa7,
|
||||||
|
0xef,
|
||||||
|
0xe1,
|
||||||
|
0xba,
|
||||||
|
0xdf,
|
||||||
|
0x4f,
|
||||||
|
0x85,
|
||||||
|
0xca,
|
||||||
|
0xc3,
|
||||||
|
0x81,
|
||||||
|
0xc1,
|
||||||
|
0x93,
|
||||||
|
)
|
||||||
|
|
||||||
|
val expected = byteArrayOfInts(
|
||||||
|
// iv
|
||||||
|
0xa7,
|
||||||
|
0xef,
|
||||||
|
0xe1,
|
||||||
|
0xba,
|
||||||
|
0xdf,
|
||||||
|
0x4f,
|
||||||
|
0x85,
|
||||||
|
0xca,
|
||||||
|
0xc3,
|
||||||
|
0x81,
|
||||||
|
0xc1,
|
||||||
|
0x93,
|
||||||
|
// msg
|
||||||
|
0x24,
|
||||||
|
0xe7,
|
||||||
|
0x26,
|
||||||
|
0x9b,
|
||||||
|
0xb8,
|
||||||
|
0x59,
|
||||||
|
0xf0,
|
||||||
|
0xe0,
|
||||||
|
0x4f,
|
||||||
|
0xda,
|
||||||
|
0xc0,
|
||||||
|
0x85,
|
||||||
|
0xc6,
|
||||||
|
0x23,
|
||||||
|
0x21,
|
||||||
|
0x61,
|
||||||
|
0x80,
|
||||||
|
0x59,
|
||||||
|
0xd6,
|
||||||
|
0x18,
|
||||||
|
0xee,
|
||||||
|
0xa0,
|
||||||
|
0xd8,
|
||||||
|
0x00,
|
||||||
|
0xe3,
|
||||||
|
0xdf,
|
||||||
|
0x6e,
|
||||||
|
0xcf,
|
||||||
|
0x89,
|
||||||
|
0x82,
|
||||||
|
0xfd,
|
||||||
|
0x63,
|
||||||
|
// verification tag
|
||||||
|
0xe9,
|
||||||
|
0xe9,
|
||||||
|
0xac,
|
||||||
|
0x92,
|
||||||
|
0xdc,
|
||||||
|
0xb1,
|
||||||
|
0x7c,
|
||||||
|
0x2d,
|
||||||
|
0x9a,
|
||||||
|
0x73,
|
||||||
|
0xda,
|
||||||
|
0x25,
|
||||||
|
0x6d,
|
||||||
|
0xda,
|
||||||
|
0xc0,
|
||||||
|
0x83,
|
||||||
|
)
|
||||||
|
val cipher = crypto.aesGcm(key)
|
||||||
|
val actual = cipher.encrypt(msg, iv)
|
||||||
|
assertEquals(actual.toHexString(), expected.toHexString())
|
||||||
|
|
||||||
|
val recovered = cipher.decrypt(actual)
|
||||||
|
assertEquals(msg.toHexString(), recovered.toHexString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_rand() {
|
||||||
|
val r1 = crypto.secureRandomBytes(8)
|
||||||
|
val r2 = crypto.secureRandomBytes(8)
|
||||||
|
assertEquals(8, r1.size)
|
||||||
|
assertNotEquals(r1.toBits(), r2.toBits())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_derive_key() {
|
||||||
|
val k1 = Key(byteArrayOfInts(0x01, 0x02, 0x03))
|
||||||
|
val k2 = crypto.deriveKey(k1, "TEST")
|
||||||
|
assertEquals(0x44.toByte(), k2.bytes[0])
|
||||||
|
assertEquals(0xd3.toByte(), k2.bytes[31])
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user