aboutsummaryrefslogtreecommitdiff
path: root/okio/src/jvmMain/kotlin/okio/HashingSink.kt
diff options
context:
space:
mode:
Diffstat (limited to 'okio/src/jvmMain/kotlin/okio/HashingSink.kt')
-rw-r--r--okio/src/jvmMain/kotlin/okio/HashingSink.kt139
1 files changed, 139 insertions, 0 deletions
diff --git a/okio/src/jvmMain/kotlin/okio/HashingSink.kt b/okio/src/jvmMain/kotlin/okio/HashingSink.kt
new file mode 100644
index 00000000..36bfd2b8
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/HashingSink.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.IOException
+import java.security.InvalidKeyException
+import java.security.MessageDigest
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+/**
+ * A sink that computes a hash of the full stream of bytes it has accepted. To use, create an
+ * instance with your preferred hash algorithm. Write all of the data to the sink and then call
+ * [hash] to compute the final hash value.
+ *
+ * In this example we use `HashingSink` with a [BufferedSink] to make writing to the
+ * sink easier.
+ * ```
+ * HashingSink hashingSink = HashingSink.sha256(s);
+ * BufferedSink bufferedSink = Okio.buffer(hashingSink);
+ *
+ * ... // Write to bufferedSink and either flush or close it.
+ *
+ * ByteString hash = hashingSink.hash();
+ * ```
+ */
+actual class HashingSink : ForwardingSink, Sink { // Need to explicitly declare sink pending fix for https://youtrack.jetbrains.com/issue/KT-20641
+ private val messageDigest: MessageDigest?
+ private val mac: Mac?
+
+ internal constructor(sink: Sink, digest: MessageDigest) : super(sink) {
+ this.messageDigest = digest
+ this.mac = null
+ }
+
+ internal constructor(sink: Sink, algorithm: String) : this(sink, MessageDigest.getInstance(algorithm))
+
+ internal constructor(sink: Sink, mac: Mac) : super(sink) {
+ this.mac = mac
+ this.messageDigest = null
+ }
+
+ internal constructor(sink: Sink, key: ByteString, algorithm: String) : this(
+ sink,
+ try {
+ Mac.getInstance(algorithm).apply {
+ init(SecretKeySpec(key.toByteArray(), algorithm))
+ }
+ } catch (e: InvalidKeyException) {
+ throw IllegalArgumentException(e)
+ }
+ )
+
+ @Throws(IOException::class)
+ override fun write(source: Buffer, byteCount: Long) {
+ checkOffsetAndCount(source.size, 0, byteCount)
+
+ // Hash byteCount bytes from the prefix of source.
+ var hashedCount = 0L
+ var s = source.head!!
+ while (hashedCount < byteCount) {
+ val toHash = minOf(byteCount - hashedCount, s.limit - s.pos).toInt()
+ if (messageDigest != null) {
+ messageDigest.update(s.data, s.pos, toHash)
+ } else {
+ mac!!.update(s.data, s.pos, toHash)
+ }
+ hashedCount += toHash
+ s = s.next!!
+ }
+
+ // Write those bytes to the sink.
+ super.write(source, byteCount)
+ }
+
+ /**
+ * Returns the hash of the bytes accepted thus far and resets the internal state of this sink.
+ *
+ * **Warning:** This method is not idempotent. Each time this method is called its
+ * internal state is cleared. This starts a new hash with zero bytes accepted.
+ */
+ @get:JvmName("hash")
+ actual val hash: ByteString
+ get() {
+ val result = if (messageDigest != null) messageDigest.digest() else mac!!.doFinal()
+ return ByteString(result)
+ }
+
+ @JvmName("-deprecated_hash")
+ @Deprecated(
+ message = "moved to val",
+ replaceWith = ReplaceWith(expression = "hash"),
+ level = DeprecationLevel.ERROR
+ )
+ fun hash() = hash
+
+ actual companion object {
+ /** Returns a sink that uses the obsolete MD5 hash algorithm to produce 128-bit hashes. */
+ @JvmStatic
+ actual fun md5(sink: Sink) = HashingSink(sink, "MD5")
+
+ /** Returns a sink that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes. */
+ @JvmStatic
+ actual fun sha1(sink: Sink) = HashingSink(sink, "SHA-1")
+
+ /** Returns a sink that uses the SHA-256 hash algorithm to produce 256-bit hashes. */
+ @JvmStatic
+ actual fun sha256(sink: Sink) = HashingSink(sink, "SHA-256")
+
+ /** Returns a sink that uses the SHA-512 hash algorithm to produce 512-bit hashes. */
+ @JvmStatic
+ actual fun sha512(sink: Sink) = HashingSink(sink, "SHA-512")
+
+ /** Returns a sink that uses the obsolete SHA-1 HMAC algorithm to produce 160-bit hashes. */
+ @JvmStatic
+ actual fun hmacSha1(sink: Sink, key: ByteString) = HashingSink(sink, key, "HmacSHA1")
+
+ /** Returns a sink that uses the SHA-256 HMAC algorithm to produce 256-bit hashes. */
+ @JvmStatic
+ actual fun hmacSha256(sink: Sink, key: ByteString) = HashingSink(sink, key, "HmacSHA256")
+
+ /** Returns a sink that uses the SHA-512 HMAC algorithm to produce 512-bit hashes. */
+ @JvmStatic
+ actual fun hmacSha512(sink: Sink, key: ByteString) = HashingSink(sink, key, "HmacSHA512")
+ }
+}