Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

To generate an HMAC string for creating a SAS token in Kotlin Service Bus, you can use the following code:

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.*
import kotlin.experimental.and

fun generateSasToken(uri: String, keyName: String, key: String, expiry: Long): String {
    val stringToSign = "$uri\n$expiry"
    val signature = encodeToBase64(hmacSha256(key, stringToSign))
    return "SharedAccessSignature sr=${UriEncoder.encode(uri)}&sig=${UriEncoder.encode(signature)}&se=$expiry&skn=$keyName"
}

private fun hmacSha256(key: String, input: String): ByteArray {
    val keyBytes = key.toByteArray()
    val secretKeySpec = SecretKeySpec(keyBytes, "HmacSHA256")
    val mac = Mac.getInstance("HmacSHA256")
    mac.init(secretKeySpec)
    return mac.doFinal(input.toByteArray())
}

private fun encodeToBase64(bytes: ByteArray): String {
    return Base64.getEncoder().encodeToString(bytes).replace("\r\n", "")
}

object UriEncoder {
    private val hexCharLookup = charArrayOf(
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
    )

    fun encode(input: String): String {
        val result = StringBuilder()
        for (c in input) {
            if (c.isLetterOrDigit()) {
                result.append(c)
            } else {
                val bytes = c.toUtf8Bytes()
                for (b in bytes) {
                    result.append('%')
                    result.append(hexCharLookup[(b and 0xF0.toByte()).toInt() shr 4])
                    result.append(hexCharLookup[(b and 0x0F).toInt()])
                }
            }
        }
        return result.toString()
    }

    private fun Char.toUtf8Bytes(): ByteArray {
        val charAsInt = this.toInt()
        return when {
            charAsInt <= 0x7f -> byteArrayOf(charAsInt.toByte())
            charAsInt <= 0x7ff -> byteArrayOf((0xc0 or (charAsInt shr 6)).toByte(), (0x80 or (charAsInt and 0x3f)).toByte())
            charAsInt <= 0xffff -> byteArrayOf(
                (0xe0 or (charAsInt shr 12)).toByte(),
                (0x80 or ((charAsInt shr 6) and 0x3f)).toByte(),
                (0x80 or (charAsInt and 0x3f)).toByte()
            )
            else -> byteArrayOf(
                (0xf0 or (charAsInt shr 18)).toByte(),
                (0x80 or ((charAsInt shr 12) and 0x3f)).toByte(),
                (0x80 or ((charAsInt shr 6) and 0x3f)).toByte(),
                (0x80 or (charAsInt and 0x3f)).toByte()
            )
        }
    }
}

Then, you can use the generateSasToken function to generate a SAS token for your Service Bus namespace or entity. You need to pass in the URI of the namespace or entity, the key name and key value for the Shared Access Policy that you want to use, and the expiry time in seconds since the Unix epoch. The function will return a string in the format of "SharedAccessSignature sr={URI}&sig={HMACSHA256SIGNATURE}&se={EXPIRYTIME}&skn={KEYNAME}".