diff --git a/src/dorkbox/bytes/HashExtensions.kt b/src/dorkbox/bytes/HashExtensions.kt index 4cd2e04..8879427 100644 --- a/src/dorkbox/bytes/HashExtensions.kt +++ b/src/dorkbox/bytes/HashExtensions.kt @@ -15,8 +15,9 @@ */ package dorkbox.bytes -import dorkbox.bytes.Hash.charToBytes16 -import java.awt.SystemColor.text +import java.io.File +import java.io.InputStream +import java.nio.ByteBuffer import java.security.MessageDigest import java.security.NoSuchAlgorithmException @@ -27,19 +28,81 @@ object Hash { */ const val version = BytesInfo.version + object MessageDigestAlgorithm { + @Deprecated("Do not use this, it is insecure and prone to attack!") + const val MD2 = "MD2" + const val MD5 = "MD5" + const val SHA_1 = "SHA-1" + const val SHA_224 = "SHA-224" + const val SHA_256 = "SHA-256" + const val SHA_384 = "SHA-384" + @Deprecated("Do not use this, it is vulnerable to ht-extension attacks") + const val SHA_512 = "SHA-512" + const val SHA_512_224 = "SHA-512/224" + const val SHA_512_256 = "SHA-512/256" + const val SHA3_224 = "SHA3-224" + const val SHA3_256 = "SHA3-256" + const val SHA3_384 = "SHA3-384" + const val SHA3_512 = "SHA3-512" + } + + @Deprecated("Do not use this, it is insecure and prone to attack!") + internal val digestMd5 = ThreadLocal.withInitial { + try { + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.MD5) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException("Unable to initialize hash algorithm. MD5 digest doesn't exist?!?") + } + } internal val digest1 = ThreadLocal.withInitial { try { - return@withInitial MessageDigest.getInstance("SHA1") + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA_1) } catch (e: NoSuchAlgorithmException) { - throw RuntimeException("Unable to initialize hash algorithm. SHA1 digest doesn't exist?!? (This should not happen") + throw RuntimeException("Unable to initialize hash algorithm. SHA1 digest doesn't exist?!?") } } internal val digest256 = ThreadLocal.withInitial { try { - return@withInitial MessageDigest.getInstance("SHA-256") + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA_256) } catch (e: NoSuchAlgorithmException) { - throw RuntimeException("Unable to initialize hash algorithm. SHA256 digest doesn't exist?!? (This should not happen") + throw RuntimeException("Unable to initialize hash algorithm. SHA256 digest doesn't exist?!?") + } + } + + internal val digest384 = ThreadLocal.withInitial { + try { + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA_384) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException("Unable to initialize hash algorithm. SHA384 digest doesn't exist?!?") + } + } + internal val digest512 = ThreadLocal.withInitial { + try { + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA_512_256) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException("Unable to initialize hash algorithm. SHA512 digest doesn't exist?!?") + } + } + internal val digest3_256 = ThreadLocal.withInitial { + try { + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA3_256) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException("Unable to initialize hash algorithm. SHA3-256 digest doesn't exist?!?") + } + } + internal val digest3_384 = ThreadLocal.withInitial { + try { + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA3_384) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException("Unable to initialize hash algorithm. SHA3-384 digest doesn't exist?!?") + } + } + internal val digest3_512 = ThreadLocal.withInitial { + try { + return@withInitial MessageDigest.getInstance(MessageDigestAlgorithm.SHA3_512) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException("Unable to initialize hash algorithm. SHA3-512 digest doesn't exist?!?") } } @@ -57,111 +120,339 @@ object Hash { } + @Deprecated("Do not use this, it is insecure and prone to attack!") + val md5 get() = digest1.get() val sha1 get() = digest1.get() val sha256 get() = digest256.get() + val sha384 get() = digest384.get() + val sha512 get() = digest512.get() + val sha3_256 get() = digest3_256.get() + val sha3_384 get() = digest3_384.get() + val sha3_512 get() = digest3_512.get() } +/** + * Reads an InputStream and updates the digest for the data + */ +private fun updateDigest(digest: MessageDigest, data: InputStream, bufferSize: Int = 4096): MessageDigest { + val buffer = ByteArray(bufferSize) + var read = data.read(buffer, 0, bufferSize) + while (read > -1) { + digest.update(buffer, 0, read) + read = data.read(buffer, 0, bufferSize) + } + return digest +} -fun ByteArray.sha1(): ByteArray { - val digest: MessageDigest = Hash.digest1.get() +/** + * Reads an InputStream and updates the digest for the data + */ +private fun updateDigest(state: org.lwjgl.util.xxhash.XXH32State, data: InputStream, bufferSize: Int = 4096) { + val buffer = ByteArray(bufferSize) + val bbuffer = ByteBuffer.wrap(buffer) + var read = data.read(buffer, 0, bufferSize) + while (read > -1) { + bbuffer.limit(read) + org.lwjgl.util.xxhash.XXHash.XXH32_update(state, bbuffer) + read = data.read(buffer, 0, bufferSize) + } +} + +private fun hash(byteArray: ByteArray, digest: MessageDigest): ByteArray { digest.reset() - digest.update(this) + digest.update(byteArray) return digest.digest() } -fun ByteArray.sha256(): ByteArray { - val digest: MessageDigest = Hash.digest256.get() +@Deprecated("Do not use this, it is insecure and prone to attack!") +fun ByteArray.md5(): ByteArray = hash(this, Hash.digestMd5.get()) +fun ByteArray.sha1(): ByteArray = hash(this, Hash.digest1.get()) +fun ByteArray.sha256(): ByteArray = hash(this, Hash.digest256.get()) +fun ByteArray.sha384(): ByteArray = hash(this, Hash.digest384.get()) +fun ByteArray.sha512(): ByteArray = hash(this, Hash.digest512.get()) +fun ByteArray.sha3_256(): ByteArray = hash(this, Hash.digest3_256.get()) +fun ByteArray.sha3_384(): ByteArray = hash(this, Hash.digest3_384.get()) +fun ByteArray.sha3_512(): ByteArray = hash(this, Hash.digest3_512.get()) +/** + * @param seed used to initialize the hash value (for the xxhash seed), use whatever value you want, but always the same + */ +fun ByteArray.xxHash(seed: Int = -0x31bf6a3c): Int { + val state: org.lwjgl.util.xxhash.XXH32State = org.lwjgl.util.xxhash.XXHash.XXH32_createState()!! + org.lwjgl.util.xxhash.XXHash.XXH32_reset(state, seed) - digest.reset() - digest.update(this) - return digest.digest() + val bbuffer = ByteBuffer.wrap(this) + + org.lwjgl.util.xxhash.XXHash.XXH32_update(state, bbuffer) + return org.lwjgl.util.xxhash.XXHash.XXH32_digest(state) } +private fun hash(string: String, digest: MessageDigest): ByteArray { + val charToBytes = string.toCharArray().charToBytes16() + digest.reset() + digest.update(charToBytes, 0, charToBytes.size) + return digest.digest() +} + +/** + * gets the MD5 hash of the specified string, as UTF-16 + */ +@Deprecated("Do not use this, it is insecure and prone to attack!") +fun String.md5(): ByteArray = hash(this, Hash.digestMd5.get()) /** * gets the SHA1 hash of the specified string, as UTF-16 */ -fun String.sha1(): ByteArray { - val charToBytes = this.toCharArray().charToBytes16() - - val digest: MessageDigest = Hash.digest1.get() - val usernameHashBytes = ByteArray(digest.digestLength) - digest.update(charToBytes, 0, charToBytes.size) - digest.digest(usernameHashBytes) - - return usernameHashBytes -} - +fun String.sha1(): ByteArray = hash(this, Hash.digest1.get()) /** * gets the SHA256 hash of the specified string, as UTF-16 */ -fun String.sha256(): ByteArray { +fun String.sha256(): ByteArray = hash(this, Hash.digest256.get()) +/** + * gets the SHA384 hash of the specified string, as UTF-16 + */ +fun String.sha384(): ByteArray = hash(this, Hash.digest384.get()) +/** + * gets the SHA512 hash of the specified string, as UTF-16 + */ +fun String.sha512(): ByteArray = hash(this, Hash.digest512.get()) +/** + * gets the SHA3_256 hash of the specified string, as UTF-16 + */ +fun String.sha3_256(): ByteArray = hash(this, Hash.digest3_256.get()) +/** + * gets the SHA3_384 hash of the specified string, as UTF-16 + */ +fun String.sha3_384(): ByteArray = hash(this, Hash.digest3_384.get()) +/** + * gets the SHA3_512 hash of the specified string, as UTF-16 + */ +fun String.sha3_512(): ByteArray = hash(this, Hash.digest3_512.get()) +/** + * gets the xxHash of the string, as UTF-16 + * + * @param seed used to initialize the hash value (for the xxhash seed), use whatever value you want, but always the same + */ +fun String.xxHash(saltBytes: ByteArray, seed: Int = -0x31bf6a3c): Int { + val state: org.lwjgl.util.xxhash.XXH32State = org.lwjgl.util.xxhash.XXHash.XXH32_createState()!! + org.lwjgl.util.xxhash.XXHash.XXH32_reset(state, seed) + val charToBytes = this.toCharArray().charToBytes16() - val digest: MessageDigest = Hash.digest256.get() - val usernameHashBytes = ByteArray(digest.digestLength) - digest.update(charToBytes, 0, charToBytes.size) - digest.digest(usernameHashBytes) + val bbuffer = ByteBuffer.wrap(charToBytes) - return usernameHashBytes + org.lwjgl.util.xxhash.XXHash.XXH32_update(state, bbuffer) + return org.lwjgl.util.xxhash.XXHash.XXH32_digest(state) } /** * gets the SHA256 hash + SALT of the string, as UTF-16 */ -fun String.sha1WithSalt(saltBytes: ByteArray): ByteArray { - val charToBytes = this.toCharArray().charToBytes16() - val userNameWithSalt = charToBytes + saltBytes +private fun hashWithSalt(string: String, saltBytes: ByteArray, digest: MessageDigest): ByteArray { + val charToBytes = string.toCharArray().charToBytes16() + val withSalt = charToBytes + saltBytes - val digest: MessageDigest = Hash.digest1.get() - val usernameHashBytes = ByteArray(digest.digestLength) - digest.update(userNameWithSalt, 0, userNameWithSalt.size) - digest.digest(usernameHashBytes) - - return usernameHashBytes + digest.reset() + digest.update(withSalt, 0, withSalt.size) + return digest.digest() } + /** * gets the SHA256 hash + SALT of the string, as UTF-16 */ -fun String.sha256WithSalt(saltBytes: ByteArray): ByteArray { +fun String.sha1WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest1.get()) +/** + * gets the SHA256 hash + SALT of the string, as UTF-16 + */ +fun String.sha256WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest256.get()) +/** + * gets the SHA384 hash + SALT of the string, as UTF-16 + */ +fun String.sha384WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest384.get()) +/** + * gets the SHA512 hash + SALT of the string, as UTF-16 + */ +fun String.sha512WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest512.get()) +/** + * gets the SHA3_256 hash + SALT of the string, as UTF-16 + */ +fun String.sha3_256WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest3_256.get()) +/** + * gets the SHA3_384 hash + SALT of the string, as UTF-16 + */ +fun String.sha3_384WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest3_384.get()) +/** + * gets the SHA3_512 hash + SALT of the string, as UTF-16 + */ +fun String.sha3_512WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest3_512.get()) +/** + * gets the xxHash + SALT of the string, as UTF-16 + * + * @param seed used to initialize the hash value (for the xxhash seed), use whatever value you want, but always the same + */ +fun String.xxHashWithSalt(saltBytes: ByteArray, seed: Int = -0x31bf6a3c): Int { + val state: org.lwjgl.util.xxhash.XXH32State = org.lwjgl.util.xxhash.XXHash.XXH32_createState()!! + org.lwjgl.util.xxhash.XXHash.XXH32_reset(state, seed) + val charToBytes = this.toCharArray().charToBytes16() - val userNameWithSalt = charToBytes + saltBytes + val withSalt = charToBytes + saltBytes - val digest: MessageDigest = Hash.digest256.get() - val usernameHashBytes = ByteArray(digest.digestLength) - digest.update(userNameWithSalt, 0, userNameWithSalt.size) - digest.digest(usernameHashBytes) + val bbuffer = ByteBuffer.wrap(withSalt) - return usernameHashBytes + org.lwjgl.util.xxhash.XXHash.XXH32_update(state, bbuffer) + return org.lwjgl.util.xxhash.XXHash.XXH32_digest(state) } + +private fun hashWithSalt(bytes: ByteArray, saltBytes: ByteArray, digest: MessageDigest): ByteArray { + val bytesWithSalt = bytes + saltBytes + + digest.reset() + digest.update(bytesWithSalt, 0, bytesWithSalt.size) + return digest.digest() +} + + /** * gets the SHA1 hash + SALT of the byte array */ -fun ByteArray.sha1WithSalt(saltBytes: ByteArray): ByteArray { - val bytesWithSalt = this + saltBytes - - val sha256: MessageDigest = Hash.digest1.get() - val usernameHashBytes = ByteArray(sha256.digestLength) - sha256.update(bytesWithSalt, 0, bytesWithSalt.size) - sha256.digest(usernameHashBytes) - - return usernameHashBytes -} - +fun ByteArray.sha1WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest1.get()) /** * gets the SHA256 hash + SALT of the byte array */ -fun ByteArray.sha256WithSalt(saltBytes: ByteArray): ByteArray { +fun ByteArray.sha256WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest256.get()) +/** + * gets the SHA384 hash + SALT of the byte array + */ +fun ByteArray.sha384WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest384.get()) +/** + * gets the SHA512 hash + SALT of the byte array + */ +fun ByteArray.sha512WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest512.get()) +/** + * gets the SHA3_256 hash + SALT of the byte array + */ +fun ByteArray.sha3_256WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest3_256.get()) +/** + * gets the SHA3_384 hash + SALT of the byte array + */ +fun ByteArray.sha3_384WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest3_384.get()) +/** + * gets the SHA3_512 hash + SALT of the byte array + */ +fun ByteArray.sha3_512WithSalt(saltBytes: ByteArray): ByteArray = hashWithSalt(this, saltBytes, Hash.digest3_512.get()) +/** + * gets the xxHash + SALT of the byte array + * + * @param seed used to initialize the hash value (for the xxhash seed), use whatever value you want, but always the same + */ +fun ByteArray.xxHashWithSalt(saltBytes: ByteArray, seed: Int = -0x31bf6a3c): Int { + val state: org.lwjgl.util.xxhash.XXH32State = org.lwjgl.util.xxhash.XXHash.XXH32_createState()!! + org.lwjgl.util.xxhash.XXHash.XXH32_reset(state, seed) + val bytesWithSalt = this + saltBytes + val bbuffer = ByteBuffer.wrap(bytesWithSalt) - val sha256: MessageDigest = Hash.digest256.get() - val usernameHashBytes = ByteArray(sha256.digestLength) - sha256.update(bytesWithSalt, 0, bytesWithSalt.size) - sha256.digest(usernameHashBytes) - - return usernameHashBytes + org.lwjgl.util.xxhash.XXHash.XXH32_update(state, bbuffer) + return org.lwjgl.util.xxhash.XXHash.XXH32_digest(state) } + + + + +private fun hash(file: File, startPosition: Long = 0L, endPosition: Long = file.length(), bufferSize: Int = 4096, digest: MessageDigest): ByteArray { + digest.reset() + + if (!file.isFile) { + throw IllegalArgumentException("Unable open as file: ${file.absolutePath}") + } + if (!file.canRead()) { + throw IllegalArgumentException("Unable to read file: ${file.absolutePath}") + } + + file.inputStream().use { + val skipped = it.skip(startPosition) + if (skipped != startPosition) { + throw IllegalArgumentException("Unable to skip $startPosition bytes. Only able to skip $skipped bytes instead") + } + + var size = file.length() - startPosition + val lengthFromEnd = size - endPosition + if (lengthFromEnd in 1 until size) { + size -= lengthFromEnd + } + + updateDigest(digest, it, bufferSize) + return digest.digest() + } +} + + + +/** + * gets the SHA1 hash of the file + */ +fun File.sha1(): ByteArray = hash(this, 0, length(), 4096, Hash.digest1.get()) +/** + * gets the SHA256 hash of the file + */ +fun File.sha256(): ByteArray = hash(this, 0, length(), 4096, Hash.digest256.get()) +/** + * gets the SHA384 hash of the file + */ +fun File.sha384(): ByteArray = hash(this, 0, length(), 4096, Hash.digest384.get()) +/** + * gets the SHA512 hash of the file + */ +fun File.sha512(): ByteArray = hash(this, 0, length(), 4096, Hash.digest512.get()) +/** + * gets the SHA3_256 hash of the file + */ +fun File.sha3_256(): ByteArray = hash(this, 0, length(), 4096, Hash.digest3_256.get()) +/** + * gets the SHA3_384 hash of the file + */ +fun File.sha3_384(): ByteArray = hash(this, 0, length(), 4096, Hash.digest3_384.get()) +/** + * gets the SHA3_512 hash of the file + */ +fun File.sha3_512(): ByteArray = hash(this, 0, length(), 4096, Hash.digest3_512.get()) + +/** + * Return the xxhash of the file as or IllegalArgumentExceptions if there are problems with the file + * + * @param seed used to initialize the hash value (for the xxhash seed), use whatever value you want, but always the same + */ +fun File.xxHash(startPosition: Long = 0L, endPosition: Long = this.length(), bufferSize: Int = 4096, seed: Int = -0x31bf6a3c): Int { + val state: org.lwjgl.util.xxhash.XXH32State = org.lwjgl.util.xxhash.XXHash.XXH32_createState()!! + org.lwjgl.util.xxhash.XXHash.XXH32_reset(state, seed) + + if (!this.isFile) { + throw IllegalArgumentException("Unable open as file: ${this.absolutePath}") + } + if (!this.canRead()) { + throw IllegalArgumentException("Unable to read file: ${this.absolutePath}") + } + + this.inputStream().use { + val skipped = it.skip(startPosition) + if (skipped != startPosition) { + throw IllegalArgumentException("Unable to skip $startPosition bytes. Only able to skip $skipped bytes instead") + } + + var size = this.length() - startPosition + val lengthFromEnd = size - endPosition + if (lengthFromEnd in 1 until size) { + size -= lengthFromEnd + } + + updateDigest(state, it, bufferSize) + return org.lwjgl.util.xxhash.XXHash.XXH32_digest(state) + } +} + + + +