diff options
author | Torsten Grote <t@grobox.de> | 2020-06-04 14:04:34 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-06-04 14:07:05 -0300 |
commit | f37a38dc0fb6a144b8e9ea898331fb8665e602fc (patch) | |
tree | 88f53ab111968ad57e5e66d7d9b3c749375fd7b3 /src | |
parent | e5b34507fb0af1932bc7d17df61146ea13173ca1 (diff) | |
download | wallet-kotlin-f37a38dc0fb6a144b8e9ea898331fb8665e602fc.tar.gz wallet-kotlin-f37a38dc0fb6a144b8e9ea898331fb8665e602fc.tar.bz2 wallet-kotlin-f37a38dc0fb6a144b8e9ea898331fb8665e602fc.zip |
Add GNUnet's HKDF implementation to Crypto interface
including a common implementation for all platforms
Diffstat (limited to 'src')
8 files changed, 360 insertions, 3 deletions
diff --git a/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt index 0bc933a..d41b615 100644 --- a/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt +++ b/src/androidMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt @@ -11,10 +11,16 @@ internal actual object CryptoFactory { internal actual fun getCrypto(): Crypto = CryptoJvmImpl } -internal object CryptoJvmImpl : Crypto { +internal object CryptoJvmImpl : CryptoImpl() { private val sodium = LazySodiumJava(SodiumJava()) + override fun sha256(input: ByteArray): ByteArray { + val output = ByteArray(Hash.SHA256_BYTES) + sodium.cryptoHashSha256(output, input, input.size.toLong()) + return output + } + override fun sha512(input: ByteArray): ByteArray { val output = ByteArray(Hash.SHA512_BYTES) sodium.cryptoHashSha512(output, input, input.size.toLong()) diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt index 3735c31..c5c0a0f 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Crypto.kt @@ -1,6 +1,7 @@ package net.taler.wallet.kotlin.crypto internal interface Crypto { + fun sha256(input: ByteArray): ByteArray fun sha512(input: ByteArray): ByteArray fun eddsaGetPublic(eddsaPrivateKey: ByteArray): ByteArray fun ecdheGetPublic(ecdhePrivateKey: ByteArray): ByteArray @@ -10,6 +11,7 @@ internal interface Crypto { fun eddsaVerify(msg: ByteArray, sig: ByteArray, eddsaPub: ByteArray): Boolean fun keyExchangeEddsaEcdhe(eddsaPrivateKey: ByteArray, ecdhePublicKey: ByteArray): ByteArray fun keyExchangeEcdheEddsa(ecdhePrivateKey: ByteArray, eddsaPublicKey: ByteArray): ByteArray + fun kdf(outputLength: Int, ikm: ByteArray, salt: ByteArray, info: ByteArray): ByteArray } class EddsaKeyPair(val privateKey: ByteArray, val publicKey: ByteArray) diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoImpl.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoImpl.kt new file mode 100644 index 0000000..ae333eb --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoImpl.kt @@ -0,0 +1,9 @@ +package net.taler.wallet.kotlin.crypto + +abstract class CryptoImpl : Crypto { + + override fun kdf(outputLength: Int, ikm: ByteArray, salt: ByteArray, info: ByteArray): ByteArray { + return Kdf.kdf(outputLength, ikm, salt, info, { sha256(it) }, { sha512(it) }) + } + +} diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Kdf.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Kdf.kt new file mode 100644 index 0000000..d6a7658 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Kdf.kt @@ -0,0 +1,74 @@ +package net.taler.wallet.kotlin.crypto + +import kotlin.experimental.xor +import kotlin.math.ceil + +internal object Kdf { + + const val HMAC_SHA256_BLOCK_SIZE = 64 + const val HMAC_SHA512_BLOCK_SIZE = 128 + + fun kdf( + outputLength: Int, + ikm: ByteArray, + salt: ByteArray, + info: ByteArray, + sha256: (ByteArray) -> ByteArray, + sha512: (ByteArray) -> ByteArray + ): ByteArray { + //extract + val prk = hmacSha512(salt, ikm, sha512) + + // expand + val n = ceil(outputLength.toDouble() / 32).toInt() + val output = ByteArray(n * 32) + for (i in 0 until n) { + val buf: ByteArray + if (i == 0) { + buf = ByteArray(info.size + 1) + info.copyInto(buf) + } else { + buf = ByteArray(info.size + 1 + 32) + for (j in 0 until 32) { + buf[j] = output[(i - 1) * 32 + j] + } + info.copyInto(buf, destinationOffset = 32) + } + buf[buf.size - 1] = (i + 1).toByte() + val chunk = hmacSha256(prk, buf, sha256) + chunk.copyInto(output, destinationOffset = i * 32) + } + return output.copyOfRange(0, outputLength) + } + + fun hmacSha256(key: ByteArray, message: ByteArray, sha256: (ByteArray) -> ByteArray): ByteArray { + return hmac(HMAC_SHA256_BLOCK_SIZE, key, message) { sha256(it) } + } + + fun hmacSha512(key: ByteArray, message: ByteArray, sha512: (ByteArray) -> ByteArray): ByteArray { + return hmac(HMAC_SHA512_BLOCK_SIZE, key, message) { sha512(it) } + } + + private fun hmac(blockSize: Int, key: ByteArray, message: ByteArray, hash: (ByteArray) -> ByteArray): ByteArray { + var newKey = key + if (newKey.size > blockSize) newKey = hash(newKey) + if (newKey.size < blockSize) newKey = ByteArray(blockSize).apply { + newKey.copyInto(this) + } + val okp = ByteArray(blockSize) + val ikp = ByteArray(blockSize) + for (i in 0 until blockSize) { + ikp[i] = newKey[i] xor 0x36 + okp[i] = newKey[i] xor 0x5c + } + val b1 = ByteArray(blockSize + message.size) + ikp.copyInto(b1) + message.copyInto(b1, destinationOffset = blockSize) + val h0 = hash(b1) + val b2 = ByteArray(blockSize + h0.size) + okp.copyInto(b2) + h0.copyInto(b2, destinationOffset = blockSize) + return hash(b2) + } + +} diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/KdfTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/KdfTest.kt new file mode 100644 index 0000000..702d6e3 --- /dev/null +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/KdfTest.kt @@ -0,0 +1,197 @@ +package net.taler.wallet.kotlin.crypto + +import net.taler.wallet.kotlin.Base32Crockford +import net.taler.wallet.kotlin.crypto.Kdf.HMAC_SHA256_BLOCK_SIZE +import net.taler.wallet.kotlin.crypto.Kdf.HMAC_SHA512_BLOCK_SIZE +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class KdfTest { + + private val crypto = CryptoFactory.getCrypto() + + @Test + fun testHmacSha256() { + // key smaller than block size + val key1 = "EDJP6WK5EG" + val message1 = "91JPRV3F41BPYWKCCGGG" + val expectedOut1 = "DYKV9QN2HVHMHQRGZ6XNJPPSGQZHA2JAVZB1676ACXYSNKQ0FQ30" + val out1 = Kdf.hmacSha256(Base32Crockford.decode(key1), Base32Crockford.decode(message1)) { crypto.sha256(it) } + assertTrue(Base32Crockford.decode(key1).size < HMAC_SHA256_BLOCK_SIZE) + assertEquals(expectedOut1, Base32Crockford.encode(out1)) + + // key bigger than block size + val key2 = + "AHM6JWS089GQ6S9K68G7CRBJD5GPWX10EXGQ6833E9JP2X35CGG64Y908HQQASVCC5SJ0GVJDXHPPSKFE9J20X3F41GQCVV9CGG66VVECSTQ6TBFDRQ0" + val message2 = "89P62RVB4166JXK5ECG4TRBMEHJQ488" + val expectedOut2 = "QGEK8853DW3GSQ4RHDYXZ0KX9R6E356R5495Z10KDNFNZ98V9FQG" + val out2 = Kdf.hmacSha256(Base32Crockford.decode(key2), Base32Crockford.decode(message2)) { crypto.sha256(it) } + assertTrue(Base32Crockford.decode(key2).size > HMAC_SHA256_BLOCK_SIZE) + assertEquals(expectedOut2, Base32Crockford.encode(out2)) + + // key exactly block size + val key3 = + "AHM6JWS0EDJP6WK5EGG6PSBS41MQ6831ECG6RVVECWG62WS0EHM6A832DHQP6TS0EDMQMS90DXK20J2D851J0MT884S3ADH07MG3CD0" + val message3 = "89P62RVB4166JXK5ECG4TRBMEHJQ488" + val expectedOut3 = "HAEWRWXEWWPRMT4W6E32GS0P6QMW85MMZZA5CX2BMJW36QZQFJ2G" + val out3 = Kdf.hmacSha256(Base32Crockford.decode(key3), Base32Crockford.decode(message3)) { crypto.sha256(it) } + assertEquals(HMAC_SHA256_BLOCK_SIZE, Base32Crockford.decode(key3).size) + assertEquals(expectedOut3, Base32Crockford.encode(out3)) + } + + @Test + fun testHmacSha512() { + // key smaller than block size + val key1 = "EDJP6WK5EG" + val message1 = "91JPRV3F41BPYWKCCGGG" + val expectedOut1 = + "ZMVHDTWE341YSE6YQ4S0Y6XESW7RVFG4WK2CE7K4ZESQ10125JJ2VTRBCHPAQTTKW9MT4H350ZXN8GMP2SBFG03SB6YK63QMHQRE2FG" + val out1 = Kdf.hmacSha512(Base32Crockford.decode(key1), Base32Crockford.decode(message1)) { crypto.sha512(it) } + assertTrue(Base32Crockford.decode(key1).size < HMAC_SHA512_BLOCK_SIZE) + assertEquals(expectedOut1, Base32Crockford.encode(out1)) + + // key bigger than block size + val key2 = + "AHM6JWS089GQ6S9K68G7CRBJD5GPWX10EXGQ6833E9JP2X35CGG64Y908HQQASVCC5SJ0GVJDXHPPSKFE9J2W829EGG6AY33DHTP8SBK414JR82C5GG4Y83MDWG62XKFD5J20RVFDSK7AWV9DXQ20XV9EHM20S39CXMQ8WS0C5Q6882N41T6Y83GE9JQCSBEEGG62RV3D5J6AVKMC5P20VV2EDHPAVK9EHWJW" + val message2 = "89P62RVB4166JXK5ECG4TRBMEHJQ488" + val expectedOut2 = + "0N9GF90FES6HXS344WB396CF78SQDRZ7CS9MRAVCVZPJ3PJ97HYSB5A3C7RCXTTYYSS4CV11X3DANKQD71YMTA46R3NS1X8A99YVAE0" + val out2 = Kdf.hmacSha512(Base32Crockford.decode(key2), Base32Crockford.decode(message2)) { crypto.sha512(it) } + assertTrue(Base32Crockford.decode(key2).size > HMAC_SHA512_BLOCK_SIZE) + assertEquals(expectedOut2, Base32Crockford.encode(out2)) + + // key exactly block size + val key3 = + "AHM6JWS0ESJQ4Y90EDJP6WK5EGG6PSBS41MQ6835F1GP6X3CF4G62WS0DHQPWSS0C5SJ0X38CMG64V3FCDNJ0WV9F9JJ0VV641T6GS90916M2GS0AD44281N64S20RBCCXQQ4TBMD1PJ0XV8D5HPG839ECG32CHR41H7JX35ECG62VK441GJ0V3FEGG6YSH0EHJQGX1E5RQ2W" + val message3 = "89P62RVB4166JXK5ECG4TRBMEHJQ488" + val expectedOut3 = + "A3ZAKHA0EADSBP832KBCPDNVB4GP876PMQEEB2F55ERTQFQ7KMYNTJ3SEWJEQRNH3EV2H0HSA740JE4JQH7GM8FC708KHF4A5FG9QNG" + val out3 = Kdf.hmacSha512(Base32Crockford.decode(key3), Base32Crockford.decode(message3)) { crypto.sha512(it) } + assertEquals(HMAC_SHA512_BLOCK_SIZE, Base32Crockford.decode(key3).size) + assertEquals(expectedOut3, Base32Crockford.encode(out3)) + } + + @Test + fun testRfc4231() { + val key1 = ByteArray(20) { 0x0b } + val data1 = "91MJ0N38CNS6A" + assertEquals( + "P0T4RRERVCW56Q58NZ7AY2ZH5E41VGG0S61KV9S6X4VPRBHJSZVG", + Base32Crockford.encode(Kdf.hmacSha256(key1, Base32Crockford.decode(data1)) { crypto.sha256(it) }) + ) + assertEquals( + "GYN7SQN5XXGSTKZGPGJ1M7BCP0HQKX72SS7C4Y3TT2SGAHF1FKFDNA1KPZBBH9R20E5JEKNEMFTE9FMXJ57EPRFHE0Q6JV107896GN0", + Base32Crockford.encode(Kdf.hmacSha512(key1, Base32Crockford.decode(data1)) { crypto.sha512(it) }) + ) + + val key2 = Base32Crockford.decode("99JPCS8") + val data2 = "EXM62X10CHQJ0YB141VP2VKM41K6YWH0DSQQ8T39DSKKY" + assertEquals( + "BFEC2HNZC1TMWTG44GK0H5BNRXD00FR8KMKKK0WXXHCBJS7C711G", + Base32Crockford.encode(Kdf.hmacSha256(key2, Base32Crockford.decode(data2)) { crypto.sha256(it) }) + ) + assertEquals( + "2S5QMYZWZ0CY5RWNZFKKPNQ0ME3VTS125T1HZNGG4W6DFTH50NA9EP5ZEQ05N6AADM1MYSFRY3KFVJQAP6HMTJKB9DHPW1RA72YEEDR", + Base32Crockford.encode(Kdf.hmacSha512(key2, Base32Crockford.decode(data2)) { crypto.sha512(it) }) + ) + + val key3 = ByteArray(20) { 0xaa.toByte() } + val data3 = "VQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEXVQEX" + assertEquals( + "EWZAJ7HPG074D1ADQ3NX14C1MWMNJ2CB7VWC28PSCDAH9KPNCQZ0", + Base32Crockford.encode(Kdf.hmacSha256(key3, Base32Crockford.decode(data3)) { crypto.sha256(it) }) + ) + assertEquals( + "Z9SV024XATH89VXGY1TPS28BX6RVBPYXHVM1MDJNZ0Z37CH7KMWVYFM4G9WTE8P80TT8B93YCZ40FEA6MCVVXT4M4ST2F22SW4S95YR", + Base32Crockford.encode(Kdf.hmacSha512(key3, Base32Crockford.decode(data3)) { crypto.sha512(it) }) + ) + + val key4 = Base32Crockford.decode("041061050R3GG28A1C60T3GF208H44RM2MB1E60S") + val data4 = "SQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKEDSQ6WVKED" + assertEquals( + "G9ARME4T8GY0X96CG6C9KWG87A2Z1YN3WNWFG1VT5RZZ8SS9CSDG", + Base32Crockford.encode(Kdf.hmacSha256(key4, Base32Crockford.decode(data4)) { crypto.sha256(it) }) + ) + assertEquals( + "P2X4CNHQ8P66K475N32ZC7AAYZJQDPBZZ55REBF7DY050DGYWFDTJ755R4DA4QNMTSWJEQ65F20679FHJX0H432F5QHAVTZB22H9HQ8", + Base32Crockford.encode(Kdf.hmacSha512(key4, Base32Crockford.decode(data4)) { crypto.sha512(it) }) + ) + + val key5 = ByteArray(20) { 0x0c.toByte() } + val data5 = "AHJQ6X10AXMQ8T10AHS7AVK3C5T6JVVE" + assertEquals( + "MEV1CX3K207E0VGCF5P2JNAN5C", + Base32Crockford.encode(Kdf.hmacSha256(key5, Base32Crockford.decode(data5)) { crypto.sha256(it) } + .copyOfRange(0, 16)) + ) + assertEquals( + "85FTTRKHB05567A1F6Y8J7C7MR", + Base32Crockford.encode(Kdf.hmacSha512(key5, Base32Crockford.decode(data5)) { crypto.sha512(it) } + .copyOfRange(0, 16)) + ) + + val key6 = ByteArray(131) { 0xaa.toByte() } + val data6 = "AHJQ6X10ANSPJVK741662WK7CNS20N38C5Q20GKCDXHPPBAKD5X6A82BCNWJ0B9091GQ6T109DJQJ826D5S76X0" + assertEquals( + "C3J32P8YW2V7Y3CA4TNCQXDQFY70QHH16WMCA5058R20Y3Q3FXA0", + Base32Crockford.encode(Kdf.hmacSha256(key6, Base32Crockford.decode(data6)) { crypto.sha256(it) }) + ) + assertEquals( + "G2S44RY7R6HYQDRMJF0XTYZ8PJDMDMFM3D5EXG8J3C0KF0ZRYD96PNPG6ZG5Y9CRQM7X48AXD8F555F69XSZCFRAXJ5S2PMRBNW6B60", + Base32Crockford.encode(Kdf.hmacSha512(key6, Base32Crockford.decode(data6)) { crypto.sha512(it) }) + ) + + val key7 = ByteArray(131) { 0xaa.toByte() } + val data7 = + "AHM6JWS0D5SJ0R90EHJQ6X10ENSPJVK741GJ0V31E9KPAWH0EHM62VH0C9P6YRVB5NSPJYK541NPAY90C5Q6883141P62WK7CNS20X38C5Q20RKCDXHPPBBKD5X6A834C5T62BH0AHM6A83BCNWJ0VK5CNJ7683MDWG64S90D1GQ6T35CGG64SB6DXS6A832CNMPWSS0ENSPAS10C9WJ0X38CMG4GKA18CG62V37DXS6JX38DMQ0" + assertEquals( + "KC4ZZ9RVJGQWP9V3BYYDBC798JZXRRV49W3H74WAFX8N6Q1T6QH0", + Base32Crockford.encode(Kdf.hmacSha256(key7, Base32Crockford.decode(data7)) { crypto.sha256(it) }) + ) + assertEquals( + "WDXPMXTXS1YVN96ZN7WPWQHZZQFBTWFRGSS8K1JXYPHJT86DS52BC0HCNGY4K0NH1NFEPNE3WKF1A4T6EVXPVR24C1JWJX20ZA66MP0", + Base32Crockford.encode(Kdf.hmacSha512(key7, Base32Crockford.decode(data7)) { crypto.sha512(it) }) + ) + } + + @Test + fun testKdf() { + val salt = "94KPT83PCNS7J83KC5P78Y8" + val ikm = "94KPT83MD1JJ0WV5CDS6AX10D5Q70XBM41NPAY90DNGQ8SBJD5GPR" + val ctx = "94KPT83141HPYVKMCNW78833D1TPWTSC41GPRWVF41NPWVVQDRG62WS04XMPWSKF4WG6JVH0EHM6A82J8S1G" + val expectedOut = + "GTMR4QT05Z9WF5HKVG0WK9RPXGHSMHJNW377G9GJXCA8B0FEKPF4D27RJMSJZYWSQNTBJ5EYVV7ZW18B48Z0JVJJ80RHB706Y96Q358" + val out = + crypto.kdf(64, Base32Crockford.decode(ikm), Base32Crockford.decode(salt), Base32Crockford.decode(ctx)) + assertEquals(expectedOut, Base32Crockford.encode(out)) + + val salt2 = "94KPT83141V6AWKS41SP2V3MF4G76VK1CDNG" + val ikm2 = + "94KPT83MD1JJ0WV5CDS6AX10D5Q70XBM41NPAY90DNGQ8SBJD5GPR83MD1GQ8838C5SJ0RK5CNQ20RV8C5Q6ESB441K6YWH08X75A82MC5P6AWG" + val ctx2 = + "94KPT83141HPYVKMCNW78833D1TPWTSC41GPRWVF41NPWVVQDRG62WS04XMPWSKF4WG6JVH0EHM6A82J8S1J0WVF41VPGY90DSQQ8833C5P6R83DCMG6JVK6DWZG" + val expectedOut2 = + "C6TXT6GMVZ8JNWZF27SPD6939FK035Y3FTWXYT0W4EZX9FNJ5KH24MQSK9D0MW3G7T1BBZ4ERVQC1RE24BNDPJQ68ZRQ8S3XN4GNHC8" + val out2 = + crypto.kdf(64, Base32Crockford.decode(ikm2), Base32Crockford.decode(salt2), Base32Crockford.decode(ctx2)) + assertEquals(expectedOut2, Base32Crockford.encode(out2)) + + val salt3 = "C4G76RBCEG" + val ikm3 = "ESJQ4Y90EDJP6WK5EGG6PSBS" + val ctx3 = "D5Q6CVR" + val expectedOut3 = "ZYMG67TD51XYS0ZM3QZKH8HF8FW7CTVT91NAWD0PGZM2QGTNYKMXAA4ZJBH5V633TJW9E124CRYEY" + val out3 = + crypto.kdf(48, Base32Crockford.decode(ikm3), Base32Crockford.decode(salt3), Base32Crockford.decode(ctx3)) + assertEquals(expectedOut3, Base32Crockford.encode(out3)) + + val salt4 = "84G76XV5CNT20WV1DHT20EH9" + val ikm4 = "9NWJ0SBREHS6AVB5DHWJ0WV5CDS6AX10DDJQJ" + val ctx4 = "CDQPWX35F1T20TBECSQKY88" + val expectedOut4 = "YM46SN0HYJ0RNGY1V5S7WY1SD2RQ1Y17G5Z37JD46E2Z8KY11PH5EV772RQ5NQ9SBMGG" + val out4 = + crypto.kdf(42, Base32Crockford.decode(ikm4), Base32Crockford.decode(salt4), Base32Crockford.decode(ctx4)) + assertEquals(expectedOut4, Base32Crockford.encode(out4)) + } + +} diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha256Test.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha256Test.kt new file mode 100644 index 0000000..87b6f75 --- /dev/null +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/Sha256Test.kt @@ -0,0 +1,54 @@ +package net.taler.wallet.kotlin.crypto + +import net.taler.wallet.kotlin.Base32Crockford +import kotlin.test.Test +import kotlin.test.assertEquals + +@kotlin.ExperimentalStdlibApi +class Sha256Test { + + private val crypto = CryptoFactory.getCrypto() + + @Test + fun testAbc() { + assertEquals( + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + crypto.sha256("abc".encodeToByteArray()).toHexString() + ) + } + + @Test + fun testEmptyString() { + assertEquals( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + crypto.sha256("".encodeToByteArray()).toHexString() + ) + } + + @Test + fun testAbc448bits() { + assertEquals( + "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", + crypto.sha256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq".encodeToByteArray()).toHexString() + ) + } + + @Test + fun testAbc896bits() { + assertEquals( + "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", + crypto.sha256("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu".encodeToByteArray()) + .toHexString() + ) + } + + @Test + fun testAMillionAs() { + val input = ByteArray(1_000_000) { 0x61 } + assertEquals( + "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0", + crypto.sha256(input).toHexString() + ) + } + +} diff --git a/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt index e36b833..2904168 100644 --- a/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt +++ b/src/jsMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt @@ -7,7 +7,11 @@ internal actual object CryptoFactory { internal actual fun getCrypto(): Crypto = CryptoJsImpl } -internal object CryptoJsImpl : Crypto { +internal object CryptoJsImpl : CryptoImpl() { + + override fun sha256(input: ByteArray): ByteArray { + return sha256(input.toUint8Array()).toByteArray() + } override fun sha512(input: ByteArray): ByteArray { return nacl.hash(input.toUint8Array()).toByteArray() @@ -113,3 +117,7 @@ private external class ed2curve { fun convertPublicKey(pk: Uint8Array): Uint8Array? } } + +@JsModule("fast-sha256") +@JsNonModule +private external fun sha256(message: Uint8Array): Uint8Array diff --git a/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt b/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt index f5eca15..11c63e7 100644 --- a/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt +++ b/src/linuxMain/kotlin/net/taler/wallet/kotlin/crypto/CryptoFactory.kt @@ -11,7 +11,14 @@ internal actual object CryptoFactory { } @ExperimentalUnsignedTypes -internal object CryptoNativeImpl : Crypto { +internal object CryptoNativeImpl : CryptoImpl() { + + override fun sha256(input: ByteArray): ByteArray { + val output = ByteArray(crypto_hash_sha256_bytes().toInt()) + val cInput = if (input.isEmpty()) null else input.toCValuesRef() + crypto_hash_sha256(output.toCValuesRef(), cInput, input.size.toULong()) + return output + } override fun sha512(input: ByteArray): ByteArray { val output = ByteArray(crypto_hash_sha512_bytes().toInt()) |