Fixed sha256/512 implementation updateable version counter overflowing because it was int instead of long, fixed nonce overwriting in pure xchacha20poly1305 implementation

This commit is contained in:
Ugljesa Jovanovic 2020-07-13 23:39:36 +02:00 committed by Ugljesa Jovanovic
parent 52b6a4ad8e
commit 1dc423a509
No known key found for this signature in database
GPG Key ID: 178E6DFCECCB0E0F
7 changed files with 156 additions and 35 deletions

View File

@ -0,0 +1,75 @@
package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
import com.ionspin.kotlin.crypto.util.toHexString
import kotlinx.cinterop.*
import libsodium.*
import platform.posix.free
import kotlin.test.Ignore
import kotlin.test.Test
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 13-Jul-2020
*/
class HelperTest {
@Ignore //Just used for debugging pure implementation
@Test
fun longSha256() {
for (target in 0L until 10L) {
generateForRounds256(target)
}
for (target in 0L until 16_777_216L step 1_000_000L) {
generateForRounds256(target)
}
generateForRounds256(16_777_216L)
}
fun generateForRounds256(target: Long) {
val updateValue =
"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno".encodeToUByteArray().toCValues()
val state = platform.linux.malloc(crypto_hash_sha256_state.size.convert())!!
.reinterpret<crypto_hash_sha256_state>()
crypto_hash_sha256_init(state)
for (i in 0 until target) {
crypto_hash_sha256_update(state, updateValue, updateValue.size.convert())
}
val result = UByteArray(32)
val resultPinned = result.pin()
crypto_hash_sha256_final(state, resultPinned.addressOf(0))
println("$target to \"${result.toHexString()}\",")
free(state)
}
@Ignore //Just used for debugging pure implementation
@Test
fun longSha512() {
for (target in 0L until 10L) {
generateForRounds512(target)
}
for (target in 0L until 16_777_216L step 1_000_000L) {
generateForRounds512(target)
}
generateForRounds512(16_777_216L)
}
fun generateForRounds512(target: Long) {
println("Wut")
val updateValue =
"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno".encodeToUByteArray().toCValues()
val state = platform.linux.malloc(crypto_hash_sha512_state.size.convert())!!
.reinterpret<crypto_hash_sha512_state>()
crypto_hash_sha512_init(state)
for (i in 0 until target) {
crypto_hash_sha512_update(state, updateValue, updateValue.size.convert())
}
val result = UByteArray(32)
val resultPinned = result.pin()
crypto_hash_sha512_final(state, resultPinned.addressOf(0))
println("$target to \"${result.toHexString()}\",")
free(state)
}
}

View File

@ -135,7 +135,7 @@ class MultipartAuthenticatedEncryptor internal constructor(val key : SymmetricKe
}
override fun startEncryption(): MultipartEncryptionHeader {
return header
return header.copy()
}
override fun cleanup() {

View File

@ -84,8 +84,6 @@ class XChaCha20Poly1305Pure(val key: UByteArray, val nonce: UByteArray) {
calcNonce[1] = 0U
calcNonce[2] = 0U
calcNonce[3] = 0U
key.overwriteWithZeroes()
nonce.overwriteWithZeroes()
println("Calckey-------=")
calcKey.hexColumsPrint()
println("Calckey-------=")

View File

@ -69,7 +69,7 @@ class Sha256Pure : Sha256 {
var h = iv.copyOf()
val expansionArray = createExpansionArray(inputMessage.size)
val expansionArray = createExpansionArray(inputMessage.size.toLong())
val chunks = (
inputMessage +
@ -179,15 +179,15 @@ class Sha256Pure : Sha256 {
}
fun createExpansionArray(originalSizeInBytes: Int): UByteArray {
fun createExpansionArray(originalSizeInBytes: Long): UByteArray {
val originalMessageSizeInBits = originalSizeInBytes * 8
//K such that L + 1 + K + 64 is a multiple of 512
val expandedRemainderOf512 = (originalMessageSizeInBits + BLOCK_SIZE_IN_BYTES + 1) % BLOCK_SIZE
val zeroAddAmount = when (expandedRemainderOf512) {
0 -> 0
else -> (BLOCK_SIZE - expandedRemainderOf512) / 8
0L -> 0
else -> ((BLOCK_SIZE - expandedRemainderOf512) / 8).toInt()
}
val expansionArray = UByteArray(zeroAddAmount + 1) {
when (it) {
@ -231,12 +231,16 @@ class Sha256Pure : Sha256 {
}
var h = iv.copyOf()
var counter = 0
var counter = 0L
var bufferCounter = 0
var buffer = UByteArray(BLOCK_SIZE_IN_BYTES) { 0U }
var digested = false
fun update(data: String) {
if (digested) {
throw RuntimeException("This instance of updateable SHA256 was already finished once. You should use new instance")
}
return update(data.encodeToUByteArray())
}
@ -247,6 +251,14 @@ class Sha256Pure : Sha256 {
when {
bufferCounter + data.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(data, bufferCounter)
bufferCounter + data.size == BLOCK_SIZE_IN_BYTES -> {
counter += BLOCK_SIZE_IN_BYTES
consumeBlock(data)
}
bufferCounter + data.size == BLOCK_SIZE_IN_BYTES -> {
counter += BLOCK_SIZE_IN_BYTES
consumeBlock(data)
}
bufferCounter + data.size >= BLOCK_SIZE_IN_BYTES -> {
val chunked = data.chunked(BLOCK_SIZE_IN_BYTES)
chunked.forEach { chunk ->
@ -303,6 +315,7 @@ class Sha256Pure : Sha256 {
h[5].toPaddedByteArray() +
h[6].toPaddedByteArray() +
h[7].toPaddedByteArray()
digested = true
return digest
}

View File

@ -137,7 +137,7 @@ class Sha512Pure : Sha512Multipart {
var h = iv.copyOf()
val expansionArray = createExpansionArray(inputMessage.size)
val expansionArray = createExpansionArray(inputMessage.size.toLong())
val chunks =
(inputMessage + expansionArray + (inputMessage.size * 8).toULong().toPadded128BitByteArray()).chunked(
@ -249,13 +249,13 @@ class Sha512Pure : Sha512Multipart {
return h
}
fun createExpansionArray(originalSizeInBytes: Int): UByteArray {
fun createExpansionArray(originalSizeInBytes: Long): UByteArray {
val originalMessageSizeInBits = originalSizeInBytes * 8
val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE
val zeroAddAmount = when (expandedRemainderOf1024) {
0 -> 0
else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8
0L -> 0
else -> ((BLOCK_SIZE - expandedRemainderOf1024) / 8).toInt()
}
val expansionArray = UByteArray(zeroAddAmount + 1) {
when (it) {
@ -305,9 +305,10 @@ class Sha512Pure : Sha512Multipart {
}
var h = iv.copyOf()
var counter = 0
var counter = 0L
var bufferCounter = 0
var buffer = UByteArray(BLOCK_SIZE_IN_BYTES) { 0U }
var digested = false
fun update(data: String) {
@ -318,6 +319,9 @@ class Sha512Pure : Sha512Multipart {
if (data.isEmpty()) {
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
}
if (digested) {
throw RuntimeException("This instance of updateable SHA256 was already finished once. You should use new instance")
}
when {
bufferCounter + data.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(data, bufferCounter)
@ -377,6 +381,7 @@ class Sha512Pure : Sha512Multipart {
h[5].toPaddedByteArray() +
h[6].toPaddedByteArray() +
h[7].toPaddedByteArray()
digested = true
return digest
}

View File

@ -16,7 +16,9 @@
package com.ionspin.kotlin.crypto.hash.sha
import com.ionspin.kotlin.crypto.hash.encodeToUByteArray
import com.ionspin.kotlin.crypto.util.hexStringToUByteArray
import com.ionspin.kotlin.crypto.util.toHexString
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertTrue
@ -74,28 +76,59 @@ class Sha256UpdatableTest {
for (i in 0 until 10000) {
sha256.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
}
val resultDoubleBlock = sha256.digest()
val expectedResultForDoubleBlock = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
val result = sha256.digest()
val expectedResult = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
assertTrue {
resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.hexStringToUByteArray())
result.contentEquals(expectedResult.hexStringToUByteArray())
}
}
@Ignore()
@Ignore // Takes too long on native and js, but it now finishes correctly (and surprisingly quickly) on JVM
@Test
fun testWellKnownLonger() {
val sha256 = Sha256Pure()
for (i in 0 until 16_777_216) {
if (i % 10000 == 0) {
println("$i/16777216")
//Obtained from libsodium runs
val roundsExpectedMap = mapOf(
0 to "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
1 to "2ff100b36c386c65a1afc462ad53e25479bec9498ed00aa5a04de584bc25301b",
2 to "a9161e25f5e45eddf9a4cfa8b23456ba66be8098e474dc145bfceb2a99808b89",
3 to "7bd37dc113762cb012b69105b6302638d414ebfcabde205669aab2b8bbe1f3c4",
4 to "8fa352dc79b2482d1b55777b4932aff67f52e87bafdf9b9d45d57405a672de7a",
5 to "a229ab9b1bce3f3281b9d06a592aadb7e765029472a6b9ebf5ecd6c45a31f2f6",
6 to "09b49600c40293a902e52f89478d4886cf5771faabe38f53a63f3e9051b23e32",
7 to "02372493fd93330a8371f1dea20f5c29e5411baa1b08ba6cb33c6ca745c364a1",
8 to "e38b73d311ac89575d4e69a965763cdc618d0879142574231268f2b014fd0fec",
9 to "37ed309f368eef8560c8cbfb1d8b619f3cc5460689984b8c687f46373ff62087",
0 to "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
1000000 to "7d9d6bcd4ffc9eb37899bbd7b5810e1762c55e4104f1b26b061349dbc67b5500",
2000000 to "6b324148d357b3bb6549d339cec1a2a9ce45543eca0efcece78eecb3337097c2",
3000000 to "3a52d0fb3383bca84dab55b2f41f0827d08325dbc65e3db4f15264597a8ac828",
4000000 to "afd4587cf84fe7b6ffd7146a702d9b5dd4f0b754971eef36b7e0a6c4c3e84d58",
5000000 to "d2b74e1a88652cce4450c3040decf40f9496ec92ad5e3443045922952db7894d",
6000000 to "6e27932f14dfbbce11c9c9523e8b5a11616fd7864504d607639a98f81d5c0701",
7000000 to "06e320b651981e13fb192805ff12a848815671d15885b636f99148b0089efabe",
8000000 to "acc52c42ee92c046430f6e5454670e4f82da684a157eddd393a74c6883c881a5",
9000000 to "493a7d3a40f3575bd5e5b4a5fec005f37392f3ce44565da314d173a062f499be",
10000000 to "b3c6a178986ae0f5c81875ed68da2c72c8be961e9abc05e02982f723bd52a489",
11000000 to "48387a098f9e919ea15ae5d1a347857f1043116e24bf9224b718120e0bfdaa9f",
12000000 to "3ec9b9e64073f1c89f6d1c817a75610294d32c33d089af45540a352634e4a74a",
13000000 to "cfb7c272408acdabf35c5cf1e2c0e2e81092d4e86bf8d51575a8a3baea2e7f8c",
14000000 to "aa678e14a7d9a160177d52f153f30edd20c7f8a25fe36a3500fc1e11bd99029f",
15000000 to "77c0a6256b17d6fdfd0926312ab168c6ac7357b11e93933e31f2f5fd5aefd400",
16000000 to "28598fc683ac23a324e732ae398e093b5b80b03f01bdcf2f2d1b8e6bc76d183e",
16777216 to "50e72a0e26442fe2552dc3938ac58658228c0cbfb1d2ca872ae435266fcd055e",
)
val encoded = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno".encodeToUByteArray()
for (roundsExpected in roundsExpectedMap) {
val sha256 = Sha256Pure()
for (i in 0 until roundsExpected.key) {
sha256.update(encoded)
}
val result = sha256.digest()
assertTrue("Failed on ${roundsExpected.key} rounds, expected ${roundsExpected.value}, but got result ${result.toHexString()}") {
result.contentEquals(roundsExpected.value.hexStringToUByteArray())
}
sha256.update("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno")
}
val resultDoubleBlock = sha256.digest()
val expectedResultForDoubleBlock = "50e72a0e26442fe2552dc3938ac58658228c0cbfb1d2ca872ae435266fcd055e"
assertTrue {
resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.hexStringToUByteArray())
}
}
}

View File

@ -70,21 +70,18 @@ class Sha512UpdatableTest {
}
}
@Ignore() //Interestingly enough I'm not the only one having trouble with this test.
@Ignore // Takes too long on native and js, but it now finishes correctly (and surprisingly quickly) on JVM
@Test
fun testWellKnownLonger() {
val sha512 = Sha512Pure()
for (i in 0 until 16_777_216) {
if (i % 10000 == 0) {
println("$i/16777216")
}
sha512.update("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno")
}
val resultDoubleBlock = sha512.digest()
val expectedResultForDoubleBlock = "b47c933421ea2db149ad6e10fce6c7f93d0752380180ffd7f4629a712134831d77be6091b819ed352c2967a2e2d4fa5050723c9630691f1a05a7281dbe6c1086"
val result = sha512.digest()
val expectedResult = "b47c933421ea2db149ad6e10fce6c7f93d0752380180ffd7f4629a712134831d77be6091b819ed352c2967a2e2d4fa5050723c9630691f1a05a7281dbe6c1086"
assertTrue {
resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.hexStringToUByteArray())
result.contentEquals(expectedResult.hexStringToUByteArray())
}
}
}