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