diff options
Diffstat (limited to 'okio/src/jvmMain/kotlin/okio/HashingSource.kt')
-rw-r--r-- | okio/src/jvmMain/kotlin/okio/HashingSource.kt | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/okio/src/jvmMain/kotlin/okio/HashingSource.kt b/okio/src/jvmMain/kotlin/okio/HashingSource.kt new file mode 100644 index 00000000..25b695d7 --- /dev/null +++ b/okio/src/jvmMain/kotlin/okio/HashingSource.kt @@ -0,0 +1,150 @@ +/* + * 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 source that computes a hash of the full stream of bytes it has supplied. To use, create an + * instance with your preferred hash algorithm. Exhaust the source by reading all of its bytes and + * then call [hash] to compute the final hash value. + * + * + * In this example we use `HashingSource` with a [BufferedSource] to make reading + * from the source easier. + * ``` + * HashingSource hashingSource = HashingSource.sha256(rawSource); + * BufferedSource bufferedSource = Okio.buffer(hashingSource); + * + * ... // Read all of bufferedSource. + * + * ByteString hash = hashingSource.hash(); + * ``` + */ +actual class HashingSource : ForwardingSource, Source { // Need to explicitly declare source pending fix for https://youtrack.jetbrains.com/issue/KT-20641 + private val messageDigest: MessageDigest? + private val mac: Mac? + + internal constructor(source: Source, digest: MessageDigest) : super(source) { + this.messageDigest = digest + this.mac = null + } + + internal constructor(source: Source, algorithm: String) : this(source, MessageDigest.getInstance(algorithm)) + + internal constructor(source: Source, mac: Mac) : super(source) { + this.mac = mac + this.messageDigest = null + } + + internal constructor(source: Source, key: ByteString, algorithm: String) : this( + source, + try { + Mac.getInstance(algorithm).apply { + init(SecretKeySpec(key.toByteArray(), algorithm)) + } + } catch (e: InvalidKeyException) { + throw IllegalArgumentException(e) + } + ) + + @Throws(IOException::class) + override fun read(sink: Buffer, byteCount: Long): Long { + val result = super.read(sink, byteCount) + + if (result != -1L) { + var start = sink.size - result + + // Find the first segment that has new bytes. + var offset = sink.size + var s = sink.head!! + while (offset > start) { + s = s.prev!! + offset -= (s.limit - s.pos).toLong() + } + + // Hash that segment and all the rest until the end. + while (offset < sink.size) { + val pos = (s.pos + start - offset).toInt() + if (messageDigest != null) { + messageDigest.update(s.data, pos, s.limit - pos) + } else { + mac!!.update(s.data, pos, s.limit - pos) + } + offset += s.limit - s.pos + start = offset + s = s.next!! + } + } + + return result + } + + /** + * Returns the hash of the bytes supplied thus far and resets the internal state of this source. + * + * **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 supplied. + */ + @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 source that uses the obsolete MD5 hash algorithm to produce 128-bit hashes. */ + @JvmStatic + actual fun md5(source: Source) = HashingSource(source, "MD5") + + /** Returns a source that uses the obsolete SHA-1 hash algorithm to produce 160-bit hashes. */ + @JvmStatic + actual fun sha1(source: Source) = HashingSource(source, "SHA-1") + + /** Returns a source that uses the SHA-256 hash algorithm to produce 256-bit hashes. */ + @JvmStatic + actual fun sha256(source: Source) = HashingSource(source, "SHA-256") + + /** Returns a source that uses the SHA-512 hash algorithm to produce 512-bit hashes. */ + @JvmStatic + actual fun sha512(source: Source) = HashingSource(source, "SHA-512") + + /** Returns a source that uses the obsolete SHA-1 HMAC algorithm to produce 160-bit hashes. */ + @JvmStatic + actual fun hmacSha1(source: Source, key: ByteString) = HashingSource(source, key, "HmacSHA1") + + /** Returns a source that uses the SHA-256 HMAC algorithm to produce 256-bit hashes. */ + @JvmStatic + actual fun hmacSha256(source: Source, key: ByteString) = HashingSource(source, key, "HmacSHA256") + + /** Returns a source that uses the SHA-512 HMAC algorithm to produce 512-bit hashes. */ + @JvmStatic + actual fun hmacSha512(source: Source, key: ByteString) = HashingSource(source, key, "HmacSHA512") + } +} |